Documentation
Everything to build on Sommelier Engine.
curl -X POST https://api.sommelierengine.ai/compose \
-H "Content-Type: application/json" \
-d '{"identity":"Barolo","vintage_year":2016}'Interactive reference and a live Playground ship with the Console. Quick Start below and most Guides now have real, contract-sourced content. Rate limits and SDKs stay honestly "planned" — neither has a real answer yet, and this page won't invent one.
Quick Start
Not specified in API_CONTRACT_v1.md — the contract has no authentication section for any endpoint. Measured live against the deployed origin (`serve/app.py`): the service has no authentication code path at all, and a request with no `Authorization` header and a request carrying an invalid Bearer token both returned the same 200 as a normal call — this API currently performs no authentication. Shown on this page as a reconciliation flag, not as a contract fact.
docs/API_CONTRACT_v1.md — full-file read, no auth section
The fastest way to see the API respond is POST /compose with a wine identity the registry already knows (docs/API_CONTRACT_v1.md:29-48). The call and response below are the same live-captured pair shown in Examples further down this page — repeated here, not re-derived, so Quick Start and Examples can never quietly disagree.
curl -X POST http://127.0.0.1:8081/compose \
-H "Content-Type: application/json" \
-d '{"identity":"Barolo"}'{
"snapshot_id": "sha256:08dcec31da3d2bc6c9d23abbd28d6d6a03c636bc5ed6cc9a98307e60b1fb8acc",
"handle": "[email protected]/1.1",
"identity": "Barolo",
"version": { "research": "1.1", "composition": "1.1", "engine": "0.1", "api": "v1" },
"profile": {
"descriptors": [
{ "descriptor": "cherry", "confidence": 0.85, "dominant_mechanism": "varietal_aroma" },
{ "descriptor": "tar", "confidence": 0.85, "dominant_mechanism": "varietal_aroma" },
{ "descriptor": "truffle", "confidence": 0.85, "dominant_mechanism": "varietal_aroma" }
/* 9 more descriptors trimmed for this page — full list in the evidence artifact */
],
"axes": {
"tannin": { "value": 0.8, "label": "high" },
"acidity": { "value": 0.8, "label": "high" },
"body": { "value": 0.5, "label": "medium-full" },
"sweetness": { "value": 0.05, "label": "dry" },
"alcohol": { "value": 0.65, "label": "medium-high" }
},
"confidence": { "final": 0.85 },
"warnings": [],
"is_blend": true,
"vintage_available": false,
"bottle_evolution_available": false
}
}docs/API_CONTRACT_v1.md:29-48
Four independent version axes: research, composition, engine (runtime), and api (this contract) (docs/API_CONTRACT_v1.md:7-9). A client pins api=v1 and negotiates the knowledge triple; engine internals may change with no api bump as long as the response shapes hold. GET /capabilities, in API Reference below, reports the exact version quad the service is running right now.
docs/API_CONTRACT_v1.md:7-9
API Reference
Sourced from docs/API_CONTRACT_v1.md, the Knowledge Service's versioned API contract — every field below cites the contract line it comes from. Not specified in API_CONTRACT_v1.md — the contract has no authentication section for any endpoint. Measured live against the deployed origin (`serve/app.py`): the service has no authentication code path at all, and a request with no `Authorization` header and a request carrying an invalid Bearer token both returned the same 200 as a normal call — this API currently performs no authentication. Shown on this page as a reconciliation flag, not as a contract fact.
Capability and version negotiation.
{
"research": "1.1", "composition": "1.1", "engine": "0.1", "api": "v1",
"supports": { "compose": true, "compose_bulk": true, "lookup": true, "search": true,
"explain": true, "validate": true, "health": true, "pairing": false },
"explain_levels": ["minimal","standard","debug"],
"search_kinds": ["descriptor","mechanism","identity","grape","region","style","alias","canonical_id"],
"async": { "supported": false, "ready": true }
}The contract does not scope error codes per endpoint (API_CONTRACT_v1.md:90-92, :100-110 define one global typed-error taxonomy and HTTP-status mapping, not a per-endpoint list). Shown below is that global taxonomy; which subset a given endpoint can actually raise is not stated in the contract, so none is asserted here beyond it.
Compose a versioned Knowledge Snapshot for a wine identity.
{
"snapshot_id": "sha256:…",
"handle": "[email protected]/1.1",
"identity": "Barolo",
"version": { "research":"1.1","composition":"1.1","engine":"0.1","api":"v1" },
"profile": {
"descriptors": [ { "descriptor":"tar","confidence":0.9,"dominant_mechanism":"varietal_aroma" } ],
"axes": { "acidity": {"value":0.8}, ... },
"confidence": { "final":0.85 },
"warnings": [],
"is_blend": false,
"vintage_available": false,
"bottle_evolution_available": false
}
}Contract's own example truncates `axes` with `...`; not filled in here to avoid fabricating a shape the contract doesn't give. `snapshot_id = sha256(identity + version triple + args + resolved profile)` (API_CONTRACT_v1.md:48).
The contract does not scope error codes per endpoint (API_CONTRACT_v1.md:90-92, :100-110 define one global typed-error taxonomy and HTTP-status mapping, not a per-endpoint list). Shown below is that global taxonomy; which subset a given endpoint can actually raise is not stated in the contract, so none is asserted here beyond it.
- Live-observed: an unknown identity returns HTTP 400 (evidence artifact quotes the transcript), not the 404 the contract's status table states (docs/API_CONTRACT_v1.md:106).
- Live-observed: a request missing the required `identity` field returns a raw Python `TypeError` message, not the typed `BadRequest` code the contract promises (docs/API_CONTRACT_v1.md:91, :105).
Compose many wines in one request (async-ready envelope).
{
"mode": "sync",
"job": null,
"count": 200,
"results": [ { "ok": true, "snapshot_id":"…", "identity":"…", "profile":{…} },
{ "ok": false, "identity":"Xyz", "error":"UnknownIdentity" } ]
}Async-ready: when async lands, `mode:"async"` + `job:{id,status,poll_url}` and `results:null` until ready — same envelope, no client break (API_CONTRACT_v1.md:61-62).
The contract does not scope error codes per endpoint (API_CONTRACT_v1.md:90-92, :100-110 define one global typed-error taxonomy and HTTP-status mapping, not a per-endpoint list). Shown below is that global taxonomy; which subset a given endpoint can actually raise is not stated in the contract, so none is asserted here beyond it. Note the response envelope's own per-item `error` field (e.g. "UnknownIdentity") reuses the global taxonomy's codes (API_CONTRACT_v1.md:58).
Fetch a Knowledge Snapshot by identity (no vintage/bottle args).
{
"snapshot_id": "sha256:…", "handle": "[email protected]/1.1", "identity": "Barolo",
"version": { "research":"1.1","composition":"1.1","engine":"0.1","api":"v1" },
"profile": { "descriptors":[...], "axes":{...}, "confidence":{"final":0.85}, "warnings":[],
"is_blend": false, "vintage_available": false, "bottle_evolution_available": false }
}"(no vintage/bottle args)" (API_CONTRACT_v1.md:64) scopes the REQUEST, not the response — the contract states the response is identical to POST /compose's.
The contract does not scope error codes per endpoint (API_CONTRACT_v1.md:90-92, :100-110 define one global typed-error taxonomy and HTTP-status mapping, not a per-endpoint list). Shown below is that global taxonomy; which subset a given endpoint can actually raise is not stated in the contract, so none is asserted here beyond it.
Search across descriptor/mechanism/identity/grape/region/style/alias/canonical_id.
{ "query":"oxidative","kind":null,"version":{…},
"results": { "descriptors":{…}, "identities":[…], "by_style":{…} },
"match_count": 3 }The contract itself abbreviates nested fields with `{…}`/`[…]` — not filled in here to avoid fabricating structure the contract doesn't provide.
The contract does not scope error codes per endpoint (API_CONTRACT_v1.md:90-92, :100-110 define one global typed-error taxonomy and HTTP-status mapping, not a per-endpoint list). Shown below is that global taxonomy; which subset a given endpoint can actually raise is not stated in the contract, so none is asserted here beyond it.
Causal mechanism graph for a descriptor, at a chosen detail level.
{ "descriptor": "tar", "dominant_mechanism": "varietal_aroma", "confidence": 0.9 }The contract gives no JSON example for /explain, only prose per level (API_CONTRACT_v1.md:76-78): minimal = {descriptor, dominant_mechanism, confidence}; standard = minimal + supporting_mechanisms and an evidence summary; debug = full mechanism graph (paths, suppressed duplicates, disagreement). This illustrative example covers only the minimal shape using field names the contract states; standard/debug are not JSON-ized here since the contract gives no field-level shape for either.
The contract does not scope error codes per endpoint (API_CONTRACT_v1.md:90-92, :100-110 define one global typed-error taxonomy and HTTP-status mapping, not a per-endpoint list). Shown below is that global taxonomy; which subset a given endpoint can actually raise is not stated in the contract, so none is asserted here beyond it.
Validate an object against a kind. Creates nothing.
{ "ok": true, "errors": [], "version": { "research":"1.1","composition":"1.1","engine":"0.1","api":"v1" } }Contract states the shape as prose (`{ok, errors[], version}`, API_CONTRACT_v1.md:81) but gives no JSON example and does not state what strings populate `errors[]` on a failed validation.
This endpoint's own `errors[]` response field (API_CONTRACT_v1.md:81) is a generic array whose contents the contract does not specify — it is not stated to reuse the global typed-error taxonomy below. The contract does not scope error codes per endpoint (API_CONTRACT_v1.md:90-92, :100-110 define one global typed-error taxonomy and HTTP-status mapping, not a per-endpoint list). Shown below is that global taxonomy; which subset a given endpoint can actually raise is not stated in the contract, so none is asserted here beyond it.
- Live-observed: the `version` object in a /validate response uses different key names (`research_version` / `composition_version` / `runtime_version`) than every other endpoint's `version` object (`research` / `composition` / `engine` / `api`) — the contract does not document this endpoint's `version` shape separately.
Readiness and version report.
{ "status": "ready",
"version": { "research":"1.1","composition":"1.1","engine":"0.1","api":"v1" },
"registry": { "loaded": true, "identities": 125, "registries": 1, "caps": {} },
"cache": {}, "tests": { "composition": "pass", "cross_layer_audit": "pass", "integration": "pass", "golden": "pass" } }Contract states the field list as prose (API_CONTRACT_v1.md:85-86), not a JSON example. Field names are transcribed as given; nested value shapes (`registry.identities` count, `cache`/`tests` contents) are illustrative placeholders, not contract-stated values.
Explicitly stated in contract, unlike other endpoints: returns HTTP 200 when ready, 503 when not ready; readiness = knowledge+registries+golden+active bundle (API_CONTRACT_v1.md:110). The contract does not scope error codes per endpoint (API_CONTRACT_v1.md:90-92, :100-110 define one global typed-error taxonomy and HTTP-status mapping, not a per-endpoint list). Shown below is that global taxonomy; which subset a given endpoint can actually raise is not stated in the contract, so none is asserted here beyond it.
- Live-observed: the response wraps the contract's flat `{status, version, registry, cache, tests}` fields (docs/API_CONTRACT_v1.md:85-86) inside `{ready, health, active_version}` instead of returning them at the top level. Full transcript (including current test-suite counters, not reproduced on this public page) is in the evidence artifact for this task.
The contract does not scope error codes per endpoint (API_CONTRACT_v1.md:90-92, :100-110 define one global typed-error taxonomy and HTTP-status mapping, not a per-endpoint list). Shown below is that global taxonomy; which subset a given endpoint can actually raise is not stated in the contract, so none is asserted here beyond it.
Examples
Real request/response pairs captured against the running Knowledge Service for this page (2026-08-23T03:44:00Z–2026-08-23T03:46:00Z) — not hand-written illustrations. One is an honest abstention: the engine refuses to guess a profile for a wine it doesn't know.
curl -X POST http://127.0.0.1:8081/compose \
-H "Content-Type: application/json" \
-d '{"identity":"Barolo"}'{
"snapshot_id": "sha256:08dcec31da3d2bc6c9d23abbd28d6d6a03c636bc5ed6cc9a98307e60b1fb8acc",
"handle": "[email protected]/1.1",
"identity": "Barolo",
"version": { "research": "1.1", "composition": "1.1", "engine": "0.1", "api": "v1" },
"profile": {
"descriptors": [
{ "descriptor": "cherry", "confidence": 0.85, "dominant_mechanism": "varietal_aroma" },
{ "descriptor": "tar", "confidence": 0.85, "dominant_mechanism": "varietal_aroma" },
{ "descriptor": "truffle", "confidence": 0.85, "dominant_mechanism": "varietal_aroma" }
/* 9 more descriptors trimmed for this page — full list in the evidence artifact */
],
"axes": {
"tannin": { "value": 0.8, "label": "high" },
"acidity": { "value": 0.8, "label": "high" },
"body": { "value": 0.5, "label": "medium-full" },
"sweetness": { "value": 0.05, "label": "dry" },
"alcohol": { "value": 0.65, "label": "medium-high" }
},
"confidence": { "final": 0.85 },
"warnings": [],
"is_blend": true,
"vintage_available": false,
"bottle_evolution_available": false
}
}Captured live, this task. Trimmed to 3 of 12 descriptors for page length — the untrimmed response is quoted verbatim in the evidence artifact. Field names and shape are unedited.
curl -X POST http://127.0.0.1:8081/explain \
-H "Content-Type: application/json" \
-d '{"identity":"Barolo","descriptor":"tar"}'{
"descriptor": "tar",
"dominant_mechanism": "varietal_aroma",
"supporting_mechanisms": [],
"confidence": 0.85,
"evidence": [["grape baseline"]]
}Captured live, this task, with no `level` field sent. The default response already includes `supporting_mechanisms` and `evidence` — fields the contract's prose (docs/API_CONTRACT_v1.md:77) assigns to the `standard` level, not `minimal`. The contract states no default level, so which level an omitted field resolves to is not specified there; this is what was observed live, not a contract claim.
curl -X POST http://127.0.0.1:8081/compose \
-H "Content-Type: application/json" \
-d '{"identity":"NotARealWineXYZ123"}'{
"error": "UnknownIdentity",
"message": "NotARealWineXYZ123"
}Captured live, this task. No profile is guessed for an identity outside the registry — the engine returns the typed error named in the contract's error list (docs/API_CONTRACT_v1.md:91) instead of a best-effort fabrication. Live-observed status is 400; the contract's own HTTP-status table (docs/API_CONTRACT_v1.md:106) maps UnknownIdentity to 404 — that mismatch is real and unresolved, not corrected here.
Guides
Sourced from the same docs/API_CONTRACT_v1.md as API Reference above — these expand on contract lines already cited there, not new claims.
Every typed error (BadRequest, UnknownIdentity, UnknownGrape, UnknownRegion, UnknownDescriptorMechanism, VersionIncompatible, CorruptedProvenance, PartialResearchVersion) maps to a stable HTTP status (docs/API_CONTRACT_v1.md:90-92, :100-110) — the full taxonomy table is in API Reference above. The contract states the service never returns a silent best-effort or fabricated result in place of one of these.
docs/API_CONTRACT_v1.md:90-92, :100-110
The same request against the same version quad always returns an identical snapshot_id (docs/API_CONTRACT_v1.md:96, "Guarantees") — snapshot_id = sha256(identity + version triple + args + resolved profile) (docs/API_CONTRACT_v1.md:48), so it is safe to use as a cache key or a client-side dedupe key.
docs/API_CONTRACT_v1.md:48, :96
POST /compose/bulk takes an items array (up to N, N unstated in the contract) and returns {mode, job, count, results[]} (docs/API_CONTRACT_v1.md:50-59) — see API Reference above for the full shape. The envelope is sync today; when async support lands, the same envelope switches to mode:"async" with a job object instead of breaking existing clients (docs/API_CONTRACT_v1.md:61-62).
docs/API_CONTRACT_v1.md:50-62