Skip to content

ChoonDevelop

demoThis build is a demo. The catalog and detections are a fixed sample.Scopereading…Sound is used for one thing: a short tone when a long scan finishes while this tab is in the background. The A/B listening check plays audio either way, because there the sound is the evidence.

Develop

What can I call, what does it return, and which of it can I reach from here?

Surface area

20 of 28routes this page knows to be proxied here, of routes on record — the service did not answer, so this is the hand-kept list0of them require an API key today — no route checks one

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

MethodPathWhat it doesFrom the browserDeclared in
GET/api/catalogRecordings under protection, with QC state stated rather than inferred. Takes search, qc_status, limit, offset.proxiedcatalog_router.py:48
GET/api/catalog/{recording_id}One recording: references, detections and watermark manifest, as three separate arrays.proxiedcatalog_router.py:97
POST/api/catalog/ingestAdd references. Reports grouped by outcome; every accepted row lands at qc_status pending.backend onlycatalog_router.py:176
GET/api/develop/keysThe keys minted for this scope. Only a hash is stored, never the secret.proxiedcatalog_router.py:244
POST/api/develop/keysMint a key. The plaintext is in that response and nowhere else, ever.backend onlycatalog_router.py:259
DELETE/api/develop/keys/{key_id}Revoke a key. Sets revoked_at; the row stays.backend onlycatalog_router.py:278
GET/api/develop/logsWhat was called and what it returned, within a stated retention window.proxiedcatalog_router.py:294
GET/api/develop/endpointsThe API surface, enumerated from the running app rather than a hand-kept list.proxiedcatalog_router.py:314
GET/api/settingsStored settings, the settable keys, and the operating point actually in force.proxiedcatalog_router.py:345
PUT/api/settings/{key}Set operating_point, alerts or ownership_defaults. Validates the operating point.backend onlycatalog_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

MethodPathWhat it doesFrom the browserDeclared in
GET/api/healthLiveness. 200 once the server is up, whether or not models have loaded.backend onlyservice/api.py:405
GET/api/readyReadiness. 503 until models and indexes have finished loading.backend onlyservice/api.py:411
GET/api/tracksThe catalog the index was built from.proxiedservice/api.py:435
GET/api/demo-tracksThe subset with audio available for the demo surfaces.proxiedservice/api.py:484
POST/api/identifySubmit audio, get matches with their per-channel evidence.proxiedservice/api.py:733
POST/api/identify_segmentsIdentify a long file segment by segment rather than as one query.backend onlyservice/api.py:829
POST/api/simulateApply a named distortion server-side and identify the result. Deterministic.proxiedservice/api.py:988
POST/api/watermark/simulateEmbed and read back a payload under a named distortion.proxiedservice/api.py:1013

Monitor

MethodPathWhat it doesFrom the browserDeclared in
GET/api/monitor/summaryTotals, claim facets and coverage, all scoped and all showing their inputs.proxiedmonitor_router.py:654
GET/api/monitor/detectionsThe queue. Filterable by claim state, decision state and recording.proxiedmonitor_router.py:421
GET/api/monitor/detections/{id}One detection with its window points and per-channel evidence.proxiedmonitor_router.py:474
GET/api/monitor/freshnessWhen the last scan ran and how much of the catalog it covered.proxiedmonitor_router.py:317
GET/api/monitor/vocabulariesThe closed vocabularies — claim, decision and review states.proxiedmonitor_router.py:286
GET/api/monitor/tracksPer-recording rollup over detections. NOT a catalog listing: a recording with no detections does not appear.proxiedmonitor_router.py:714
GET/api/monitor/scansScan runs: what each touched, what it missed, whether it is still moving.proxiedmonitor_router.py:747
GET/api/monitor/auditEvery disposition, with the thresholds in force when it was made.proxiedmonitor_router.py:583
GET/api/monitor/export.csvThe queue as CSV, streamed through the proxy as a download.proxiedmonitor_router.py:1130
PATCH/api/monitor/detections/{id}Record a disposition.backend onlymonitor_router.py:513

Not documented here

Not in this build

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.

Looked, the field is absent

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.