//SCHEMADocs
Console
Reference

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/json

Keys 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).

Warning

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 revoked

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

ScopeGrants
catalog_readRead releases, resources, credits, deliveries and vocabularies
catalog_writeDraft, edit and publish releases; read unpublished drafts
config_readList API keys, members and delivery accounts
config_writeIssue and revoke credentials; manage members and delivery accounts
media_readMint signed URLs for lossy encodings (aac-256, mp3-320…), artwork and delivered DDEX XML
media_previewMint signed URLs for preview clips and AAC 128 only — auditioning without a master-quality file
media_losslessMint signed URLs for lossless audio — FLAC, WAV, ALAC, a lossless original. Organizations with lossless media only — every enterprise organization, and others by arrangement
media_writeUpload files and deliver them
usage_readRead how much you have stored and delivered
billing_readRead your rates, invoices and projected spend
billing_writeUpdate 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:

RoleScopes
ownerall eleven
adminall eleven
memberall 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.

Operation or fieldKindRequires
cancelTrialmutationowner or admin, signed in
resumeTrialmutationowner or admin, signed in
startSubscriptionmutationowner or admin, signed in
alertCountsquerycatalog_read
alertsquerycatalog_read
artworkSizesquerycatalog_read
blockedReleasesquerycatalog_read
catalogquerycatalog_read
deliveryRejectionsquerycatalog_read
encodingProfilesquerycatalog_read
ingestionStatsquerycatalog_read
labelsquerycatalog_read
markAlertsReadmutationcatalog_read
messagesquerycatalog_read
outstandingFilesquerycatalog_read
releasequerycatalog_read
releasesquerycatalog_read
releasesCountquerycatalog_read
vocabKindIsClosedquerycatalog_read
vocabValuesquerycatalog_read
addResourcemutationcatalog_write
createPartymutationcatalog_write
createReleasemutationcatalog_write
creditReleasemutationcatalog_write
creditResourcemutationcatalog_write
deliverMessagemutationcatalog_write
discardBlockedReleasemutationcatalog_write
dismissAlertmutationcatalog_write
draftReleasequerycatalog_write
draftReleasesquerycatalog_write
publishReleasemutationcatalog_write
reinstateReleasemutationcatalog_write
removeReleaseCreditmutationcatalog_write
removeResourcemutationcatalog_write
removeResourceCreditmutationcatalog_write
reorderTracksmutationcatalog_write
updateReleasemutationcatalog_write
updateResourcemutationcatalog_write
withdrawReleasemutationcatalog_write
apiKeysqueryconfig_read
deliveryAccountKeysqueryconfig_read
deliveryAccountsqueryconfig_read
orgMembersqueryconfig_read
webhookEndpointqueryconfig_read
WebhookEndpoint.deliveriesfieldconfig_read
webhookEndpointsqueryconfig_read
addDeliveryKeymutationconfig_write
addOrgMembermutationconfig_write
createArtworkSizemutationconfig_write
createWebhookEndpointmutationconfig_write
deleteArtworkSizemutationconfig_write
deleteWebhookEndpointmutationconfig_write
grantableRolesqueryconfig_write
grantableScopesqueryconfig_write
issueDeliveryAccountmutationconfig_write
mintApiKeymutationconfig_write
redeliverWebhookmutationconfig_write
removeOrgMembermutationconfig_write
resetDeliveryPasswordmutationconfig_write
resetOrgMemberPasswordmutationconfig_write
revokeApiKeymutationconfig_write
revokeDeliveryAccountmutationconfig_write
revokeDeliveryKeymutationconfig_write
rotateWebhookSecretmutationconfig_write
sendTestWebhookmutationconfig_write
setOrgMemberRolemutationconfig_write
setSftpHostmutationconfig_write
updateArtworkSizemutationconfig_write
updateWebhookEndpointmutationconfig_write
Message.sourceUrlfieldmedia_read
Release.coverArtfieldmedia_read
requestEncodingmutationmedia_read
Resource.artworkfieldmedia_read
Resource.urlfieldmedia_read
completeUploadmutationmedia_write
deliverFilemutationmedia_write
requestDeliveryUploadmutationmedia_write
requestUploadmutationmedia_write
trialqueryusage_read
usagequeryusage_read
billingContactquerybilling_read
billingPeriodquerybilling_read
invoicesquerybilling_read
priceTermsquerybilling_read
createBillingPortalSessionmutationbilling_write
updateBillingContactmutationbilling_write
artworkSizeLimitsqueryany credential
changePasswordmutationany credential
viewerqueryany credential
webhookEventTypesqueryany credential

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.