API Reference

The app-plane REST API your application calls, and the conventions every endpoint shares.

This section documents the API your application calls at runtime. The operation pages are generated from the server's OpenAPI contract, so their paths, parameters, schemas and samples match what the API accepts. This overview covers what they all have in common.

What is here

GroupContainsStart with
AuthSign-up, sign-in, tokens, magic links, email codes, Google and GitHub, sessions, linked identitiesSign up, Log in, Refresh
Your dataEvery table, through the same five operationsList, Create
OrganizationsYour app's own organizations: membership and invitationsMy organizations, Invite
UsersRole bindings for your app's usersList roles
BillingYour app's products, orders and subscriptionsSubscribe
AgentsInvoke an agent, follow its runs, call its tools directly, MCPInvoke, Call a tool, MCP

The tables under Your data are those of the demo app this reference is generated from. Your own app's tables follow exactly the same shape; the console's Code › Docs section opens a live reference for your app's own tables and endpoints, behind the app's docs password.

Base URL and paths

Every path begins with the app's uid:

https://api.authoric.ai/{appUid}/autocrud/{table}
  • The host is the hosted API, https://api.authoric.ai. These docs write it as $AUTHORIC_URL.
  • {appUid} is the app's App ID, shown with a copy button in the header of the app's page in the console. A request for one app can reach that app only.
  • {table} is one of your app's tables, by name. The examples use the example app's invoices.

Requests and responses are JSON (Content-Type: application/json), except file content and event streams.

Authentication

CredentialSent asReaches
Access token, from sign-up, sign-in, a magic link, an email code, or Google/GitHubAuthorization: Bearer <accessToken>Auth, Your data, Storage, Organizations, Users, Billing, as the signed-in app user
API key, created under App access › API keys and bound to one agentAuthorization: Bearer plak_… (or X-API-Key: plak_…)Agents: /{appUid}/agent/… and /{appUid}/mcp, as that agent

Access tokens are short-lived (15 minutes by default). When one expires the API answers 401 AUTH_TOKEN_EXPIRED; exchange the refresh token at POST /{appUid}/auth/token for a new pair and retry. A refresh token is single-use. A token pair acts in one of the user's organizations (auth.activeOrgUid), chosen at sign-in or refresh; a request cannot switch it.

An API key does not open the table API. An agent reaches data through its tools, which are derived from its roles. Every API-key failure, whether the key is missing, malformed, unknown, revoked or expired, is the same 401 API_KEY_INVALID.

Either way, a credential only identifies the caller. Whether a call is allowed is decided per request by the caller's roles and policies; see The governance model.

Records are named by uid

  • A row is addressed by its uid: GET, PATCH and DELETE /{appUid}/autocrud/{table}/{rowUid}.
  • Responses never contain the internal id or the org_id scope column.
  • A foreign key is sent and returned as <name>_uid: customer_id in the table is customer_uid in the API, holding the referenced row's uid. An unknown one is 400 ROW_REFERENCE_NOT_FOUND.
  • A body carrying id, uid, org_id or a raw foreign key column is 400 ROW_FIELD_NOT_ACCEPTED, and the message names the field to use instead.

Listing, filtering and paging

A table's list operation takes these query parameters:

ParameterFormatNotes
whereURL-encoded JSON filter treeEquality, null, $eq $ne $gt $gte $lt $lte, $in, $between, $contains, $startsWith, combined with $and / $or. A reference is filtered by its uid: {"customer_uid":"<uid>"}.
ordercolumn.asc,column.descUp to 3 columns. id sorts in creation order; uid cannot be sorted on.
columnscomma-separated namesOnly these columns, plus uid.
includecomma-separated references, without _uidUp to 3. Each referenced row is read with the caller's own permissions on its table.
limitinteger, 1 to 1,000Page size, 50 by default.
offsetinteger ≥ 0Rows to skip. Pair with limit and order.
aftercursorKeyset paging in creation order: empty for the first page, then paging.nextCursor.
total1Count every matching row into paging.total.
curl --get "$AUTHORIC_URL/$APP_UID/autocrud/invoices"   -H "Authorization: Bearer $TOKEN"   --data-urlencode 'where={"status":"approved","amount":{"$gte":1000}}'   --data-urlencode 'order=due_at.asc'   --data-urlencode 'limit=25'

Every list answers { "items": [...], "paging": { "limit", "offset", "count", "total", "hasMore" } }, where count is the rows in this page, and total the rows matching the filter when total=1 was asked for (otherwise null). A keyset page also carries nextCursor. Only the rows the caller may read are listed and counted: inside an app, that is the caller's own organization. The older filter=column:value and sort=column DIR still work.

Two more operations sit beside the list:

  • GET /{appUid}/autocrud/{table}/aggregate?spec=<JSON> counts, sums, averages, minimums and maximums, grouped, in the database. It takes the same permission as listing.
  • POST /{appUid}/autocrud/{table}/batch creates, changes or deletes up to 500 rows in one transaction, all or nothing.

Querying covers the filter language, paging and aggregates in full, and Batch writes the batch. For a join or a workflow across tables, add a custom action or a handler.

Changes to a table can also be streamed as server-sent events; see Realtime.

Idempotency

Writes to a table (POST, PATCH, DELETE, and a batch) and agent tool calls (POST /{appUid}/agent/tools/{name}) accept an optional Idempotency-Key header. Use a fresh unique value, such as a UUID, per logical operation and send the same value on every retry of it.

  • The first request runs, and its response is stored for 24 hours.
  • A repeat with the same key, path and body returns the stored response without running again.
  • The same key with a different body is 409. A repeat while the first is still running is 409.
  • A request that failed may be retried with the same key.

Without the header, a write is an ordinary write.

Errors

A failure returns one envelope:

{
  "error": {
    "error": "A row with that value already exists.",
    "code": "CREATE_DUPLICATE",
    "requestId": "3f9a1c7e-2b54-4d8a-9f10-6c2e7b5a4d31"
  },
  "requestId": "3f9a1c7e-2b54-4d8a-9f10-6c2e7b5a4d31"
}

Branch on error.code, never on the message. A plain policy denial is a 403 with no code. The request id is also in the X-Request-Id header. See Errors and status codes for what each status means, and the error-code catalog for every code.

Rate limits and quotas

Requests are rate limited at several levels: per IP address, per app, per account, per API key, more tightly for unauthenticated traffic, and per email address and IP on the sign-in and code doors. A limited request gets 429, with RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset headers and a Retry-After. Wait that long, then retry with backoff. The per-IP limit answers with the code RATE_LIMITED, and a browser on any website can read that response and its headers, so a front end can tell "slow down" from a network failure.

Separately from rate limits, an account's plan sets quotas (API requests, egress, storage and others). Past one, app-plane requests answer 402 SUBSCRIPTION_LIMIT_REACHED, with the metric in error.details; the console stays reachable so the plan can be changed.

What is not here

  • The platform plane. Creating apps, editing schema, writing policies and the rest of what the console does use the console's own API. It is documented as how-to guides, not as a contract, and is not something your application should call.
  • Internal tables. An app's database holds machinery Authoric uses to run it: policy rows, handler versions, the realtime outbox. They are not customer API and are left out.
  • Storage files and folders. The file API (/{appUid}/storage/…) is covered in the Storage guide.

What's next

On this page