A free, public, read-only API over the catalogue. JSON, versioned, paginated, no key, no sign-up. The facts are CC0. The parts that aren't ours to give away are marked, and this page says which and why.
Describes /api/v1 as of 16 August 2026.
GET /api/v1/works/the-black-crook — that's it, that's the API.| Works | 939 | Catalogued at the Work tier |
| Productions | 13,459 | 1,536 resolve to a catalogued work; the rest
are served with inScope: false |
| People | 7,139 | Creators and performers, with credits |
| Venues | 135 | Buildings, with addresses and dates |
| Albums | 237 | Releases, with catalogue numbers and identifiers |
| Images | 10,601 | Metadata always; files under their own licences |
/api/v1/coverage returns
these numbers alongside the gaps, in the same response. That is deliberate. A
client that can only see the rows it was given cannot tell a thin catalogue
from a complete one, and the production tier here is Broadway-shaped in a way
the counts alone don't reveal.
curl -H 'Accept: application/json' \
'https://castalbumdb.org/api/v1/works?per_page=5'
Every response has the same shape.
{
"data": { … } or [ … ],
"meta": { "page": 1, "perPage": 50, "total": 939, "pages": 19 },
"links": { "self": "…", "next": "…", "prev": null },
"licence": { "data": "CC0-1.0", … }
}
The licence block is on every response without exception,
including errors' siblings and single records. If you are building something
that redistributes this data, it is the field to read.
"provenance": {
"sources": ["https://www.ibdb.com/broadway-production/the-black-crook-12518"],
"lastVerified": "2026-08-14",
"external": { "ibdb": "…", "wikipedia": "…" },
"via": "broadway-data"
}
A record with no source says so with an empty array rather than by dropping the key. "We don't know where this came from" is a finding, and it should survive a round trip through your database.
Generated from the running route table. If it's listed here it exists.
GET /api/v1 |
root
page per_page include |
GET /api/v1/ |
root
page per_page include |
GET /api/v1/openapi.json |
openapi |
GET /api/v1/works |
works
page per_page include |
GET /api/v1/works/{wid} |
work |
GET /api/v1/works/{wid}/albums |
work_albums |
GET /api/v1/works/{wid}/images |
work_images |
GET /api/v1/works/{wid}/licensing |
Who licenses this show for performance, and where that was read. |
GET /api/v1/works/{wid}/productions |
work_productions |
GET /api/v1/productions |
Every staging on record — 13,459 of them, and only 1,536 hang off a
page per_page include |
GET /api/v1/productions/{pid} |
production |
GET /api/v1/productions/{pid}/cast |
production_cast |
GET /api/v1/productions/{pid}/images |
production_images |
GET /api/v1/people |
people
page per_page include |
GET /api/v1/people/{pid} |
person |
GET /api/v1/people/{pid}/credits |
person_credits |
GET /api/v1/people/{pid}/images |
person_images |
GET /api/v1/venues |
venues
page per_page include |
GET /api/v1/venues/{vid} |
venue |
GET /api/v1/venues/{vid}/images |
venue_images |
GET /api/v1/venues/{vid}/productions |
venue_productions |
GET /api/v1/albums |
albums
page per_page include |
GET /api/v1/albums/{aid} |
album |
GET /api/v1/albums/{aid}/images |
album_images |
GET /api/v1/images |
images
page per_page include |
GET /api/v1/images/{iid} |
image |
GET /api/v1/licensing |
licensing_index
page per_page include |
GET /api/v1/collections |
The site's curated cuts through the catalogue, as data.
page per_page include |
GET /api/v1/collections/{slug} |
collection_detail |
GET /api/v1/search |
Titles and names, prefix-weighted. Deliberately narrow.
page per_page include |
GET /api/v1/coverage |
What the catalogue holds and, more usefully, what it does not.
page per_page include |
GET /api/v1/licence |
The terms, as data. The prose version is API-LICENCE.md and /api/licence. |
GET /api/v1/licence.md |
licence_text |
GET /api/v1/license |
The terms, as data. The prose version is API-LICENCE.md and /api/licence. |
OpenAPI 3.1 description, generated the same way.
?page=1&per_page=50,
maximum 100. Follow links.next rather than
computing offsets.sort or include is a 400 with
the allowed values in the body, because silently sorting by something else
is worse than failing.?sort=year, ?sort=-year to
reverse.ETag on everything,
Cache-Control: public, max-age=3600. Send
If-None-Match; a 304 doesn't count against your rate limit
budget in any meaningful way and costs the server almost nothing./api/v1, never the site's
HTML error page.
{"error": {"status": 404, "code": "not_found", "message": "…"}}Access-Control-Allow-Origin: *. Call it from a
browser.This is the part worth reading properly. The catalogue is a mixture of material under at least four different sets of terms, and a single blanket licence over the output would be a claim nobody here can support.
Drafted with care and with sources, by someone unqualified to give legal
advice. The full text with citations is at
/api/v1/licence.md.
| Layer | Licence | How you get it |
|---|---|---|
| Facts titles, dates, casts, credits, venues, catalogue numbers, song titles, identifiers |
CC0 1.0 no conditions |
Default. Every response. |
| Prose synopses, biographies, venue histories |
Per field mostly CC BY-SA 4.0 |
?include=prose |
| Book text 48,042 quoted passages |
Not served | Never. Counts and citations only. |
| Image files | Per image | Metadata always; pixels under their own terms |
The default response is single-licence on purpose. You have to ask for the mixed layer, and when you do, every field arrives carrying its own terms rather than inheriting one. You cannot accidentally mix them.
Facts aren't copyrightable in the United States — Feist Publications v. Rural Telephone Service, 499 U.S. 340 (1991), which also killed the "sweat of the brow" idea that effort alone earns protection. So for most of this, CC0 declares what was already true.
The EU and UK are the reason it matters anyway. The sui generis database right under Directive 96/9/EC protects substantial extraction from a database that took substantial investment, for fifteen years, restarting on substantial change; the UK kept it after Brexit. It doesn't care about Feist. CC0 waives it explicitly and a bare copyright licence doesn't, so a European reuser never has to run the argument.
Book quotations. The catalogue holds 48,042 verbatim passages from books about theatre that are still in copyright. Quoting one beside the record it illuminates is an ordinary use. Handing over all of them, machine-readable, in bulk, is a different act with a much worse argument. So the API serves the citation and withholds the text:
"withheld": {
"trivia": {
"count": 4,
"reason": "Verbatim text from a book still in copyright. …",
"citations": ["book:showtime-a-history-of-the-broadway-musical-theatre-larry-stempel#p13"]
}
}
A bibliography is a fact. You learn that Stempel discusses this show and on which page. The block exists rather than the field being silently absent, because a client that can't tell "no book discusses this" from "we're not allowed to show you" is being misled by omission.
Two fields with no provenance at all. funFact and
taglines were hand-entered upstream with no source recorded.
They're served only on request and marked
"licence": "unresolved". We don't know that we have the right to
license them to you, and we're not going to pretend otherwise.
The licence on a response does not cover image files. Metadata about an image is factual and CC0; the pixels are not ours to license.
licence.attributionRequired, licence.creator and
licence.credit are in every record. A CC BY or CC BY-SA image
published without its credit is a breach, not a discourtesy. Rendering it is
your job.null, because a documented absence
is a finding and a missing value is a gap.rendering: "blurred" says so. The unblurred file
isn't reachable through this API.licence.stated verbatim, plus
licence.publicationCleared. That field is a fact, not a
verdict — it doesn't mean the image is cleared, and it doesn't mean you
may not use it. It means nobody said anything, and now you know too.
(decisions.md §14.)licence.cropPolicy carries per-source crop terms.
unknown is not permission.This table is the same structure the serialiser executes. It can't be wrong about what the API does, because it's what the API does.
closingDate description era form openingDate performances previews pulitzer revivalOf season songs status themes title tonyCategories tonyNominations tonyWins venueId venueName wonBestMusical yearfunFact
unresolved · history
CC-BY-SA-4.0 · synopsis
CC-BY-SA-4.0 · taglines
unresolvedexcerpts openingNight productionNotes quotes triviaclosingDate era market openingDate performances previews productionType status tonyCategories tonyNominations tonyWins venueId venueName wonBestMusical wonBestPlay wonBestRevivalOfMusical wonBestRevivalOfPlay yearhistory
CC-BY-SA-4.0 · taglines
unresolvedexcerpts openingNight productionNotes quotes triviabirthYear deathYear name rolesbio
CC-BY-SA-4.0excerptsaddress altNames architect architecturalStyle capacity category closedYear coordinates isActive landmark name openedYear ownerhistory
CC-BY-SA-4.0excerptscatalogueNumber identifierNote identifiers label labelsNote labelsStated releaseDate territory titleA field that isn't in this table isn't served. That's an allowlist on purpose: the failure mode of an allowlist is a missing field, and the failure mode of a denylist is publishing 48,042 paragraphs of somebody else's book under CC0.
60 requests per 60 seconds per client.
20 for /search, which is heavier. Both are per
worker process, so in practice roughly double — the limit exists to stop one
scraper saturating a small machine, not to meter anything.
Over the limit is a 429 with retryAfter in the
body. Every response carries X-RateLimit-Remaining.
The numbers follow what the comparable catalogues do rather than being invented. MusicBrainz throttles anonymous clients to about one request a second and returns 503 above it. Open Library allows one a second unidentified, three with a User-Agent carrying a contact address, and says plainly that its API is for "human-facing discovery and lookup services" rather than bulk harvesting.
Set a User-Agent with a way to reach you. Nothing enforces it today. If this gets abused, identified clients are the ones that keep working.
If you want all of it, ask. A bulk export is a smaller favour than 939 requests, and it comes with a manifest saying which layer each field is in.
/api/v1/coverage names this as a gap rather than
leaving you to infer it.An earlier version of this page said they never change. That was wrong and it is the one thing on here worth getting right, so it is corrected in full below rather than quietly edited.
ids.md specifies identifiers that never change and are never
reused — W-0001A, P-002TX, N-0KQ44 — and
it exists because castalbumdb.com recomputed its singer index in alphabetical
order on every import, broke every inbound link and every citation, and its
maintainer knew and could not fix it.
That scheme is designed and not yet built. Counted today:
| Albums | 237 | Minted A- identifiers, per ids.md |
| Works, productions, people, venues | 21,672 | Title- and name-derived slugs inherited from the upstream dataset |
So /api/v1/works/the-black-crook is keyed on a slug, not on an
identifier. 122 people ids carry a numeric disambiguation
suffix — adolph-deutsch-2, andy-razaf-2 — and that
suffix is assigned by position among records that share a name. It is exactly
the shape that moves when a record is merged away or a third person of the same
name arrives.
What that means for you. Treat an id as the current address of a
record, not as a permanent key. Store the id and the title together so you can
re-resolve if a link stops working, and tell us if one does. Album
A- identifiers you may key on today; nothing else.
When minted identifiers land, the slugs become redirects rather than
disappearing — that is what ids.md §Slugs requires, and it is the
reason to fix this before the API has users rather than after.
/api/v1 will not become more restrictive than it is today. If
terms have to change it becomes /api/v2, and v1 keeps answering
under these terms for at least twelve months. CC0 is irrevocable in any case:
what you already have, you keep.
Full licence text · OpenAPI · Coverage and gaps · Site terms