REST API reference
Every record endpoint of your project's generated REST API: base URL, authentication, filters, paging, relationships, errors and limits.
Last updated
Every entity you create gets a REST API with no code to write. This page is the reference for it: where requests go, how they authenticate, what each record endpoint takes and returns, and what comes back when something goes wrong. For a walkthrough with a worked example, start with Call the instant REST API.
The request and response bodies are JSON, and every property name is snake_case.
Base URL and project ID
Every endpoint lives under your project:
https://api.my.anythink.cloud/org/{project_id}
Your project ID is the number after /org/ in the dashboard address. Every path on this page is relative to that base URL, so GET /entities/orders/items means GET https://api.my.anythink.cloud/org/{project_id}/entities/orders/items.
Entity names are the lower-case names you gave your entities in the data model, such as orders. They appear in the path exactly as written there.
Authenticate requests
Every endpoint needs credentials except the public record endpoints, the sign-in endpoints and public search. Send one of two things:
| Credential | Header | Use it for |
|---|---|---|
| User access token | Authorization: Bearer <access_token> |
Apps where a person is signed in. Each request runs as that user. See Sign in users: email, tokens and API keys. |
| API key | x-api-key: ak_… |
Servers, scripts and CI with nobody signed in. See API keys. |
GET /org/{project_id}/entities/orders/items
Authorization: Bearer eyJhbGciOiJIUzI1NiIs…
GET /org/{project_id}/entities/orders/items
x-api-key: ak_your_key_here
Put the API key in the x-api-key header, not in Authorization. Never ship a key in a browser or mobile app.
What each permission allows
Authentication says who is calling. Permissions say what they may do. Each entity has four permissions, named {entity}:{action}, and every record endpoint checks one of them:
| Permission | Allows |
|---|---|
orders:read |
List and get records. |
orders:create |
Create, bulk create and import records. |
orders:update |
Update records, and lock or unlock a record. |
orders:delete |
Delete and delete-many. |
A user gets permissions from their role, and an API key carries the permissions picked when it was created. A caller without the permission gets 403 Forbidden. On top of permissions, Groups and row-level security decides which individual records a caller can see or change, and field-level security can hide fields. See Roles and permissions.
Platform features use their own permissions in the same pattern, for example anythink_files:read for files, anythink_workflows:trigger for running a workflow and anythink_documentation:read for the API Docs.
Record endpoints
These nine endpoints exist for every entity. Replace {entity} with the entity name.
| Task | Request | Permission | Success |
|---|---|---|---|
| List records | GET /entities/{entity}/items |
read |
200 with a page of records |
| Get one record | GET /entities/{entity}/items/{id} |
read |
200 with the record |
| Create a record | POST /entities/{entity}/items |
create |
201 with the new record |
| Create many records | POST /entities/{entity}/items/bulk |
create |
200 with the new records |
| Import records | POST /entities/{entity}/items/import |
create |
204 |
| Update a record | PUT /entities/{entity}/items/{id} |
update |
204 |
| Delete a record | DELETE /entities/{entity}/items/{id} |
delete |
204 |
| Delete many records | POST or DELETE /entities/{entity}/items/delete-many |
delete |
204 |
| Lock or unlock a record | POST /entities/{entity}/items/{id}/set-lock-status?locked=true |
update |
204 |
The examples below use an orders entity with the fields reference, total, status and placed_on.
Try a request
In the Anythink dashboard
- Open API Docs in the sidebar and choose the Data Model API tab.
- Expand an operation under your entity, such as
GET /entities/orders/items. - Select Try it out, fill in the parameters and select Execute. The request is sent as the user you're signed in as.
With the CLI
anythink data list orders --limit 10 --filter 'status=paid&total=GTE:10'
anythink data get orders 3
anythink fetch "/entities/orders/items?status=paid&fields=reference,total"
anythink data covers list, get, create, update and delete. anythink fetch calls any path on this page, adds the project's base URL and signs the request for you.
With an AI assistant (MCP)
Show me the five most expensive paid orders.
The assistant uses the cli tool to run data list orders with a filter, and you see the records it gets back.
List records
GET /org/{project_id}/entities/orders/items?status=paid&sortBy=total&sortDirection=desc&page=1&pageSize=2&fields=reference,total
{
"items": [
{ "locked": false, "id": 3, "reference": "A-3", "total": 30, "__name": "A-3" },
{ "locked": false, "id": 1, "reference": "A-1", "total": 10.5, "__name": "A-1" }
],
"page": 1,
"page_size": 2,
"total_items": 3,
"total_pages": 2,
"has_next_page": true,
"has_previous_page": false,
"retrieval_time": 6
}
retrieval_time is how long the query took, in milliseconds.
| Parameter | Default | What it does |
|---|---|---|
page |
1 |
Page number, starting at 1. |
pageSize |
25 |
Records per page, from 1 to 1000. |
sortBy |
id |
The field to sort by. It must be a field of the entity. |
sortDirection |
asc |
asc or desc. |
fields |
all fields | Comma-separated fields to return. See Choose fields. |
| any field name | none | A filter on that field. See Filter records. |
pageSize, sortBy and sortDirection are camel case in the query string, even though the JSON is snake case. An out-of-range value returns 400 with a message such as 'pageSize' must be between 1 and 1000.
Filter records
Add the field name and a value to the query string. A bare value means equals. Put an operator before the value for anything else. Filters on different fields combine with AND.
| Operator | Matches | Example |
|---|---|---|
| (none) | Equal to | status=paid |
! |
Not equal to | status=!paid |
GT: |
Greater than | total=GT:10 |
GTE: |
Greater than or equal to | placed_on=GTE:2026-10-02 |
LT: |
Less than | total=LT:30 |
LTE: |
Less than or equal to | total=LTE:30 |
C: |
Contains, ignoring case | reference=C:A- |
NC: |
Does not contain | reference=NC:A |
SW: |
Starts with | reference=SW:A |
EW: |
Ends with | reference=EW:1 |
IN: |
Is one of a comma-separated list | reference=IN:A-1,B-2 |
To put two conditions on one field, separate them with a comma: total=GTE:6,LT:30 means 6 or more and under 30.
Dates take yyyy-MM-dd, a full timestamp or a Unix time in seconds. Booleans take true or false. A filter on a field the entity doesn't have is ignored, so a misspelt field name returns unfiltered records. Check a surprising total first.
To limit a read to one group's records, add group_scope={group_id}. The caller needs a role in that group that allows reading its data. See Groups and row-level security.
Choose fields
By default a record has every field you can read, plus these:
| Property | Meaning |
|---|---|
id |
The record ID. |
locked |
Whether the record is locked against changes. |
__name |
The value of the entity's name field, or the id if it has none. |
Pass fields to cut the response down. id, locked and __name always come back. created_at and updated_at come back only when you ask for them or leave fields out. Use fields=* to ask for everything explicitly.
GET /org/{project_id}/entities/orders/items/3?fields=reference
{ "locked": false, "id": 3, "reference": "A-3", "__name": "A-3" }
fields works on the single-record endpoint too. Relationship fields use dots; see Relationships in responses.
Get one record
GET /org/{project_id}/entities/orders/items/3
It returns the record on its own, without the paging wrapper. An unknown ID returns 404.
Create a record
Send the field values as a JSON object. Properties that aren't fields of the entity are ignored.
POST /org/{project_id}/entities/orders/items
Content-Type: application/json
{ "reference": "A-4", "total": 42, "status": "draft", "placed_on": "2026-10-04" }
{
"locked": false,
"id": 9,
"created_at": "2026-10-04T13:42:54.807588Z",
"updated_at": "2026-10-04T13:42:54.807588Z",
"reference": "A-4",
"total": 42,
"status": "draft",
"placed_on": "2026-10-04T00:00:00",
"paid": false,
"__name": "A-4"
}
The status is 201 Created, and the Location header points at the new record. id, created_at and updated_at are set for you. Creating a record also fires any workflow listening for a record-created event.
Create many records
Send an array. The response is an array of the records that were created, in the same shape as above.
POST /org/{project_id}/entities/orders/items/bulk
Content-Type: application/json
[
{ "reference": "B-1", "total": 5, "status": "draft" },
{ "reference": "B-2", "total": 6, "status": "paid" }
]
/items/import takes the same array but returns 204 No Content and no records. Use it to load data when you don't need the new IDs back.
Update a record
PUT changes only the fields you send. Fields you leave out keep their current values, so you don't need to send the whole record. There is no PATCH: it returns 405.
PUT /org/{project_id}/entities/orders/items/3
Content-Type: application/json
{ "status": "shipped" }
It returns 204 No Content. Read the record again if you need the new values. A locked record can't be updated or deleted: both return 400 Record is locked - no changes can be made. Lock or unlock a record with POST /entities/{entity}/items/{id}/set-lock-status?locked=true.
Delete records
DELETE /org/{project_id}/entities/orders/items/3
It returns 204 No Content, including when the record doesn't exist. To delete several, send the IDs as a JSON array:
POST /org/{project_id}/entities/orders/items/delete-many
Content-Type: application/json
[4, 5]
delete-many accepts POST or DELETE with that body and returns 204. Deleting a record that other records point to follows the relationship's deletion rule. See Connect entities with relationships.
Relationships in responses
When an entity has relationship fields, a record includes the related data under the field's name. What you get depends on the relationship type.
| Relationship | Shape under the field name |
|---|---|
| Many to one, one to one | The related record as an object with its fields, or null when it isn't set. |
| One to many, many to many | An object { "__count": n, "__data": [ … ] }. __count is the number of related records and __data holds them. |
A list of orders where each order has a customer (many to one) looks like this by default:
{
"locked": false,
"id": 3,
"reference": "A-3",
"customer": {
"id": 1,
"name": "Ada Lovelace",
"email": "ada@example.com",
"__name": "Ada Lovelace"
},
"__name": "A-3"
}
And the customers list, where each customer has many orders:
{
"id": 1,
"name": "Ada Lovelace",
"orders": {
"__count": 2,
"__data": [
{ "id": 3, "__name": "A-3", "customer": 1 },
{ "id": 7, "__name": "A-7", "customer": 1 }
]
}
}
By default a many-to-one or one-to-one field comes back fully expanded, while a one-to-many or many-to-many field comes back with each related record's id and __name only. Use fields to change either.
fields value |
Effect |
|---|---|
orders |
Expand the relationship with the related records' id and __name (collections) or all fields (single). |
orders.reference,orders.total |
Expand it with only those fields of the related records. |
customer.name |
Expand a many-to-one with only the related name. |
reference,total |
Leave relationship fields out of the response. |
Expansion follows relationships up to four levels deep and never visits the same relationship twice on one path. The related records you get back are filtered by the caller's permissions and row-level security, just like top-level records, so two callers can see different __data for the same record. To model relationships, see Connect entities with relationships.
Read public records
An entity marked public can be read without credentials:
| Task | Request |
|---|---|
| List | GET /entities/{entity}/public/items?page=1&pageSize=25 |
| Get one | GET /entities/{entity}/public/items/{id} |
Send page and pageSize on the list. These endpoints return only the fields you've marked public, and they take the same filter, sortBy, sortDirection and fields parameters as the authenticated list. For an entity that isn't public they return 404. Search has a public form too: GET /search/public.
Errors
Read the status code first. The body depends on where the error came from, so don't assume it's JSON.
| Status | When | Body |
|---|---|---|
400 |
A bad parameter or body, a locked record, or a value the database rejects. | Plain text message, such as 'page' must be greater than 0. |
401 |
No credentials were sent. | JSON: { "type": "…", "title": "Unauthorized", "status": 401, "traceId": "…" }. |
403 |
The caller lacks the permission, an API key isn't valid for the call, or the project is over its storage quota on a write. | Empty for permissions. A message for the quota. |
404 |
The entity doesn't exist, or the record doesn't exist or isn't visible to the caller. | Plain text for an unknown entity, such as Entity 'orders' not found. JSON in the same shape as 401 for an unknown record. |
405 |
The method isn't supported, such as PATCH on a record. |
Empty. |
429 |
The rate limit was reached. | JSON, shown below. |
500 |
Something failed on our side. | Message. Quote the traceId if you contact support. |
The problem-details bodies carry a traceId. Log it with the request, because it's what support needs to find the failure.
A record that row-level security hides from the caller returns 404, the same as a record that doesn't exist.
Limits
| Limit | Value |
|---|---|
| Page size | 1 to 1000 records. The default is 25. |
| Relationship expansion | Four levels deep. |
| Requests | A per-minute cap for each project and client IP address. The value is set per deployment, so read it from the X-RateLimit-Limit header rather than hard-coding it. |
| Storage | Writes and uploads are refused with 403 and a message once a project is over its storage quota. |
Every response carries rate-limit headers:
| Header | Meaning |
|---|---|
X-RateLimit-Limit |
Requests allowed in the window. |
X-RateLimit-Remaining |
Requests left in the window. |
X-RateLimit-Reset |
When the window resets. |
Once the limit is reached, requests return 429 until the window passes:
{
"error": "Rate limit exceeded",
"message": "Too many requests. Limit: 2000 requests per minute",
"retry_after": 60,
"route_type": "org"
}
Wait retry_after seconds, then retry. Loading many records? Use bulk or import rather than one request per record, and a larger pageSize rather than many small pages.
Other endpoints
The record endpoints are the part generated from your data model. The platform has fixed endpoints for the rest. Each links to the guide that covers it.
| Area | Main endpoints | Guide |
|---|---|---|
| Files | POST /files (multipart upload), GET /files, GET /files/{id}, GET /files/{id}/get (redirects to a download URL), DELETE /files/{id} |
Upload and manage files |
| Users | GET /users/me, GET /users, POST /users, PUT /users/{id}, DELETE /users/{id} |
Manage app users and team members |
| Sign in | POST /auth/v1/token, POST /auth/v1/refresh, POST /auth/v1/register |
Sign in users: email, tokens and API keys |
| Search | GET /search?q=&e=&page=&pageSize=, GET /search/public |
Search records across your entities |
| Workflows | POST /workflows/{id}/trigger, POST /workflows/api/{route} |
How workflows work, Triggers and scheduling |
Search takes q for the query (everything when empty), e for a comma-separated list of entities to search, and page and pageSize (default 20). Each result carries __entity and __url, which points at the record's endpoint.
Payment value lists
GET /enums returns the value lists behind AnythinkPay's dropdowns, so your app can label them without hard-coding. It needs anythink_entities:read. The response is an object keyed by list name, and each list is an array of { "value": "...", "label": "..." }:
anythink fetch /enums
The raw request:
GET /org/{project_id}/enums
{
"payment_provider": [
{ "value": "stripe", "label": "Stripe" },
{ "value": "apple", "label": "Apple" }
]
}
The lists include payment_status, payment_provider, billing_interval, subscription_event_type, subscription_event_source and reward_status. Send the value to the API and show the label to people.
Your project's own API documentation
The dashboard documents your project's API from your current data model, so it includes the entities you created yesterday. Open API Docs in the sidebar. It has two tabs:
- Data Model API covers your entities: list, get, create, update and delete for each, with their request and response shapes.
- Anythink API covers the platform endpoints, such as sign-in, files, search, workflows, dashboards and integrations.
Each tab is an interactive page. Use the filter box to find an endpoint and Try it out to send a real request as the user you're signed in as.
The dashboard has no download button, but the same documents are available over the API. They are OpenAPI 3 documents, so you can feed them to a client generator or an API tool.
GET /org/{project_id}/docs
Authorization: Bearer eyJhbGciOiJIUzI1NiIs…
The response is a JSON object with two OpenAPI documents inside it: data_model_documentation for your entities, and module_documentation.anythink for the platform endpoints. Pull out the one you want and save it as a file. The caller needs the anythink_documentation:read permission.