API overview
Everything is served by one FastAPI service on port 4603 (host port
API_PORT). There are two surfaces:
| Surface | Prefix | Versioned twin | Contract |
|---|---|---|---|
| Core inference and search | /detect, /faces, /persons, /embed, /search, /ingest, /analyze, /ocr, /clusters, /query, /models | Yes: every core router is also mounted under /v1 | FastAPI's live schema at /docs and /openapi.json |
| Curation (experimental) | /curation (OP_API_PREFIX); data routes are /curation/projects/{project}/... | No /v1 twin | contracts/openapi/curation.json (generated) and docs/design/curation_api_contract.md (hand-written) |
Health and metrics routes (/, /live, /ready, /health,
/health/pe_text, /metrics, /connection_pool_info) sit at the root
with no prefix; see Health and stall reasons.
curl http://localhost:4603/ | jq '.api_versions'
# {"current":"v1","supported":["v1"],"deprecated":[]}
Conventions
- Images are multipart uploads. Single-image routes take a file field
named
image; batch routes take a repeatedimagesfield./faces/verifytakesimage1andimage2. - Coordinates are normalized to
0.0-1.0(x1, y1, x2, y2); multiply by the image size for pixels. - Tuning knobs are query parameters (
confidence,top_k,min_score,model_name, ...), not JSON bodies./search/textand/search/ocrtaketextas a query parameter. - Embeddings from MobileCLIP and ArcFace are 512-dim and
L2-normalized; curation's PE-Core
pe_embeddingis 1024-dim. - No authentication, authorization or rate limiting on any route. Run it on a trusted network behind your own reverse proxy; see Security.
The curation contract
contracts/openapi/curation.json is app.openapi() filtered to the
/curation paths, regenerated with make contracts and checked for drift
by a pre-commit hook, so it always matches the code. TypeScript types for
the item wire format, class sources and region statuses are generated
next to it under contracts/ts/. Point any OpenAPI client generator at
that file.
The item wire shape is also exported as contracts/json/item_wire.json
and contracts/ts/itemWire.ts, and the keymap action registry as
contracts/json/keymap_actions.json.
The hand-written contract (docs/design/curation_api_contract.md) adds the
parts a schema can't express: which names are frozen, the one-vocabulary
wire rule, undo semantics, error behavior, and breaking-change notes for
consumers.
Three rules apply to every curation route:
- Projects scope the data. Every route that touches data is under
/curation/projects/{project}/. The only routes outside a project are project lifecycle and combine, the VLM endpoint registry and catalog,GET /curation/healthandGET /curation/events. See Curation routes. - The wire vocabulary is generic and fixed. Item-level region summary
fields go out as
region_<attr>, VLM output asvlm_*, classifier output asclassifier_*, whatever storage field names a deployment configured. A region's own data (geometry, score, state, text, cluster, thumbnail URL) is an element of the item'sregion_boxeslist. Every item-returning endpoint emits the same item shape from one serializer, so a client parses one shape. - Class identity is the name. Class ids belong to one project's registry. Datasets, combined projects and promoted models are matched to classes by name, never by number.
Read endpoints fail closed: a backend error is an HTTP error, never a
silently empty result. Project, config and import routes answer errors as
{"detail": {"error": "<code>", "message": "..."}}.