Paginate and search
How to list a whole catalog efficiently, and how to find releases by what people actually type.
Page through releases#
releases takes limit (at most 200) and offset.
Releases come most recently updated first. Ask
releasesCount for the total, so you know when to
stop:
query Page($offset: Int!) {
releasesCount
releases(limit: 200, offset: $offset) {
releasePk
upc
title
updatedAt
}
}{
"offset": 0
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query Page($offset: Int!) {\n releasesCount\n releases(limit: 200, offset: $offset) {\n releasePk\n upc\n title\n updatedAt\n }\n}",
"variables": {
"offset": 0
}
}
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 Page($offset: Int!) {
releasesCount
releases(limit: 200, offset: $offset) {
releasePk
upc
title
updatedAt
}
}
`,
variables: {
"offset": 0
},
}),
});
const { data, errors } = await res.json();{
"data": {
"releasesCount": 1284,
"releases": [
{ "releasePk": 48213, "upc": "0196871402293", "title": "Paper Radio", "updatedAt": "2026-09-17T09:14:04.771520+00:00" }
]
}
}A loop that fetches every release:
const PAGE = 200;
const all = [];
for (let offset = 0; ; offset += PAGE) {
const { data } = await schema(
`query ($offset: Int!) { releases(limit: 200, offset: $offset) { releasePk upc title } }`,
{ offset },
);
all.push(...data.releases);
if (data.releases.length < PAGE) break;
}Offsets are positions in a list ordered by updatedAt. If releases change
while you page, an item can shift between pages. For a full sync, collect by
releasePk and de-duplicate; re-run periodically to pick up changes.
Search#
Pass q to search. It is a case- and accent-insensitive partial match
against the release title, any release-level display artist, and any
label — a release matches if any one of them does. Accents fold on both
sides, so sinead finds Sinéad:
query {
releasesCount(q: "ghosts")
releases(q: "ghosts", limit: 10) {
title
contributors(role: "MainArtist") { party { name } }
labels { name }
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n releasesCount(q: \"ghosts\")\n releases(q: \"ghosts\", limit: 10) {\n title\n contributors(role: \"MainArtist\") { party { name } }\n labels { name }\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 {
releasesCount(q: "ghosts")
releases(q: "ghosts", limit: 10) {
title
contributors(role: "MainArtist") { party { name } }
labels { name }
}
}
`,
}),
});
const { data, errors } = await res.json();Pass the same q to releasesCount to page through search results.
Look up one release#
When you already know the UPC, release is a direct
lookup. It returns null if no release in your catalog has that UPC.
Browse by label#
labels lists every label in the catalog with its release
count, busiest first. A label's partyPk is the same on every release it
appears on, so you can group releases by label on your side.
Load nested data for many releases at once#
Nested fields are batched on the server. This request asks for the tracks of up to 50 releases and costs about as much as asking for one:
query {
releases(limit: 50) {
upc
title
resources {
isrc
title
durationSeconds
}
}
}curl https://api.schema.fm/graphql \
-H "Authorization: Bearer $SCHEMA_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'EOF'
{
"query": "query {\n releases(limit: 50) {\n upc\n title\n resources {\n isrc\n title\n durationSeconds\n }\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 {
releases(limit: 50) {
upc
title
resources {
isrc
title
durationSeconds
}
}
}
`,
}),
});
const { data, errors } = await res.json();The limit that applies is complexity — 500 selected fields per request — which counts fields in your query, not items in the response. See Limits.
Other lists#
Every other list takes a limit:
| Query | Default | Maximum |
|---|---|---|
| messages | 25 | 200 |
| blockedReleases | 25 | 200 |
| draftReleases | 25 | 200 |
| outstandingFiles | 200 | 1000 |
| labels | 200 | 1000 |
| vocabValues | 50 | 1000 |
Values above the maximum are clamped rather than rejected.