Authentication and scopes
Every request carries one credential. The credential decides who you are, which catalog you act on, and what you may do there.
API keys#
Programs authenticate with an API key in the Authorization header:
POST /graphql HTTP/1.1
Host: api.schema.fm
Authorization: Bearer sk_8Hq2xJ0vZQm1r3TbW7cN5yLs4KpA9eUdGfHj6VxOiBt
Content-Type: application/jsonKeys begin sk_. Each key belongs to one organization, acts on one catalog,
and carries an explicit set of scopes. The token is shown once, when the key
is minted; //SCHEMA stores only a hash, so a lost key is replaced, never
recovered. Mint keys in the console or with
mintApiKey.
The scheme name is case-insensitive (Bearer, bearer).
An API key is a secret. Use it from servers, never from a browser or a mobile app, and never commit it. If one leaks, revoke it — revocation is immediate.
Sessions#
The //SCHEMA console signs people in with an email and password, which sets an HTTP-only session cookie. Requests carrying that cookie are authenticated as the signed-in user, with the scopes their role grants.
Sessions exist for the console. For programs, use an API key. If a request
carries both, the Authorization header wins.
POST /auth/login {"email": "…", "password": "…"} → 200 + session cookie, or 401
POST /auth/logout → 204, session revokedFailed authentication#
A request with no credential, or with one that is unknown, revoked, expired
or belongs to a suspended organization, still returns HTTP 200: each field
that needed a credential comes back null with an UNAUTHENTICATED error.
The message does not say why a credential was rejected.
{
"data": null,
"errors": [
{
"message": "invalid or expired credential",
"locations": [{ "line": 1, "column": 3 }],
"path": ["viewer"],
"extensions": { "code": "UNAUTHENTICATED" }
}
]
}With no credential at all, the message is
not authenticated: present a bearer token in the Authorization header.
Scopes#
Authorization is checked per field. A query that asks for a field the
credential may not read gets null for that field and an error naming the
missing scope — the other fields in the same request still resolve.
| Scope | Grants |
|---|---|
catalog_read | Read releases, resources, credits, deliveries and vocabularies |
catalog_write | Draft, edit and publish releases; read unpublished drafts |
config_read | List API keys, members and delivery accounts |
config_write | Issue and revoke credentials; manage members and delivery accounts |
media_read | Mint signed URLs for lossy encodings (aac-256, mp3-320…), artwork and delivered DDEX XML |
media_preview | Mint signed URLs for preview clips and AAC 128 only — auditioning without a master-quality file |
media_lossless | Mint signed URLs for lossless audio — FLAC, WAV, ALAC, a lossless original. Organizations with lossless media only — every enterprise organization, and others by arrangement |
media_write | Upload files and deliver them |
usage_read | Read how much you have stored and delivered |
billing_read | Read your rates, invoices and projected spend |
billing_write | Update your billing contact details |
Reading metadata and downloading masters are deliberately separate grants: a
key that powers a catalog browser needs catalog_read; one that auditions
needs media_preview; one that pulls streams into your pipeline needs
media_read; and lossless needs media_lossless, which only an organization
with lossless media can put on a key — and which is checked against your plan
every time a URL is minted. See Stream media.
Usage and billing are separated the same way, and for the same reason.
usage_read returns quantities — bytes stored, bytes delivered — which a
monitoring system has every reason to want. billing_read additionally
discloses your negotiated rates. A key that charts delivery volume should hold
only usage_read, so your commercial terms stay out of it.
No credential can change what you are charged: pricing is agreed with
//SCHEMA, and billing_write covers contact details only.
Scopes by role#
A signed-in user's scopes come from their role in the organization:
| Role | Scopes |
|---|---|
owner | all eleven |
admin | all eleven |
member | all but config_write, media_lossless, billing_read and billing_write |
An owner or admin of an organization without lossless media holds
media_lossless and is still refused a lossless URL: the plan decides.
A member keeps usage_read: "how much have we delivered this month" is an
operational question, and answering it discloses nothing about the contract.
Scope spelling#
Scopes are written catalog_read everywhere you send or list them —
mintApiKey, ApiKey.scopes,
grantableScopes. The one exception is
Viewer.scopes, which returns the same scopes as CatalogRead,
MediaRead, and so on. Normalize if you compare them.
What each operation requires#
any credential means the field needs a valid credential but no particular
scope.
A credential cannot escalate#
A key can only mint keys with scopes it holds itself, so revoking a key also bounds everything it could have created. Only a signed-in owner can make someone else an owner; an API key never can.