powered by

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:

text
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.
http
GET /org/{project_id}/entities/orders/items
Authorization: Bearer eyJhbGciOiJIUzI1NiIs…
http
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

  1. Open API Docs in the sidebar and choose the Data Model API tab.
  2. Expand an operation under your entity, such as GET /entities/orders/items.
  3. 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

bash
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)

text
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

http
GET /org/{project_id}/entities/orders/items?status=paid&sortBy=total&sortDirection=desc&page=1&pageSize=2&fields=reference,total
json
{
  "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.

http
GET /org/{project_id}/entities/orders/items/3?fields=reference
json
{ "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

http
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.

http
POST /org/{project_id}/entities/orders/items
Content-Type: application/json

{ "reference": "A-4", "total": 42, "status": "draft", "placed_on": "2026-10-04" }
json
{
  "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.

http
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.

http
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

http
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:

http
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:

json
{
  "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:

json
{
  "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:

json
{
  "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": "..." }:

bash
anythink fetch /enums

The raw request:

http
GET /org/{project_id}/enums
json
{
  "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.

http
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.

Next steps