Theatre Register

API

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

What's in it

Works939 Catalogued at the Work tier
Productions13,459 1,536 resolve to a catalogued work; the rest are served with inScope: false
People7,139 Creators and performers, with credits
Venues135 Buildings, with addresses and dates
Albums237 Releases, with catalogue numbers and identifiers
Images10,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": 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.

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

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.

Not written by a lawyer

Drafted with care and with sources, by someone unqualified to give legal advice. The full text with citations is at /api/v1/licence.md.

LayerLicenceHow 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.

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,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.

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.

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
closingDate description era form openingDate performances previews pulitzer revivalOf season songs status themes title tonyCategories tonyNominations tonyWins venueId venueName wonBestMusical year
?include=prose
funFact unresolved · history CC-BY-SA-4.0 · synopsis CC-BY-SA-4.0 · taglines unresolved
not served
excerpts openingNight productionNotes quotes trivia

production

CC0
closingDate era market openingDate performances previews productionType status tonyCategories tonyNominations tonyWins venueId venueName wonBestMusical wonBestPlay wonBestRevivalOfMusical wonBestRevivalOfPlay year
?include=prose
history CC-BY-SA-4.0 · taglines unresolved
not served
excerpts openingNight productionNotes quotes trivia

person

CC0
birthYear deathYear name roles
?include=prose
bio CC-BY-SA-4.0
not served
excerpts

venue

CC0
address altNames architect architecturalStyle capacity category closedYear coordinates isActive landmark name openedYear owner
?include=prose
history CC-BY-SA-4.0
not served
excerpts

album

CC0
catalogueNumber identifierNote identifiers label labelsNote labelsStated releaseDate territory title

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,042 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 939 requests, and it comes with a manifest saying which layer each field is in.

What's not in it

Stability

Identifiers are not stable yet. Do not key on them.

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:

Albums237 Minted A- identifiers, per ids.md
Works, productions, people, venues21,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.

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