Develop
What can I call, what does it return, and which of it can I reach from here?
Surface area
This app proxies a subset of the service under the same paths and signs each request with the developer’s own gcloud identity. Keys are minted and stored (catalog_router.py:259) and nothing verifies one yet, which is why the keys page says what a key will authenticate rather than what it does.
Quickstart
Identify audio. The response carries the match with the windows it was measured across — a score without its base is not a result.
curl -s -X POST "$CHOON/api/identify" \ -F 'file=@clip.wav'
Read the catalog that scan is matched against.
curl -s "$CHOON/api/catalog?limit=5" \ -H 'accept: application/json'
$CHOON is the deployed service. From here the same paths are reachable at /api/… on this origin — try one on the playground, which performs the call rather than describing it.
Enumerated from the running service
Reading the service's route table…
Catalog, develop and settings
| Method | Path | What it does | From the browser | Declared in |
|---|---|---|---|---|
| GET | /api/catalog | Recordings under protection, with QC state stated rather than inferred. Takes search, qc_status, limit, offset. | proxied | catalog_router.py:48 |
| GET | /api/catalog/{recording_id} | One recording: references, detections and watermark manifest, as three separate arrays. | proxied | catalog_router.py:97 |
| POST | /api/catalog/ingest | Add references. Reports grouped by outcome; every accepted row lands at qc_status pending. | backend only | catalog_router.py:176 |
| GET | /api/develop/keys | The keys minted for this scope. Only a hash is stored, never the secret. | proxied | catalog_router.py:244 |
| POST | /api/develop/keys | Mint a key. The plaintext is in that response and nowhere else, ever. | backend only | catalog_router.py:259 |
| DELETE | /api/develop/keys/{key_id} | Revoke a key. Sets revoked_at; the row stays. | backend only | catalog_router.py:278 |
| GET | /api/develop/logs | What was called and what it returned, within a stated retention window. | proxied | catalog_router.py:294 |
| GET | /api/develop/endpoints | The API surface, enumerated from the running app rather than a hand-kept list. | proxied | catalog_router.py:314 |
| GET | /api/settings | Stored settings, the settable keys, and the operating point actually in force. | proxied | catalog_router.py:345 |
| PUT | /api/settings/{key} | Set operating_point, alerts or ownership_defaults. Validates the operating point. | backend only | catalog_router.py:367 |
These writes are reachable from this browser as of 2026-08-24: lib/backend-proxy.ts forwards POST, PATCH, PUT and DELETE, and the catalog, develop and settings proxies each export all four. Reachable is not the same as wired — most surfaces below still print the curl rather than issuing the call, and each says which it does. The sentence that stood here until 2026-08-24 said the opposite ( “this app’s proxies are GET-only … v1 must be structurally incapable of triggering a write”); it is recorded rather than deleted because the argument under it was also wrong. What makes an external rights action possible is a credential and a client, not an HTTP verb on a proxy, so GET-only never enforced the property it was justified by.
Identify and audio
| Method | Path | What it does | From the browser | Declared in |
|---|---|---|---|---|
| GET | /api/health | Liveness. 200 once the server is up, whether or not models have loaded. | backend only | service/api.py:405 |
| GET | /api/ready | Readiness. 503 until models and indexes have finished loading. | backend only | service/api.py:411 |
| GET | /api/tracks | The catalog the index was built from. | proxied | service/api.py:435 |
| GET | /api/demo-tracks | The subset with audio available for the demo surfaces. | proxied | service/api.py:484 |
| POST | /api/identify | Submit audio, get matches with their per-channel evidence. | proxied | service/api.py:733 |
| POST | /api/identify_segments | Identify a long file segment by segment rather than as one query. | backend only | service/api.py:829 |
| POST | /api/simulate | Apply a named distortion server-side and identify the result. Deterministic. | proxied | service/api.py:988 |
| POST | /api/watermark/simulate | Embed and read back a payload under a named distortion. | proxied | service/api.py:1013 |
Monitor
| Method | Path | What it does | From the browser | Declared in |
|---|---|---|---|---|
| GET | /api/monitor/summary | Totals, claim facets and coverage, all scoped and all showing their inputs. | proxied | monitor_router.py:654 |
| GET | /api/monitor/detections | The queue. Filterable by claim state, decision state and recording. | proxied | monitor_router.py:421 |
| GET | /api/monitor/detections/{id} | One detection with its window points and per-channel evidence. | proxied | monitor_router.py:474 |
| GET | /api/monitor/freshness | When the last scan ran and how much of the catalog it covered. | proxied | monitor_router.py:317 |
| GET | /api/monitor/vocabularies | The closed vocabularies — claim, decision and review states. | proxied | monitor_router.py:286 |
| GET | /api/monitor/tracks | Per-recording rollup over detections. NOT a catalog listing: a recording with no detections does not appear. | proxied | monitor_router.py:714 |
| GET | /api/monitor/scans | Scan runs: what each touched, what it missed, whether it is still moving. | proxied | monitor_router.py:747 |
| GET | /api/monitor/audit | Every disposition, with the thresholds in force when it was made. | proxied | monitor_router.py:583 |
| GET | /api/monitor/export.csv | The queue as CSV, streamed through the proxy as a download. | proxied | monitor_router.py:1130 |
| PATCH | /api/monitor/detections/{id} | Record a disposition. | backend only | monitor_router.py:513 |
Not documented here
There is no published OpenAPI document, no client library, and no versioning policy.
FastAPI generates a schema at /docs on the service itself, which is the honest place to send someone until a published spec exists. A hand-written parameter table on this page would drift from the service the first time a default changed, and nothing would detect it.
Rate limits and quotas are not stated because none are enforced.
No middleware in service/api.py counts requests per caller, and nothing writes the request log either (see the log page). Publishing a limit nothing enforces would be a promise in one direction and a lie in the other.