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.
The short version
GET /api/v1/works/the-black-crook: that's it, that's the API.- Facts are CC0. Take them, sell them, no attribution required.
- Prose arrives only if you ask, and carries its own licence per field.
- 48,041 passages quoted from books in copyright are never served. You get the citation.
- Image files are under their own terms, per image. Read them.
- 60 requests a minute. Everything carries an ETag. Cache it and the limit stops mattering.
What's in it
| Works | 10,373 | Catalogued at the Work tier |
| Productions | 13,401 | 1,724 resolve to a catalogued work; the rest
are served with inScope: false |
| People | 7,369 | Creators and performers, with credits |
| Venues | 352 | Buildings, with addresses and dates |
| Albums | 4,074 | 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.
Start here
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": 10373, "pages": 208 },
"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.
Every record carries its provenance
"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.
Endpoints
Generated from the running route table. If it's listed here it exists.
service
GET /api/v1 |
root
page per_page include |
GET /api/v1/ |
root
page per_page include |
GET /api/v1/openapi.json |
openapi |
works
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 |
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 |
people
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 |
venues
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 |
albums
GET /api/v1/albums |
albums
page per_page include |
GET /api/v1/albums/{aid} |
album |
GET /api/v1/albums/{aid}/images |
album_images |
images
GET /api/v1/images |
images
page per_page include |
GET /api/v1/images/{iid} |
image |
licensing
GET /api/v1/licensing |
licensing_index
page per_page include |
collections
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 |
search
GET /api/v1/search |
Titles and names, prefix-weighted. Deliberately narrow.
page per_page include |
coverage
GET /api/v1/coverage |
What the catalogue holds and, more usefully, what it does not.
page per_page include |
licence
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.
Conventions
- Pagination:
?page=1&per_page=50, maximum 100. Followlinks.nextrather than computing offsets. - Filtering: listed per endpoint above. Unknown parameters are
ignored; an unknown
sortorincludeis a 400 with the allowed values in the body, because silently sorting by something else is worse than failing. - Sorting:
?sort=year,?sort=-yearto reverse. - Caching:
ETagon everything,Cache-Control: public, max-age=3600. SendIf-None-Match; a 304 doesn't count against your rate limit budget in any meaningful way and costs the server almost nothing. - Errors: always JSON under
/api/v1, never the site's HTML error page.{"error": {"status": 404, "code": "not_found", "message": "…"}} - CORS:
Access-Control-Allow-Origin: *. Call it from a browser. - Read-only: GET and HEAD. Anything else is 405.
The licence
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,041 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.
Why CC0 and not CC BY
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.
What we can't license, and admit it
Book quotations. The catalogue holds 48,041 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.
Images, in detail
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.
- 2,322 images require attribution.
licence.attributionRequired,licence.creatorandlicence.creditare 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. - 4,519 images have no licence statement of
any kind. Mostly Cover Art Archive, whose site says only that all images
are copyrighted by their owners. That string is passed through verbatim
rather than normalised to
null, because a documented absence is a finding and a missing value is a gap. - 254 are served blurred. Commons flags a costume,
personality or trademark right the photographer never held and so couldn't
license away.
rendering: "blurred"says so. The unblurred file isn't reachable through this API. - Some sources stated no licence at all: not a permissive one, not a
restrictive one, none. Those images are served with a URL, and their silence
is served with them:
licence.statedverbatim, pluslicence.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.) - Permission to use is not permission to alter.
licence.cropPolicycarries per-source crop terms.unknownis not permission.
Which fields are in which layer
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.
work
- CC0
closingDatedescriptioneraformopeningDateperformancespreviewspulitzerrevivalOfseasonsongsstatusthemestitletonyCategoriestonyNominationstonyWinsvenueIdvenueNamewonBestMusicalyear- ?include=prose
funFactunresolved ·historyCC-BY-SA-4.0 ·synopsisCC-BY-SA-4.0 ·taglinesunresolved- not served
excerptsopeningNightproductionNotesquotestrivia
production
- CC0
closingDateeramarketopeningDateperformancespreviewsproductionTypestatustonyCategoriestonyNominationstonyWinsvenueIdvenueNamewonBestMusicalwonBestPlaywonBestRevivalOfMusicalwonBestRevivalOfPlayyear- ?include=prose
historyCC-BY-SA-4.0 ·taglinesunresolved- not served
excerptsopeningNightproductionNotesquotestrivia
person
- CC0
birthYeardeathYearnameroles- ?include=prose
bioCC-BY-SA-4.0- not served
excerpts
venue
- CC0
addressaltNamesarchitectarchitecturalStylecapacitycategoryclosedYearcoordinatesisActivelandmarknameopenedYearowner- ?include=prose
historyCC-BY-SA-4.0- not served
excerpts
album
- CC0
catalogueNumberidentifierNoteidentifierslabellabelsNotelabelsStatedreleaseDateterritorytitle
A 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,041 paragraphs of somebody else's book under CC0.
Rate limits, and what to do instead
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 10,373 requests, and it comes with a manifest saying which layer each field is in.
What's not in it
- No bootleg links, ever. The catalogue records that unauthorised recordings exist. It has never held a link to audio and never will.
- No Recording tier yet. The model is Work → Production → Recording →
Release. Albums currently hang off works directly, with nothing between
them.
/api/v1/coveragenames this as a gap rather than leaving you to infer it. - No write access. Corrections go through the site.
- Nothing about a person that isn't a public professional credit.
Stability
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 | 4,074 | Minted A- identifiers, per ids.md |
| Works, productions, people, venues | 31,495 | 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. 18 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.
The terms are stable even though the ids are not
/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