Core concepts
The two planes, organizations, apps and users, uid addressing, roles and policies, handlers and agents.
A handful of ideas explain how Authoric is arranged. Every other page assumes them.
Two planes
Authoric keeps two kinds of database apart, and the API is shaped by the split.
- The platform plane is your Authoric account: your organization, its members, its subscription, and the apps it owns. You work in it through the console. The console's own API is not part of the published contract and is not something your application calls.
- The app plane is each app's own database: its users, tables, files, roles, policies,
handlers and agents. This is what your application calls at runtime, under
/{appUid}/…, and it is what the API reference documents.
A caller on an app's plane can reach that app and nothing else. Naming another app, or the
platform, is refused (CROSS_PLANE_FORBIDDEN) before anything is read, so a token minted for one
app's user can never create a table, change a policy, or read a neighbor's data.
Organizations, apps and users
| Thing | Lives on | What it is |
|---|---|---|
| Your organization | Platform | Your customer account. Owns apps, holds the subscription, has members. |
| Members | Platform | The people on your team who sign in to the console. |
| App | Platform registry, own database | One isolated database with its own users, tables, policies, handlers, storage and agents. |
| App users | App | The end users of your product. They sign up and sign in to your app, never to the console. |
| App organizations | App | Your product's own tenants, inside the app. Every app user belongs to at least one. |
Your organization and your app's organizations are unrelated. The second set is the multi-tenancy
your product offers its own customers: when someone signs up to your app they get an organization
of their own, and rows they create in an org-scoped table belong to it. Inside the app, a caller
sees only its own organization's rows; another organization's row is a 404, the same as a
missing one. You, the app's owner, see every organization's rows from the console. See
Organizations.
Isolation between apps is physical: each app is its own database. An app is created first and provisioned later, which is when the database is built; console sections that need it stay disabled until then.
Records are addressed by uid
Every record that leaves the database is named by its uid, a UUID. The sequential id is
the database's internal key: it never appears in a URL, a request, a response, an event, an SDK
call or a tool schema.
- A row is read, updated and deleted at
/{appUid}/autocrud/{table}/{rowUid}. - A reference to another row is sent and returned as
<name>_uid. A foreign key columncustomer_idappears in the API ascustomer_uid, holding the customer row's uid. - Sending
id,uid,org_idor a raw foreign key column in a body is a400(ROW_FIELD_NOT_ACCEPTED) whose message names the field to use instead. - Uids carry a creation timestamp. Don't treat one as secret, and sort by
created_at(oridinsort), never by uid.
Principals, roles and policies
A principal is whoever is making a request. In an app they are:
| Principal | How it calls | Typical use |
|---|---|---|
| App user | An access token from sign-in | Your product's runtime traffic |
| Agent | An API key bound to that one agent, or a console run | AI that reads and acts on the app's data |
| Scheduled job | Runs as its own principal on a clock | Background work |
| You, the owner | The console | Building and operating the app |
Every one of them is authorized the same way. A principal holds roles; roles carry
policies; a policy grants actions on resources, such as notes:read_list or
refunds:create, optionally under conditions (an amount ceiling, ownership of the row). Anything
not granted is denied. The check runs per request against the caller's own roles, so a token can
never widen its own reach. Your app's users start with the built-in User role, which grants
membership basics and no access to your tables until you add it.
Work done for the owner without a person clicking, such as the owner's agents, scheduled jobs and API keys, has an org reach: which of the app's organizations it may act in. The default is none beyond the owner's own records.
Read The governance model for how this extends to agents, and Access control for the console steps.
Handlers
A handler is server-side code you write for an app. It runs when an endpoint is called, on a
schedule, or as a tool an agent calls, and it reaches the database, storage, email and AI through
an SDK (sdk.*). Each SDK call is a capability (sdk:read, sdk:write, sdk:query, …) checked
against the principal the handler runs for, so a handler cannot do more than its caller may. See
Handlers and Scheduled jobs.
Agents
An agent is a principal in an app with an AI model behind it. It has no tool list of its own:
its tools are derived from the policies on its roles, and the same derivation serves its runs,
direct tool calls and MCP clients. Steps you mark as needing a person pause the run until someone
approves or rejects them. Your application reaches an agent over REST (/{appUid}/agent/…) or
MCP (/{appUid}/mcp) with the agent's API key. See Agents & AI.
The example app
Your app's API is shaped by the tables you create, so these docs need some tables to show. They
use a sample invoicing app with five: vendors, invoices (each referencing a vendor),
payments, purchase_orders and approval_thresholds. It is the app the
API reference is generated from, which is why an example such as
/{appUid}/autocrud/invoices links straight to its
operation page.
No app has these tables by default. Yours has only the tables you create; the
quickstart makes one called notes. Every table gets the same
five operations and the same rules, so an example carries over unchanged once you put your own
table and column names in it. Where a page describes a path in general, it writes the table as
{table}.