TauOpt REST API reference#
The hosted TauOpt service is a separate, closed repository; this page is rendered from its OpenAPI document (info.title tauopt, info.version 0.1.0, OpenAPI 3.1.0) dumped on 2026-10-31. The Python client in this repository wraps the experiment, suggestion, and observation routes below through the ask loop’s methods (Python API), and GET /v1/experiments, GET /health, and GET /v1/auth/check through Client.experiments(), Client.health(), and Client.auth_check(); the experiment summary, the CSV export of observations, GET /v1/me and GET /v1/usage/me are REST-only (the web app’s reads), and the session and token-management routes are the web app’s alone. The MCP server exposes twelve of the routes as tools (MCP tools). Bodies are JSON. Ids are integers in paths and strings in response bodies. Response objects are the wire models of the Python API (experiment, suggestion, observation). The hosted plane names the service build it runs in GET /health; this document describes the build it was dumped from.
Routes#
GET /health#
Health. Requires no authentication.
Responses:
200 – Successful Response:
{"status": "ok", "build": {...}}after a database round-trip –buildnames the service commit (service_sha), the engine (engine_version,engine_sha), the dependency fingerprint (deps_fingerprint) and the lock hash (uv_lock_blake2b) the image was made from; outside the pre-authentication gate and the/v1bucket429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1) –
rate_limited– beta lane only:/healthhas its own token bucket (5 requests per second, burst 10, the uptime checks’ budget, never shared with the/v1bucket) and it is empty; Retry-After 1500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
GET /v1/auth/check#
Auth Check. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Responses:
200 – The caller’s user id; the queue state on the headers:
{"user_id": <integer>}; the request is not counted as usage. Since the queue (2026-10-24) the caller’s queue state rides the RESPONSE HEADERS:X-Tauopt-Inflight(asks admitted to the master queue or running on a worker, across every instance),X-Tauopt-Inflight-Limit(the per-user bound the QUEUE applies: the organization’s override, else the service default of 8; the plan’sinflight_per_usercolumn is informational since deploy 2 retired the inline lane) andX-Tauopt-Queued(asks waiting in the caller’s own queue) – the numbers a client sizes its own parallelism by401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
POST /v1/auth/session#
Bootstrap. Authentication: a fresh Identity Platform ID token in Authorization: Bearer (the web app’s sign-in; a tau_ token answers 401).
Responses:
201 – Successful Response:
{"user_id": <integer>, "email": "<the verified address>", "expires_at": "<ISO 8601>"}and the session cookie inSet-Cookie(__Host-tauopt_session;Secure; HttpOnly; SameSite=Lax; seven days, re-issued while the session is in use). The one credential is a fresh Identity Platform ID token inAuthorization: Bearer, never atau_token: the web app’s sign-in calls this route, a script does not401 – unauthorized (the ID token did not verify, is stale, or names no admitted account; the body never says which)
403 – account_pending | account_disabled (a verified identity’s own account is not active) | origin_refused (no allowed Origin) –
account_pending/account_disabled– a verified identity whose account is not active (the cookie is cleared);origin_refused– no allowedOriginon the request408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
DELETE /v1/auth/session#
Logout. Authentication: the web app’s session cookie (a tau_ token answers 401).
Responses:
204 – Successful Response: 204 with the cookie cleared (
Max-Age=0), whether or not the presented cookie was live; under the session cookie only401 – unauthorized (a token or bearer on a session route)
403 – origin_refused (no allowed Origin beside the session cookie) –
origin_refused– no allowedOriginbeside the session cookie408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
GET /v1/experiments#
List Experiments. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Query parameters:
Name |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
no |
|
>= 0; <= 2147483647 |
|
|
no |
|
>= 0; <= 2147483647 |
|
|
no |
|
length 1..255 |
|
|
no |
|
– |
|
|
no |
|
– |
|
|
no |
|
– |
Responses:
200 – Successful Response:
{"count": <total under the filters>, "data": [experiment, ...]}– by default the caller’s ACTIVE experiments, newest first (sort=created,dir=desc), each WITHprogress;qnarrows by name (a case-insensitive substring),sortiscreated,updated,nameorobservation_count,dirisascordesc,include_deleted=trueadds the archived ones;limitis clamped to 1..200 and a negativeoffsetis a 400400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
POST /v1/experiments#
Create Experiment. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Request body: ExperimentIn (required).
Responses:
201 – Successful Response: the experiment object with
progress; itssuggestion_ttl_sechoes the request (null when the service default of 86400 s applies; 0 never expires)400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure naming the field;provide exactly one of metric | metrics;metric strategy/threshold require metrics mode: use metrics=[...]; an engine space or metric rule;num_solutions is single-objective only;constraint region too small: 0/4096 sobol probe points feasible; a bare stringmetric, refused with the message the Python client answers before any request leaves (Troubleshooting); a NUL byte, a lone surrogate, or a non-finite number anywhere in the body, one message per class (NUL bytes are not allowed;strings must be valid UTF-8 (lone surrogates are not allowed);non-finite numbers (NaN, Infinity) are not allowed)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – experiment_quota_exceeded / quota_unverifiable (the active-experiment quota) | rate_limited / credential_rate_limited (the beta lane) –
experiment_quota_exceeded– the active-experiment cap is reached; no Retry-After (archive an experiment first) –quota_unverifiable– the quota store could not be read; Retry-After 30500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
GET /v1/experiments/{experiment_id}#
Get Experiment. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Path parameters: experiment_id (integer; >= 1; <= 2147483647).
Responses:
200 – Successful Response: the experiment object with
progress, includingexpected_suggest_s(the median of the last five ask durations in seconds; null until one exists); archived experiments are returned withstatedeleted400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– unknown id, or another user’s experiment408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
PATCH /v1/experiments/{experiment_id}#
Patch Experiment. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Path parameters: experiment_id (integer; >= 1; <= 2147483647).
Request body: ExperimentPatch (required).
Responses:
200 – Successful Response: the updated experiment object with
progress;suggestion_ttl_ssent as an explicit null reverts the experiment to the service default, an absent field leaves it unchanged400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure naming the field;metric_thresholds requires metrics mode;unknown metrics [...]; an engine metric rule on the new thresholds; a NUL byte, a lone surrogate, or a non-finite number anywhere in the body, one message per class (NUL bytes are not allowed;strings must be valid UTF-8 (lone surrogates are not allowed);non-finite numbers (NaN, Infinity) are not allowed)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– unknown id, another user’s experiment, or an archived experiment408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – experiment_busy (another request holds the experiment; Retry-After 1) | connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1) –
experiment_busy– another request holds the experiment’s lock and the 3 s bounded wait expired; Retry-After 1 (the ownership read precedes the lock, so a 404 never waits)
DELETE /v1/experiments/{experiment_id}#
Archive Experiment. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Path parameters: experiment_id (integer; >= 1; <= 2147483647).
Responses:
204 – Successful Response: no body; the experiment stays readable by id with
statedeleted; there is no unarchive400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– unknown id, another user’s experiment, or an experiment that is already archived408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – experiment_busy (another request holds the experiment; Retry-After 1) | connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1) –
experiment_busy– another request holds the experiment’s lock and the 3 s bounded wait expired; Retry-After 1 (the ownership read precedes the lock, so a 404 never waits)
GET /v1/experiments/{experiment_id}/best_assignments#
Best Assignments. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Path parameters: experiment_id (integer; >= 1; <= 2147483647).
Responses:
200 – Successful Response:
{"count": n, "data": [...]}– the single best observation, the diverse top-k whennum_solutions> 1, or the threshold-filtered Pareto frontier when multimetric;{"count": 0, "data": []}when nothing has succeeded; archived experiments included400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– unknown id, or another user’s experiment408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
GET /v1/experiments/{experiment_id}/observations#
List Observations. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Path parameters: experiment_id (integer; >= 1; <= 2147483647).
Query parameters:
Name |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
no |
|
>= 0; <= 2147483647 |
|
|
no |
|
>= 0; <= 2147483647 |
|
|
no |
|
– |
|
|
no |
|
– |
Responses:
200 – Successful Response:
{"count": n, "data": [observation, ...]}– by default in id order (report order:sort=created,dir=asc);sortiscreated,value(the first OPTIMIZED metric: the v0 scalar, or in metrics mode the entrymetric_namemirrors),value-<metric>or a parameter name,dirisascordesc, with the id as the secondary key and an unsortable row last; anything else is a 400;countis the total;limitabsent lists every row, present it is clamped to 1..200, and a negativeoffsetis a 400; archived experiments included400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– unknown id, or another user’s experiment408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
POST /v1/experiments/{experiment_id}/observations#
Post Observation. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Path parameters: experiment_id (integer; >= 1; <= 2147483647).
Request body: ObservationIn (required).
Responses:
201 – Successful Response: the observation object; observing a suggestion closes it and frees its bandwidth slot
400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure naming the field;provide exactly one of suggestion_id | assignments;provide value(s) XOR failed=true; an engine rule on the assignments or metric values; an observation past 32768 bytes serialized (observation body exceeds 32768 bytes serialized); a NUL byte, a lone surrogate, or a non-finite number anywhere in the body, one message per class (NUL bytes are not allowed;strings must be valid UTF-8 (lone surrogates are not allowed);non-finite numbers (NaN, Infinity) are not allowed)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– unknown experiment or suggestion id, another user’s experiment, or an archived experiment408 – request_timeout (the body not received within 30 s; the gate answers before the route)
409 – conflict (the suggestion already has an observation) | suggestion_expired (details.assignments to re-post) | observation_cap_reached (details.cap / count) –
conflict– the suggestion already has an observation –suggestion_expired– the suggestion was closed by the TTL expiry and its slot reissued;detailscarriessuggestion_id,expired_at,created_at, and theassignmentsfor an unsolicited re-post (sendassignmentsinstead ofsuggestion_id); an observe that loses the row-lock race to the expiry gets this answer rather than a silent 201, while one that wins the row lock closes the suggestion first and answers 201 –observation_cap_reached– the experiment already holds its organization’s plan’smax_observations_per_experiment(4000 on the beta plan, 2000 on the free plan;details.cap,details.count), checked before the suggestion branch so an open suggestion stays open: archive the experiment and start a new one, dismiss what you cannot report, or, with repository access or at the public release, run the local driver413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
GET /v1/experiments/{experiment_id}/observations.csv#
Export Observations. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Path parameters: experiment_id (integer; >= 1; <= 2147483647).
Responses:
200 – The complete observation history as CSV (an attachment): the experiment’s observations as a UTF-8 CSV stream (a BOM first), REST-only: the Python client has no method for it
400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– unknown id, or another user’s experiment408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
GET /v1/experiments/{experiment_id}/suggestions#
List Suggestions. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Path parameters: experiment_id (integer; >= 1; <= 2147483647).
Query parameters:
Name |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
no |
|
– |
|
|
no |
|
length 1..128 |
|
|
no |
|
>= 0; <= 2147483647 |
|
|
no |
|
>= 0; <= 2147483647 |
Responses:
200 – Successful Response:
{"count": n, "data": [suggestion, ...]}in id order; archived experiments included;expired_atis set on a suggestion the TTL expiry closed and null otherwise;tagis the suggestion’s worker label or null, and?tag=narrows the list to the exact match, beside?state=–countis the total under both400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– unknown id, or another user’s experiment408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
POST /v1/experiments/{experiment_id}/suggestions#
Post Suggestion. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Path parameters: experiment_id (integer; >= 1; <= 2147483647).
Request body: SuggestionIn (optional; an empty body or null is accepted).
Responses:
201 – The open suggestion; the queue state on the headers: the suggestion object:
stateopen, theprovenancereceipt,intentwhen the suggestion is an authorized duplicate, andtag, the request’s worker label (null when the ask carried none). Before the bandwidth check, every open suggestion of the experiment older than itssuggestion_ttl_s(the service default of 86400 s when null; 0 disables) is closed withexpired_atset and its slot freed – lazy expiry, on this route only, so an orphan past its TTL still lists asopenuntil the experiment’s next ask. Since the queue (2026-10-24) the ask runs on a worker pool: the handler enqueues and waits within the ask’s bound, and the admission state rides the response headers of every answer served through the queue – the 201, or a typed refusal the ask ends in after the enqueue – and of atoo_many_queued:X-Tauopt-Admission(direct|queued|refused_queue|refused_parked|refused_share),X-Tauopt-Queue-Ms(arrival to lease),X-Tauopt-Inflight,X-Tauopt-Inflight-LimitandX-Tauopt-Queued; the door refusals (queue_full,too_many_parked) carry onlyX-Tauopt-AdmissionandX-Tauopt-Queue-Msof the five (withRetry-After1), a refusal from the prefix behind the door (a quota 429, the 409idempotency_key_mismatch, the prefix-stage 503) carries none of the five, and the idempotency joiner’s 429Retry-Afteralone. AnIdempotency-Keyrequest header makes a retry JOIN the original ask’s outcome (the 201 body or the typed error as first served) instead of starting a second ask; the key is scoped to the caller and the experiment400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure naming the field;config: <reason>whenconfigfailsOptimizerConfigvalidation;intents: <reason>for a malformed intent;tag: <reason>for a worker label outside 1 to 128 printable characters; an ask-argument rule (for example intents without dedup); a NUL byte, a lone surrogate, or a non-finite number anywhere in the body, one message per class (NUL bytes are not allowed;strings must be valid UTF-8 (lone surrogates are not allowed);non-finite numbers (NaN, Infinity) are not allowed)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– unknown id, another user’s experiment, or an archived experiment408 – request_timeout (the body not received within 30 s; the gate answers before the route)
409 – space_exhausted (details.terminal: proven completion) / unique_candidate_unavailable and the other draftless statuses (details.dedup) | idempotency_key_mismatch (the key was used against another experiment) –
idempotency_key_mismatch– theIdempotency-Keywas used against another experiment;details.experiment_idnames it –space_exhausted– proven exhaustion;details.dedupcarries the proof anddetails.terminalis true; the same answer repeats until the experiment state changes –unique_candidate_unavailable– no unique candidate and no proof;details.dedupcarries the record; the same answer repeats until the experiment state changes413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – bandwidth_full (Retry-After) | quota_exceeded / quota_unverifiable (the suggest quota) | rate_limited / credential_rate_limited (the beta lane) | too_many_queued (the per-user queue cap: Retry-After 1; details.inflight / inflight_limit / queued / queued_limit) | suggest_rate_limited (the per-user suggest rate on either path, ~10 a second with a burst of 20: Retry-After 1; details.rate_per_s / burst) | too_many_inflight_asks (a retry past the per-user joiner cap on one Idempotency-Key,
queue_join_waiters_per_user: Retry-After 1; details.inflight / inflight_limit / queued / queued_limit / joined) | too_many_parked (the per-user CEILING on an instance’s parked permits on the lanes with the credential door – the share beside other holders, the allowance alone – a capacity event, never a flooded mark: Retry-After 1; admission refused_share; details.parked / parked_share / max_parked) –too_many_inflight_asks– a retry under the sameIdempotency-Keyalready waits on one of the caller’s in-flight asks past the per-user waiter cap; Retry-After 1;detailscarriesinflight,inflight_limit,queued,queued_limitandjoined–too_many_queued– the caller’s own queue holdsqueued_limitasks waiting behind its in-flight bound (the organization’s override, else the service default of 8; the plan’sinflight_per_usercolumn is informational since deploy 2 retired the inline lane); Retry-After 1;X-Tauopt-Admission: refused_queue–too_many_parked– the caller holds its share of this instance’s suggest permits beside the other tenants, or its allowance alone; Retry-After 1;X-Tauopt-Admission: refused_share;detailscarriesparked,parked_shareandmax_parked–bandwidth_full– open suggestions already equalparallel_bandwidth; Retry-After 1 –quota_exceeded– the rolling 24-hour suggest quota; Retry-After is the time until the oldest counted suggestion leaves the window –quota_unverifiable– the quota store could not be read; Retry-After 30500 – all_rungs_failed / integrity_violation (the engine’s terminal failures: a constant message per type; details carry the typed evidence – each attempt’s rung, stage and exception class, intended, check – never the exception text, which stays in the service log under the response’s X-Request-Id) | internal_error (an engine-side ValueError, details.exception_type; or the constant crash envelope) –
all_rungs_failedorintegrity_violation– the engine’sAskFailed: every rung failed (details.intended,details.attempts– each attempt’srung,stageandexception_type) or a postcondition failed after a rung answered (details.check); the message is a constant per type and the engine’s exception text never rides the wire (it stays in the service log under the response’sX-Request-Id) –internal_error– any other engineValueError(details.exception_type), the engine’s fault and never the caller’s503 – suggest_timeout (on_timeout=fail past max_wait_s; Retry-After the estimate) | queue_full (every suggest permit of the instance held, prefix, parked or sending; Retry-After 1) | ask_deadline_exceeded (no Retry-After; details.point / stage / item_state / max_wait_s / backstop_s / elapsed_s / queue_wait_s / rung) | connection_pool_busy (Retry-After 1) –
queue_full– every suggest permit of this instance is held (prefix, parked or sending; a reserve keeps slots for observations and reads), the ask never entered the queue; Retry-After 1;X-Tauopt-Admission: refused_parked–suggest_timeout–on_timeoutisfailand the ask exceededmax_wait_s; Retry-After is the recent-ask estimate in seconds (at least 1) –ask_deadline_exceeded– the ask’s deadline: the engine’s caps-mode refusal (aconfigwithtimebox_mode: "caps"and an expiredmax_wait_s; a re-ask repeats deterministic work) or, on the queue lane, the service’s own backstop above the engine (details.pointbackstop: a hung ask ended past its bound, or the queue’s prefix bound); no Retry-After;detailscarries the engine’spoint,max_wait_s,elapsed_sandrungand, on the service’s own backstop refusals (its queue lane’s),stage(prefix: the pre-queue work overran;wait: the ask was not served within the bound),item_state,backstop_s,backstop_source,queue_wait_sanditem_id, as far as the ask got
DELETE /v1/experiments/{experiment_id}/suggestions#
Close All Suggestions. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Path parameters: experiment_id (integer; >= 1; <= 2147483647).
Responses:
200 – Successful Response:
{"closed": n}– every open suggestion is closed; archived experiments included400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– unknown id, or another user’s experiment408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – experiment_busy (another request holds the experiment; Retry-After 1) | connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1) –
experiment_busy– another request holds the experiment’s lock and the 3 s bounded wait expired; Retry-After 1 (the ownership read precedes the lock, so a 404 never waits)
DELETE /v1/experiments/{experiment_id}/suggestions/{suggestion_id}#
Close Suggestion. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Path parameters: experiment_id (integer; >= 1; <= 2147483647), suggestion_id (integer; >= 1; <= 2147483647).
Responses:
204 – Successful Response: no body; an already-closed suggestion answers 204 without change; archived experiments included
400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– unknown experiment or suggestion id, or another user’s experiment408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – experiment_busy (another request holds the experiment; Retry-After 1) | connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1) –
experiment_busy– another request holds the experiment’s lock and the 3 s bounded wait expired; Retry-After 1 (the ownership read precedes the lock, so a 404 never waits)
GET /v1/experiments/{experiment_id}/summary#
Experiment Summary. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Path parameters: experiment_id (integer; >= 1; <= 2147483647).
Responses:
200 – Successful Response: the web app’s one-call read, REST-only (the Python client has no method for it):
best_so_far(the series),counts(observations,failed,open_suggestions,suggestions_served),frontier,terminal(budget_reached,exhausted) andrevision– no experiment object and noprogress400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– unknown id, or another user’s experiment408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
GET /v1/me#
Me. Authentication: API token, or the web app’s session cookie.
Responses:
200 – Successful Response:
{"email", "status", "created_at", "credential", "org": {"id", "slug", "name", "kind", "plan", "caps": {"org": {...}, "member": {...}}, "usage": {"suggests_24h", "active_experiments", "live_tokens"}}, "providers": [...]}– the account the credential belongs to, its organization’s plan with the caps the plan resolves to (null= unlimited:suggests_per_day,active_experiments,default_token_expiry_days,max_observations_per_experiment,timebox_off_allowed; per membermax_tokensandinflight_per_user, the latter the effective in-flight bound the queue applies – the organization’s override, else 8) and the live usage against them;providersthe sign-in methods linked to the account (eachProviderOut:id,linked_at,last_sign_in_at);credentialistokenunder atau_token andsessionunder the cookie401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
PUT /v1/me/providers#
Sync Providers. Authentication: the web app’s session cookie (a tau_ token answers 401).
Request body: ProvidersSyncIn (required).
Responses:
200 – Successful Response: 200,
ProvidersOut: the account’s sign-in methods after the sync, eachProviderOutwith itsid(emailthe primary,google.com),linked_atandlast_sign_in_at; the request’s body,ProvidersSyncIn, is one fresh Identity Platform ID token whosefirebase.identitiesis the new set (the web app’s link and unlink flows end here; atau_token or a REST client has no business with it); under the session cookie only400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field) – or the typed sync refusals in the same envelope:id_token_invalid(the token did not verify),identities_missing(no identities claim; never read as no providers),primary_identity_missing(noemailidentity: the primary is never unlinked here)401 – unauthorized (no live session: a tau_ token is never a management credential; the cookie is cleared)
403 – origin_refused (a state-changing request under a session carries no allowed Origin) | subject_mismatch (the ID token names another identity subject than this account’s) –
subject_mismatch– sign in again as the account the page shows; the sign-in methods are left as they were408 – request_timeout (the body not received within 30 s; the gate answers before the route)
409 – snapshot_stale (a newer snapshot of the sign-in methods was already synced – the ID token was minted before it; reload the page to see it; no row is written) –
snapshot_stale– reload the page: the newer snapshot is the one shown, nothing was written413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
GET /v1/me/tokens#
List Tokens. Authentication: the web app’s session cookie (a tau_ token answers 401).
Responses:
200 – Successful Response:
{"data": [token, ...]}– the session user’s tokens, eachid,prefix,name,org_id,audience,created_at,last_used_at,expires_atand never the value; under the session cookie only (atau_token answers 401: it is not a management credential)401 – unauthorized (no live session: a tau_ token is never a management credential; the cookie is cleared)
403 – origin_refused (a state-changing request under a session carries no allowed Origin)
408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – token_quota_exceeded (the plan’s live-token cap per member; revoke one first) | rate_limited | credential_rate_limited –
token_quota_exceeded– the document declares it on this route (the shared token error table); a list cannot answer it, only a mint can (POST /v1/me/tokens, or a rotate whose successor would exceed a cap lowered beneath the live count)500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
POST /v1/me/tokens#
Mint. Authentication: the web app’s session cookie (a tau_ token answers 401).
Request body: MintIn (required).
Responses:
201 – Successful Response: 201, the new token object with
token, the raw value, in this answer only; the body isMintIn: an optionalname(1..255 characters) and an optionalexpires_in_days(1..3650; absent = the plan’s default expiry, none on the beta); under the session cookie only400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field)401 – unauthorized (no live session: a tau_ token is never a management credential; the cookie is cleared)
403 – origin_refused (a state-changing request under a session carries no allowed Origin) | account_pending | account_disabled (the status re-read under the mint’s lock)
408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – token_quota_exceeded (the plan’s live-token cap per member; revoke one first) | rate_limited | credential_rate_limited –
token_quota_exceeded– the plan’s live-token cap per member (5 on the beta); revoke a token first500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
DELETE /v1/me/tokens/{token_id}#
Revoke. Authentication: the web app’s session cookie (a tau_ token answers 401).
Path parameters: token_id (integer).
Responses:
200 – Successful Response:
{"id", "revoked": true, "mcp_revocation_lag_s", "note"}– the token is refused by the API at once and by the MCP server within its verdict cache (mcp_revocation_lag_sreports the service’s own setting, 60, which the service pins equal to the MCP boundary’s positive-cache TTL); another user’s token id answers 404; under the session cookie only400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field)401 – unauthorized (no live session: a tau_ token is never a management credential; the cookie is cleared)
403 – origin_refused (a state-changing request under a session carries no allowed Origin)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– not one of the caller’s tokens408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – token_quota_exceeded (the plan’s live-token cap per member; revoke one first) | rate_limited | credential_rate_limited –
token_quota_exceeded– the document declares it on this route (the shared token error table); a revoke cannot answer it, only a mint can (POST /v1/me/tokens, or a rotate whose successor would exceed a cap lowered beneath the live count)500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
POST /v1/me/tokens/{token_id}/rotate#
Rotate. Authentication: the web app’s session cookie (a tau_ token answers 401).
Path parameters: token_id (integer).
Responses:
201 – Successful Response: 201, the successor token object with
token(the raw value, shown once),revoked_id, and the revoke’s two fieldsmcp_revocation_lag_sandnote(the old value is refused by the MCP server within its verdict cache): the same name, audience and expiry, the old value refused at once by the API; under the session cookie only400 – Validation Error (the validation_error envelope; the message names the field) –
validation_error– a request-shape failure (the message names the field)401 – unauthorized (no live session: a tau_ token is never a management credential; the cookie is cleared)
403 – origin_refused (a state-changing request under a session carries no allowed Origin) | account_pending | account_disabled (the status re-read under the mint’s lock)
404 – not_found (the experiment, or the suggestion, is not the caller’s) –
not_found– not one of the caller’s tokens408 – request_timeout (the body not received within 30 s; the gate answers before the route)
409 – token_expired (a rotation never lengthens a credential’s life: an expired token has nothing to rotate into – revoke it and mint a new one) –
token_expired– an expired token has nothing to rotate into: revoke it and mint anew413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – token_quota_exceeded (the plan’s live-token cap per member; revoke one first) | rate_limited | credential_rate_limited –
token_quota_exceeded– the document declares it on this route (the shared token error table); the successor is minted under the mint’s cap check with the rotated token excluded, so a rotate answers it only when a cap lowered beneath your live count already binds500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
GET /v1/usage/me#
My Usage. Authentication: API token, or the web app’s session cookie (every /v1 route takes either; the Python client sends a token).
Responses:
200 – Successful Response:
{"suggests_24h", "suggests_cap", "active_experiments", "active_cap", "plan"}– the two quota counters against their caps and the plan name (the same numbers asGET /v1/me’susageandcaps.org)401 – unauthorized (a missing, malformed, unknown, revoked or other-lane token)
408 – request_timeout (the body not received within 30 s; the gate answers before the route)
413 – payload_too_large (a body past 1 MiB; the gate answers before the route)
429 – rate_limited (the beta lane’s buckets: the /v1 bucket, or /health’s own; Retry-After) | credential_rate_limited (the beta lane’s per-credential bucket, or its request concurrency; Retry-After 1)
500 – internal_error (a crash: the constant envelope; the traceback stays in the server log)
503 – connection_pool_busy (every pooled database connection on this instance is in use past the 5 s checkout wait; Retry-After 1)
Request body schemas#
Field order follows the service’s models. null as a default means the field may be omitted or sent as null. <= 2147483647 on an integer field is the service’s column unit (a PostgreSQL integer): a larger value answers 400 validation_error.
ExperimentIn#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
length 1..255 |
|
|
yes |
|
1..256 items |
|
|
no |
|
– |
|
|
no |
|
1..256 items |
|
|
no |
|
<= 64 items |
|
|
no |
|
>= 1; <= 2147483647 |
|
|
no |
|
>= 1; <= 2147483647 |
|
|
no |
|
>= 1; <= 2147483647 |
|
|
no |
|
>= 0; <= 2147483647 |
|
|
no |
|
– |
Unknown fields are rejected with 400 validation_error.
ExperimentPatch#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
no |
|
length 1..255 |
|
|
no |
|
>= 1; <= 2147483647 |
|
|
no |
|
>= 1; <= 2147483647 |
|
|
no |
|
>= 0; <= 2147483647 |
|
|
no |
|
– |
|
|
no |
|
<= 256 keys |
Unknown fields are rejected with 400 validation_error.
LinearConstraintIn#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
<= 256 keys |
|
|
yes |
|
– |
|
|
yes |
|
– |
Unknown fields are rejected with 400 validation_error.
MeOut#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
MemberCaps#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
|
|
yes |
|
– |
MetricIn#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
length 1..255 |
|
|
no |
|
– |
|
|
no |
|
– |
|
|
no |
|
– |
Unknown fields are rejected with 400 validation_error.
MintIn#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
no |
|
length 1..255 |
|
|
no |
|
>= 1; <= 3650 |
Unknown fields are rejected with 400 validation_error.
MintOut#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
ObservationIn#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
no |
|
– |
|
|
no |
|
<= 256 keys |
|
|
no |
|
– |
|
|
no |
|
<= 256 keys |
|
|
no |
|
– |
|
|
no |
|
<= 256 keys |
|
|
no |
|
– |
|
|
no |
|
– |
Unknown fields are rejected with 400 validation_error.
OrgCaps#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
OrgOut#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
OrgUsage#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
ParameterIn#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
length 1..255 |
|
|
yes |
|
– |
|
|
no |
|
exactly 2 items |
|
|
no |
|
– |
|
|
no |
|
<= 1024 items |
|
|
no |
|
<= 4096 items |
|
|
no |
|
<= 64 keys |
Unknown fields are rejected with 400 validation_error.
ProviderOut#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
ProvidersOut#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
ProvidersSyncIn#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
length 1..8192 |
Unknown fields are rejected with 400 validation_error.
RevokeOut#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
RotateOut#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
SessionOut#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
SuggestionIn#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
no |
|
– |
|
|
no |
|
> 0; <= 300 |
|
|
no |
|
– |
|
|
no |
|
<= 64 items |
|
|
no |
|
length 1..128 |
Unknown fields are rejected with 400 validation_error.
TaggedCaps#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
|
|
yes |
|
– |
TokenList#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
TokenOut#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
yes |
|
– |
Request limits#
The bounds the service holds every request to:
Body size: a
/v1request body past 1048576 bytes (1 MiB) answers 413payload_too_large(request body exceeds 1048576 bytes), fromContent-Lengthwhen declared and from the streamed count otherwise; a body not received within 30 s answers 408request_timeout(request body not received within 30 s). Both come from the pre-authentication gate, on every lane the service runs, outside the usage ledger.One observation: a body past 32768 bytes serialized compactly answers 400
validation_error(observation body exceeds 32768 bytes serialized).Observations per experiment: the organization’s plan’s
max_observations_per_experiment(4000 on the beta plan, 2000 on the free plan); the next one answers 409observation_cap_reached(details.cap,details.count), checked before the suggestion branch so an open suggestion stays open. Archive the experiment and start a new one, dismiss what you cannot report, or, with repository access or at the public release, run the local driver.Structure: the schema tables above carry the caps – 256 parameters, metrics, assignment and value keys, constraint terms, and thresholds; 64 linear constraints, condition keys, and intents; 1024 categorical values; 4096 grid points; names of 1 to 255 characters.
Ids and the list window:
experiment_idandsuggestion_idare 1..2147483647 andlimitandoffset0..2147483647, the column unit; a value past them answers 400validation_error.Locks: the four routes that take an experiment’s lock (
PATCHandDELETEon the experiment, the twoDELETEs on its suggestions) wait at most 3 s for it and then answer 503experiment_busywith Retry-After 1; the ownership read precedes the lock, so a 404 never waits. An ask takes the lock on its worker, behind the queue: a wait that times out there puts the ask back in the admitted set and the caller never sees it. The active-experiment quota check holds the user’s row for the create and waits at most 3 s for it, answering 429quota_unverifiablepast that.
Error shape#
Every non-2xx response is one JSON object – the service’s own refusals, the framework’s (http_error), and a crash (500 internal_error) alike:
{"error": {"type": "<error_type>", "message": "<text>"}}
details is present when the error carries a machine-readable payload; the Error types table below names the keys per type (the dedup 409 answers, the typed 500 answers, the 503 ask_deadline_exceeded, the 409 suggestion_expired and observation_cap_reached, and the admission and idempotency refusals). The exhaustion answer:
{"error": {"type": "space_exhausted", "message": "<text>", "details": {"dedup": {"exhaust_proof": true, "proof_method": "enumeration"}, "terminal": true}}}
details.dedup is the engine’s dedup record (counters, ladder hops, proof fields, intent refusals); details.terminal is true only on space_exhausted. Request-shape failures (pydantic validation of the body, path, or query) answer 400 with {"error": {"type": "validation_error", "message": "<field>: <reason>"}}. The OpenAPI document declares that 400, as the ApiError schema below, on every route with parameters or a body, and no 422 anywhere: the service’s request-validation handler answers 400 in the shape above, never the framework’s 422. Path ids and the list window are bounded at the column unit (experiment_id and suggestion_id 1..2147483647; limit and offset 0..2147483647), and a body carrying a NUL byte, a lone surrogate, or a non-finite number is refused before field parsing with one message per class.
Engine errors and crashes. The service maps every engine error it knows: AskValidationError to 400 validation_error; the caps-mode AskDeadlineExceeded (POST /v1/experiments/{experiment_id}/suggestions with a config carrying timebox_mode: "caps" and an expired max_wait_s) to 503 ask_deadline_exceeded with no Retry-After and details carrying point, max_wait_s, elapsed_s, and rung; AskFailed to 500 all_rungs_failed or integrity_violation; any other engine ValueError to 500 internal_error with details.exception_type. Anything else that escapes a route answers 500 internal_error with the constant message internal error and no details, inside the envelope; the framework’s own refusals (an unknown path, a method the route lacks, a body it cannot parse) answer http_error at the framework’s status.
ApiError#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
ApiErrorBody#
Field |
Type |
Required |
Default |
Constraints |
|---|---|---|---|---|
|
|
yes |
|
– |
|
|
yes |
|
– |
|
|
no |
|
– |
Error types#
Every error_type the service raises deliberately, found by scanning its app package for the raise sites (an engine error the service does not map is the 500 internal_error of Engine errors and crashes under Error shape); the Python client maps these onto exception classes (Errors). Retry-After says whether the answer carries that header.
HTTP status |
error_type |
Retry-After |
Raised by |
Meaning |
|---|---|---|---|---|
400 |
|
no |
|
|
400 |
|
no |
|
|
400 |
|
no |
|
the admin API only ( |
400 |
|
no |
|
|
400 |
|
no |
|
the request shape (a pydantic failure naming the field), an id or list-window value past the column unit, a body defect (a NUL byte, a lone surrogate, a non-finite number), an observation past 32768 bytes serialized, or an engine space, metric, config, or intent rule; the message carries the reason |
401 |
|
no |
|
no credential at all (no |
403 |
|
no |
|
the bootstrap route: the account is disabled ( |
403 |
|
no |
|
the bootstrap route ( |
403 |
|
no |
|
the admin API’s door only ( |
403 |
|
no |
|
the pre-authentication gate: the request carries an |
403 |
|
no |
|
|
404 |
|
no |
|
an unknown experiment or suggestion id, another user’s id, or (on mutating routes) an archived experiment |
408 |
|
no |
|
the request body was not received within 30 s; answered by the pre-authentication gate |
409 |
|
no |
|
the suggestion already has an observation |
409 |
|
no |
|
the admin API only ( |
409 |
|
no |
|
the |
409 |
|
no |
|
the experiment holds its organization’s plan’s |
409 |
|
no |
|
the admin API only ( |
409 |
|
no |
|
|
409 |
|
no |
|
every input-feasible point has been suggested or observed; |
409 |
|
no |
|
the suggestion was closed by the TTL expiry before the observation arrived; |
409 |
|
no |
|
|
409 |
|
no |
|
no unique candidate was found and exhaustion is not proven; |
413 |
|
no |
|
the request body is past 1048576 bytes, by |
429 |
|
yes |
|
open suggestions already equal |
429 |
|
yes |
|
beta lane only: the admission layer’s per-credential token bucket, charged to a shape-valid credential before the lane bucket (a first-seen digest is charged to one shared newcomer bucket first), is empty, or the credential is past its request concurrency cap; a bucket refusal is held for the refusal tarpit before it answers; Retry-After 1 |
429 |
|
no |
|
active experiments reached the organization’s |
429 |
|
yes |
|
admitted suggestions in the rolling 24-hour window reached the organization’s |
429 |
|
yes |
|
the quota store could not be read, or the create’s wait for the user’s row (the serialized active-experiment count) passed 3 s, so the request is refused rather than admitted unchecked; Retry-After 30 |
429 |
|
yes |
|
beta lane only: the per-instance pre-authentication token bucket (150 requests per second, burst 300) on every |
429 |
|
yes |
|
the per-user suggest rate (about ten a second sustained, a burst of twenty, every user; consumed before the idempotency lookup and the validation refusals, so refused asks count): slow the loop down or batch the asks; Retry-After 1; |
429 |
|
no |
|
|
429 |
|
yes |
|
the queue front: a retry (the same |
429 |
|
yes |
|
the queue front: the user holds its share of this instance’s suggest permits (the handlers parked for an answer) beside the other tenants on it, or its allowance alone; answered at once, Retry-After 1, outcome |
429 |
|
yes |
|
the queue front: the user’s own queue already holds |
500 |
|
no |
|
the engine’s |
500 |
|
no |
|
the engine’s |
500 |
|
no |
|
an engine |
503 |
|
no |
|
the ask’s deadline: the engine’s caps-mode refusal ( |
503 |
|
yes |
|
every pooled database connection on this instance is in use past the 5 s checkout wait (the pool’s bounded |
503 |
|
yes |
|
another request holds the experiment’s advisory lock and the 3 s bounded wait expired; Retry-After 1 |
503 |
|
yes |
|
the queue front: every suggest permit of this instance is held (prefix, parked or sending a response; a reserve keeps slots for observations and reads) – the ask never entered the queue; answered at once, Retry-After 1, outcome |
503 |
|
yes |
|
|
the framework’s (404, 405, 400) |
|
no |
|
the framework’s own refusal inside the envelope: 404 for a path that is no route, 405 for a method the route lacks, 400 for a body the framework cannot parse |
Authentication#
Every experiment and account route under /v1 takes an API token or the web app’s session cookie (the web app’s own reads ride the cookie); the seven session routes take the web app’s own credentials – the sign-in bootstrap a fresh Identity Platform ID token; the sign-out, the sign-in-methods sync (PUT /v1/me/providers) and the token management under /v1/me/tokens the session cookie – and a tau_ token there answers 401; /health takes nothing. Tokens start with tau_, are shown once when issued, and are stored and compared as SHA-256 hashes. The service reads the token from, in order:
X-Tauopt-Token: <token>– checked first; the header for deployments behind an identity-aware proxy that ownsAuthorization.Authorization: Bearer <token>– used whenX-Tauopt-Tokenis absent or blank. The Python client sends this header. The scheme name is case-insensitive on every door (RFC 9110 section 11.1); the separator and the token’s shape are not relaxed.
A third credential class exists for the web app alone: the browser’s session cookie (__Host-tauopt_session, minted by POST /v1/auth/session from a fresh Identity Platform ID token). It is never read beside a token (two credentials answer 401 invalid token), and the token self-service routes under /v1/me/tokens and the sign-in-methods sync PUT /v1/me/providers take it only.
A request that presents no credential – no X-Tauopt-Token, no Authorization: Bearer header and no session cookie – answers 401 unauthorized with the message missing Bearer token. A token that is unknown or revoked (revocation deletes the token row, so the two are the same lookup miss), or that was issued for a different lane of the service, answers 401 unauthorized with the message invalid token; the cases are indistinguishable by design. On the public beta lane every /v1 request first passes a per-instance token bucket (150 requests per second, burst 300) before authentication; an empty bucket answers 429 rate_limited with Retry-After 1.
Before the body is read and before any lookup, on every lane the service runs, a pre-authentication gate refuses three shapes: no credential at all (no token header and no session cookie; on the sign-in bootstrap, no ID-token bearer – a cookie there is not counted) answers the 401 missing Bearer token above without reading the body; a credential header field that is repeated (two Authorization fields, or two X-Tauopt-Token fields – one of each is the proxy posture and passes) or a token outside the minted shape (tau_ followed by 43 URL-safe characters) answers the 401 invalid token above with no lookup, indistinguishable from an unknown token by design.