HTTP API
Reef serves the provider's own inference routes: OpenAI at /v1/chat/completions and Anthropic at /v1/messages. It forwards each request to the runtime unchanged. It adds a small set of /reef/* routes for feedback, scenarios, artifacts, and status.
export REEF_TOKEN=reef-local
curl -f http://127.0.0.1:8900/healthz # {"ok": true}Routes #
|
Route |
Response |
|---|---|
|
GET /healthz |
readiness; the only unauthenticated route |
|
POST /v1/chat/completions |
OpenAI-format inference |
|
POST /v1/messages |
Anthropic-format inference |
|
POST /v1/messages/count_tokens |
count request tokens; recorded like any inference |
|
POST /reef/report |
submit feedback about one or more receipts |
|
GET /reef/scenarios |
every known scenario and current release |
|
POST /reef/scenarios |
create a scenario explicitly |
|
GET /reef/scenarios/{scenario}/contract |
what this scenario accepts |
|
GET /reef/scenarios/{scenario}/releases |
{scenario, releases}, newest first |
|
POST /reef/scenarios/{scenario}/rollback |
republish an earlier release as the head |
|
GET /reef/harness |
the served harness tree |
|
GET /reef/harness/releases |
the harness release catalog, oldest first |
|
GET /reef/harness/install |
a shell script that installs the tree |
|
GET /reef/status |
training, serving, and storage state |
Headers #
|
Header |
Required for |
|---|---|
|
x-reef-scenario |
inference, report, harness manifest and releases; optional on harness install. Names the workload a record belongs to. |
|
Authorization: Bearer <token> |
every route except GET /healthz, when auth is configured. |
|
x-reef-release-id |
optional: bind a new scenario to this starting release; on an existing scenario it must name the bound starting release, or the request is HTTP 409. Inference always answers from the scenario's current release; to pull a specific release, use ?release_id= on the harness manifest or install route. |
|
x-reef-tag-<name> |
optional on inference: opaque key/value context stored on the record under metadata.tags, for a processor to correlate on. Reef never reads a value. |
Scenarios #
The first inference request or report carrying a new x-reef-scenario creates the scenario using the deployment's configured recipe. Requests never select a recipe.
curl -sS -i http://127.0.0.1:8900/v1/chat/completions \
-H "Authorization: Bearer $REEF_TOKEN" \
-H "x-reef-scenario: hello-reef" \
-H "Content-Type: application/json" \
-d '{"model": "m", "messages": [{"role": "user", "content": "fix it"}]}'If the deployment sets reef.allow_implicit_scenario_creation: false, an unknown scenario returns HTTP 404 and you create it first:
|
Route |
Body and response |
|---|---|
|
POST /reef/scenarios |
{"name", "release_id"?} → {scenario, release_id, content_id}; 201 created, 200 already existed |
|
GET /reef/scenarios |
every known scenario and its current release once loaded |
|
GET /reef/scenarios/{scenario}/contract |
{scenario, processor, required_request_types} |
Inference #
Send the same body you would send to the provider. Reef never touches your sampling parameters. On a weight-serving deployment it adds engine bookkeeping keys: lora_path to address the served adapter and return_meta_info so the record proves which weights answered; a body naming a different lora_path is refused. Set "stream": true and read the SSE response for streaming.
The receipt identifies the stored record:
|
Response kind |
Where the receipt is |
|---|---|
|
non-streaming |
the x-reef-agent-record-id response header |
|
OpenAI SSE |
reef.agent_record_id in a final empty-choices chunk, immediately before data: [DONE] |
|
Anthropic SSE |
the same field on message_stop |
Streams carry it only after the record is stored.
Report #
Reef stores four fields and drops other top-level keys. Put harness-specific data inside metadata or feedback.
|
Field |
Type |
Required |
Notes |
|---|---|---|---|
|
score |
number |
no |
a bool is not a number and is rejected |
|
feedback |
string or object |
no |
opaque to Reef's core: a rubric, judge output, plain text |
|
references |
list of strings |
no |
the receipts this report grades; several batch as one trajectory sample, none is accepted but never trains |
|
metadata |
object |
no |
opaque, except training.eligible (default true) |
It answers {agent_record_id, scenario, request_type}.
client.report("hello-reef", {
"agent_record_id": "myharness:run42:trial7",
"score": 1.0,
"references": ["abc123"],
"metadata": {"harbor": {"trial_id": "run42:7"}},
})The optional top-level agent_record_id makes posting retry-safe: Reef uses it as the report's own record id, so an identical resend returns the stored record instead of reprocessing it, while the same id with different content is HTTP 409. It is not a receipt. Receipts go in references.
A recipe may declare a report schema. tttd declares one. In that case, Reef validates the declared score and metadata fields at ingress and answers HTTP 400 on a violation; feedback and undeclared metadata keys pass through unvalidated. To record a report but keep it out of training, send "metadata": {"training": {"eligible": false}}.
Receiving an update #
For weight-training scenarios there is nothing to do: keep calling the same inference endpoint and it serves the latest published weights.
Harness artifacts #
|
Route |
Response |
|---|---|
|
GET /reef/harness |
{release_id, content_id, parent_release_id, files, gate}, plus an x-reef-release-id response header |
|
GET /reef/harness/releases |
{scenario, releases}, oldest first, each training row carrying the gate metrics of the step that published it |
|
GET /reef/harness/install |
a self-contained POSIX shell script that installs the vendor binary and writes the tree |
All three are read-only and take x-reef-scenario. Install also requires ?adapter=, whose value may be pi, opencode, claude, dsh, or an external descriptor. If install omits x-reef-scenario, Reef creates a scenario with a generated harness- name and embeds that assignment in the wrapper script; when exactly one configured recipe serves harness files, it selects that recipe automatically.
Use ?release_id= on the manifest or install route to request a specific catalog release. An unknown or unrestorable release returns HTTP 404.
Rollback #
Pulling an older release changes only your local copy. To move the release Reef serves, send POST /reef/scenarios/{scenario}/rollback with {"release_id": "…"}; it answers the new head. Reef republishes that checkpoint as a new commit rather than rewinding history, so step numbers stay monotonic.
Choose a target from GET /reef/scenarios/{scenario}/releases, which lists newest first; GET /reef/harness/releases lists oldest first. Only releases marked restorable can be rolled back.
Status #
Read GET /reef/status when inference is still serving an older release while an update is being trained or published.
{
"error": null,
"last_drain_at": 1756400000.0,
"preload_errors": {},
"scenarios": {
"hello-reef": {
"scenario_step": 3,
"last_committed_step": {
"step": 3,
"recorded_at": 1756400000.0,
"metrics": {"published": false, "selection": {"reason": "candidate lost"}}
},
"current_runtime_load_id": "7f2a:12",
"checkpoint_storage": {"...": "..."},
"batch_ready": false,
"processor": {"...": "..."},
"inference_admission": {"...": "..."}
}
},
"serving": {"...": "..."}
}error and preload_errors report asynchronous training and preload failures. batch_ready says whether the processor has a batch waiting. last_committed_step reports the latest durable training step number, commit time, and its recipe-owned metrics; it is null before the first training commit. This distinguishes a step still in flight from a completed step that skipped or rejected its candidate. A rollback advances scenario_step without replacing the latest training outcome. A deployment without an agent-record directory has no historical commit log, so after a restart from a rollback checkpoint this field is null until the next training commit. serving is runtime-wide but recipe-shaped: a LoRA deployment reports the engine's shared adapter residency there, keyed by recipe. Each scenario's adapter_runtime_load_id appears in its own scenarios block.
Status codes #
|
Status |
Cause |
|---|---|
|
400 |
malformed body, a missing or empty x-reef-scenario on a scenario-scoped route, or a report violating the recipe's declared schema |
|
401 |
missing or wrong bearer token |
|
403 |
relayed from the upstream provider. Reef issues none of its own: an unaccepted token is 401, and per-scenario authorization belongs to the gateway in front of Reef. |
|
404 |
unknown scenario (with implicit creation off), unknown release, unknown adapter, no configured harness recipe, or a scenario that serves no files |
|
409 |
a base artifact conflicting with the scenario registration, record id resent with different content, a rollback naming a release that is not restorable, or an engine that reports no serving runtime load ID |
|
502 |
the upstream provider failed on its own account |
|
503 |
the artifact store is unreachable, or inference kept losing the weight-update race until its deadline |
Reef relays upstream 4xx failures with the provider's original message; the common client statuses (400, 401, 403, 404, 408, 409, 422, 429) keep their status code, and any other upstream 4xx comes back as 400.