Workspaces and API keys
Authenticate every call with one key, and know exactly what that key may read and write.
One Bearer header authenticates everything. The key names its workspace, so no request ever carries a tenant parameter, and the key's roles decide what it may touch. A workspace holds your objects, records, files and people; in the SDK and the API it is called a tenant.
The Authorization header
Send the key as a Bearer token on every request:
curl https://api.standards.new/v1/records/contacts \
-H "Authorization: Bearer stndrds_your_api_key"Keys are read from the Authorization header only. A key in a cookie or query string is ignored.
Two refusals cover authentication and authorization:
| Status | Code | Meaning |
|---|---|---|
401 | none | Invalid, expired, or revoked API key. One message for all three cases. |
403 | SCHEMA_FORBIDDEN | The key is valid but its roles do not grant the action. The message never names the object or the action. |
Creating keys
POST /api-keys creates a key. The caller needs api-keys:create, which the built-in Owner role holds.
curl -X POST https://api.standards.new/v1/api-keys \
-H "Authorization: Bearer stndrds_your_api_key" \
-H "Content-Type: application/json" \
-d '{ "name": "status-page", "roleId": "role-uuid", "expiresAt": "2027-01-01T00:00:00Z" }'The plaintext secret is returned once, in this response. Store it in a secrets manager. Afterwards the API exposes the key's metadata only (name, prefix, creation date, last use, and the roles it holds).
What a key may do:
- Without
roleId, the key holds thememberrole. If your own access does not covermember, the request is refused with400SCHEMA_VALIDATION_FAILED: pass a narrowerroleId. - With
roleId, the key holds that role and nothing else — one dedicated role per integration. You can bind a role only when your own permissions cover everything it grants; otherwise403. - A personal key also acts within its owner's access: it may do only what both its role and its owner's current roles allow. Demote the owner and the key narrows on its next request; suspend or remove the owner and the key is refused.
ownergoes on a personal key only. An Owner may give their own personal key theownerrole (the app offers it in the personal key dialog), and it still narrows with them. A workspace key acts for nobody, soowneris refused there with403SCHEMA_API_KEY_OWNER_ROLE_REFUSED, whoever asks: at creation, and when roles are assigned to the key afterwards.- The
permissionsarray in the body is accepted and ignored.
Listing keys
GET /api-keys lists the workspace's keys, and GET /api-keys/:id reads one. Both need api-keys:read. Each key carries a roles array: the roles the key holds, which is what it may do. An abridged entry:
{
"id": "0b6f…",
"name": "status-page",
"prefix": "stndrds_1a2b3c4d_",
"userProfileId": null,
"roles": [{ "id": "role-uuid", "name": "status_reader", "label": "Status reader" }]
}- A key created with
roleIdlists that role. - A key created without
roleIdlistsmember. - An empty array means the key holds no role, so it has no permissions.
- Each entry carries the role's
id,nameandlabelonly, never its permissions. rolesis absent from the creation response.
Each key also reports whom it acts for:
authorityActorId— the person the key acts for. Its permissions are the intersection of its own roles and this person's.detachedAt/detachedBy— set on a key that acts with its own roles alone.
Personal and workspace keys
A key is personal or workspace:
- Personal — pass
userProfileId(your own) orpersonal: true. It acts for you: never more than your own access, re-read on every request. Only you can revoke it. A personal API key may create another personal key for its owner; a workspace key or an agent owns no profile and is refused with403SCHEMA_FORBIDDEN. - Workspace — no owner. A new workspace key is detached: it acts with its own roles alone, bounded when it is created by what its creator could grant.
Keys created before this release
No existing key was converted. Every key created by a person — directly or through another key — now acts for that person: it keeps what it could do while that person keeps their access, and narrows if they are demoted. A key whose chain does not end on a person (created by an agent, or whose creator no longer exists) and a key whose creator is suspended are refused until an administrator detaches it.
To make an integration key independent of the person who created it, detach it: POST /api-keys/:id/detach, standards keys detach <id> --yes, or Detach from creator in the API keys settings. Detaching needs api-keys:create, applies to workspace keys only, and is refused with 403 when the key holds a role wider than your own access. It is recorded in the activity log.
Permissions
A permission grants a set of actions on one target, within one of two scopes. On the wire:
{ "scope": "object", "target": "contacts", "actions": ["read", "update"] }In prose, this documentation writes resource:action, so architect:update means the update action on the architect system resource.
objectscope is business data. The target is an object name, or*for every object.systemscope is the platform itself. The target is one of the catalog's resources, or*for all of them. Onlyownercan be given*: saving it on any other role is refused, and no other role holds it. There is no catch-all resource outside the catalog: an unlisted target, or an action a resource does not define, is refused with400SCHEMA_PERMISSION_INVALID_GRANT.
Actions
| Action | Grants |
|---|---|
read | Fetching data. |
create | Creating records or resources. |
update | Modifying records or resources. |
delete | Soft-deleting and restoring. |
manage | Administrative operations, such as purging records for good. |
Actions are independent, not tiers: manage does not imply read, and delete does not imply update. * in a stored actions list means all five actions, manage included.
System resources
The full list of resources, their capability groupings, incremental levels and defaults lives on its own page: Roles and permissions. architect:update syncs a schema source and architect:delete removes one; people:read unlocks the user kind of the MCP search and read tools and the GET /actors directory.
Record routes check an object-scope permission on the object they touch: read to list, search and get, create to create, update to update and attach documents, delete to delete and restore, manage to purge (object:* only). Every other route checks the matching action on its own system resource.
Built-in roles
Every workspace starts with three roles you will meet:
| Role | Object access | System access |
|---|---|---|
owner | All actions on all objects, including purge. | All actions on all resources. |
member | read, create, update, delete on all objects — never purge. | read on people, architect, agents, settings, audit, forms; read, create, update, delete on connectors, files and documents. |
agent_default | read, create, update, delete on all objects. | read, create, update on documents and files; read on env-vars. |
See Roles and permissions for the full catalog, the object grant's purge switch, and how to restrict someone below member with a custom role.
A Member cannot change the schema, mint keys or administer people. Those stay with Owner unless a custom role grants them. Built-in roles cannot be edited or deleted.
Only an actor that already holds Owner can grant the Owner role. Any other role can be assigned, to a key or a person, only by an actor whose own permissions cover everything it grants; otherwise the assignment is refused with 403. Custom roles, and their assignment to keys and people, are managed from the People area of the app.
Expiry and revocation
- Expiry:
expiresAtis optional. An expired key is refused like an invalid one. - Revocation:
DELETE /api-keys/:id, orstandards keys revoke <id> --yes. The next request with that key gets a401. - The last owner is protected: revoking the only usable Owner credential the workspace has left is refused with
400SCHEMA_VALIDATION_FAILED,Cannot revoke the last owner role. Assign another owner first.An Owner key that acts for a person does not count as an owner of its own, since it can do nothing that person cannot: only a detached key keeps a workspace owned once its last human Owner is gone. - Auditing: each key records when it was last used, so stale keys are easy to spot.
Keys are server-side secrets
Never commit a key or ship it in browser code. The SDK refuses to run with an API key in a browser. Rotate a key you suspect has leaked: create the replacement, switch, then revoke the old one.
Signed-in sessions
A person signed in to the app calls the API with a short-lived session access token instead of a key. Signing out, including sign out everywhere, revokes the session's refresh token at once: no new access token can be obtained. An access token already issued keeps reaching the API until it expires, about 1 hour on standards.new: the API checks each token's signature locally, without a call to the auth server, so it cannot see a sign-out before then. A dedicated instance can narrow that window to 30 seconds.
Next steps
- Errors: what a
401, a403and every other refusal looks like on each door. - SDK:
createStandards({ baseUrl, apiKey })and the server-only rule. - MCP OAuth: sign an agent in with scopes instead of a pasted key.
- CLI:
standards loginstores a key per instance profile. - Book a demo