//SCHEMADocs
Console
How-to guides

Authenticate requests

How to authenticate from your code, and how to choose scopes so a leaked key does as little as possible.

Send the key#

Put the key in the Authorization header of every request:

Request
query {
  viewer { actorType catalogPk scopes }
}

Load the key from the environment or a secret manager — never from source code:

const SCHEMA_API_KEY = process.env.SCHEMA_API_KEY;
if (!SCHEMA_API_KEY) throw new Error("SCHEMA_API_KEY is not set");

Keep keys on the server#

A key grants everything its scopes allow on its catalog, to anyone who holds it. Call //SCHEMA from your backend and expose only what your app needs. Never ship a key in a web page, a mobile app or a desktop binary.

Choose the narrowest scopes#

Give each integration its own key with only the scopes it uses:

IntegrationScopes
A catalog browser or search pagecatalog_read
A playercatalog_read, media_read
A metadata sync that edits releasescatalog_read, catalog_write
An uploader for masters and artworkcatalog_read, catalog_write, media_write
An automated DDEX deliverercatalog_read, catalog_write, media_write
Provisioning tooling (keys, members, SFTP accounts)config_read, config_write

See the full table of which operation needs which scope.

Discover what a key can do#

Ask viewer at startup and fail fast if a scope you rely on is missing. viewer.scopes spells scopes CatalogRead, so normalize before comparing with catalog_read (schema() is the helper defined below):

const wire = (s) => s.replace(/[A-Z]/g, (c, i) => (i ? "_" : "") + c.toLowerCase());
const { data } = await schema(`{ viewer { scopes } }`);
const held = new Set(data.viewer.scopes.map(wire));
for (const need of ["catalog_read", "media_read"]) {
  if (!held.has(need)) throw new Error(`API key is missing ${need}`);
}

Handle rejection#

An invalid, revoked or expired key does not produce an HTTP error; the response carries an error with extensions.code of UNAUTHENTICATED. Check for it and stop retrying — the key will not start working on its own.

async function schema(query, variables) {
  const res = await fetch("https://api.schema.fm/graphql", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${SCHEMA_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ query, variables }),
  });
  const body = await res.json();
  if (body.errors?.some((e) => e.extensions?.code === "UNAUTHENTICATED")) {
    throw new Error("//SCHEMA rejected the API key");
  }
  return body;
}

Rotate a key#

Keys do not need to expire, but you can give one an expiresAt when you mint it. To rotate without downtime, see Manage API keys.