Manage API keys
Creating, auditing, rotating and revoking API keys from code. You need a credential with config_write.
Mint a key#
mutation {
mintApiKey(
name: "Player backend"
scopes: ["catalog_read", "media_read"]
expiresAt: "2027-09-18T00:00:00Z"
) {
apiKeyPk
token
tokenPrefix
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation {\n mintApiKey(\n name: \"Player backend\"\n scopes: [\"catalog_read\", \"media_read\"]\n expiresAt: \"2027-09-18T00:00:00Z\"\n ) {\n apiKeyPk\n token\n tokenPrefix\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
mintApiKey(
name: "Player backend"
scopes: ["catalog_read", "media_read"]
expiresAt: "2027-09-18T00:00:00Z"
) {
apiKeyPk
token
tokenPrefix
}
}
`,
}),
});
const { data, errors } = await res.json();{
"data": {
"mintApiKey": {
"apiKeyPk": 305,
"token": "sk_8Hq2xJ0vZQm1r3TbW7cN5yLs4KpA9eUdGfHj6VxOiBt",
"tokenPrefix": "sk_8Hq2xJ0vZ"
}
}
}Store token in your secret manager straight away. It is never shown again.
- Name every key for what uses it. The name is required, because a list of anonymous keys cannot be audited.
- Scopes use the wire spelling. You can only grant scopes you hold; grantableScopes lists them.
expiresAtis optional. A key without one works until revoked.catalogPkis optional and defaults to your own catalog. Pass another of your organization's catalogs — a sandbox, say — to mint a key for it.
List keys#
query {
apiKeys {
apiKeyPk
name
tokenPrefix
scopes
createdAt
expiresAt
lastUsedAt
revokedAt
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n apiKeys {\n apiKeyPk\n name\n tokenPrefix\n scopes\n createdAt\n expiresAt\n lastUsedAt\n revokedAt\n }\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
query {
apiKeys {
apiKeyPk
name
tokenPrefix
scopes
createdAt
expiresAt
lastUsedAt
revokedAt
}
}
`,
}),
});
const { data, errors } = await res.json();tokenPrefix is the first twelve characters of the token — enough to match
a key in your secret manager to its row here. lastUsedAt is updated lazily:
use it to find dormant keys, not to audit individual requests.
Rotate a key#
Keys can overlap, so rotation needs no downtime:
- Mint a new key with the same scopes.
- Deploy it everywhere the old one is used.
- Watch the old key's
lastUsedAtstop advancing. - Revoke the old key.
Revoke a key#
mutation {
revokeApiKey(apiKeyPk: 305)
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "mutation {\n revokeApiKey(apiKeyPk: 305)\n}"
}
EOFconst res = await fetch("https://api.schema.fm/graphql", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SCHEMA_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `
mutation {
revokeApiKey(apiKeyPk: 305)
}
`,
}),
});
const { data, errors } = await res.json();Revocation is immediate: the next request with that key is rejected. The key
remains in apiKeys with revokedAt set, so you can still see when it was
created and when it stopped working.
If a key leaks#
Revoke it first, then mint a replacement. A key could only ever mint keys with
its own scopes, so check apiKeys for any created while it was exposed and
revoke those too.