Hosted beta: getting started#
The closed beta is hosted: you sign in with the email address your invitation names, mint a token, and drive the service over HTTPS. Nothing is installed; the package is not distributed until the public release. One token works on both doors to the service, the REST API and the MCP server, and the web app shows what your experiments hold. This page walks the whole path once, from the sign-in page to an archived experiment, with the service’s real answers pasted after each call; the pages it links carry the detail. (MCP is the Model Context Protocol, the way AI assistants such as Claude call external tools; an agent here is such an assistant.)
Host |
What it is |
The path |
|---|---|---|
|
The web app: sign-in, your tokens, your experiments |
|
|
The REST API your scripts call |
|
|
The MCP server an agent connects to |
|
Every host answers plain http:// with a 301 to https://, so use the https:// form exactly as written; a script that follows the redirect would turn a POST into a GET. TLS 1.2 or newer is required (an older stack fails the handshake). GET /health on the API host and on the MCP host needs no token and says whether each is up:
curl -sS https://api.tauopt.com/health
{"status":"ok","build":{"service_sha":"74e02028a4c9fb08d08cb95a9cb11d0234a7282a","engine_version":"0.4.0","engine_sha":"3b99f408d829eca2fadbc9a994e1049b022d58d6","deps_fingerprint":"env_blake2b_de0470b809f54d8f","uv_lock_blake2b":"94c898f8c5a95a3c","web_bundle_sha256":"1ae3d039f336386c776d50b9aa8dae5ea9cac04890dfff39de7dc4a66f31b2a5","admin_bundle_sha256":"2219ae671c6d2025d1081d3024bbd6be1a350247ce39418e6b320f47b795cf2b"}}
build.engine_version names the engine the service runs; these pages describe 0.4.0, and every suggestion repeats the same identity in its provenance, so a reader of an older or newer lane can tell which page clauses apply.
Before you start#
An invitation. Sign-in is by email link, and the link is only sent to an address Talaria Scientific has admitted; an address that is not admitted gets a sign-in failure that tells you to reply to your invite. Request access at tauopt.com if you have no invite.
A browser for the sign-in and the token page, and
curl(or any HTTP client) for the calls below. The example scripts need Python 3.10 or newer; eight of the thirteen need nothing else, and the five that evaluate a model or draw a plot name their libraries on the examples page.Two shell variables, set in Mint a token:
TAUOPT_BASE_URL, which ishttps://api.tauopt.comon the beta, andTAUOPT_TOKEN. The names are this site’s convention; the scripts under the hosted examples read both.
Sign in#
Open app.tauopt.com/sign-in. The page offers a Google button and, under “Or get a sign-in link by email”, an email field.
Type the address your invitation names and choose Send sign-in link. The page then reads “Check your email for the sign-in link. Open it on this device to finish signing in.”
Open the link from the email on the same device. The app completes the sign-in and lands on your experiments list, which is empty until your first experiment lands. If you open the link on another device, the page asks you to “Confirm the email address the link was sent to” before it continues. If no email arrives within a few minutes, check your spam folder, then send the link again from the same page.
The first sign-in is always by email link. Continue with Google works once a Google account is linked on the account page (/account, under “Sign-in methods”), and linking starts with the same email link, so the address is confirmed first.
What the browser holds is a session, not a token: the API sets a cookie that lasts seven days and is extended while you use the app, and the app keeps no credential it can read. Signing out (the account page’s “Session” section) revokes that browser session on the API; your tokens keep working. The account page also shows the identity you signed in as and your organization with its plan and limits; on the beta the plan is beta, and the limits it lists are the ones When the service says no explains.
Mint a token#
Give the token a name (optional) and an expiry in days (optional: a whole number from 1 to 3650; empty means your plan’s default, and the beta plan sets none, so the token never expires), then choose Mint.
Copy the value from the card the page shows. It is shown once and never again; the list keeps the name, the prefix (
tau_and the first four characters), the creation time, the last use and the expiry. Dismiss the card with Done.
A token is tau_ followed by 43 URL-safe characters. The same token authenticates the REST API and the MCP server; it is sent as Authorization: Bearer <token> on every request. It is not a management credential: listing, minting, rotating and revoking tokens happen on this page only, under your browser session, and GET /v1/me/tokens with a token answers 401. Rotate mints a successor with the same name and expiry and refuses the old value; Revoke refuses it. Both take effect on the REST API at once and at the MCP server within its cache window, up to a minute on the image the beta runs (Use the MCP server has the window; the revoke answer’s mcp_revocation_lag_s reports the same figure, 60). The beta plan allows five live tokens per member; past that a mint answers token_quota_exceeded, so revoke one first.
Keep the token in an environment variable or a secrets manager, never in a script or a notebook cell; the export below is a shell assignment, not an argument, and the scripts read the variable from the environment. The curl lines below expand it into a short-lived process’s argument list on your own machine, which that machine’s other users can read while it runs; on a shared machine, prefer the scripts. A token pasted with a trailing newline fails as an invalid header value in Python’s HTTP client, so export it without one (export TAUOPT_TOKEN="$(cat token-file)" strips it):
export TAUOPT_BASE_URL="https://api.tauopt.com"
export TAUOPT_TOKEN="tau_<the-value-the-page-showed>"
Check the pair before anything else; the answer is your user id, and the call counts against no quota:
curl -sS -H "Authorization: Bearer $TAUOPT_TOKEN" "$TAUOPT_BASE_URL/v1/auth/check"
{"user_id":4}
A rejected token answers 401 with {"error":{"type":"unauthorized","message":"invalid token"}}, whether it is mistyped, revoked, expired, or minted for another deployment of the service; mint a new one on the tokens page. The same 401 answers a tau_ token sent to a web-app route (/v1/me/tokens under the browser session; /v1/auth/session, the sign-in bootstrap, with the sign-in’s own ID token), which the web app alone calls. GET /v1/me reads the account the token belongs to, with the plan’s caps, the live usage against them and the sign-in methods linked to it (in the answer below the address and the organization’s name are replaced with an example address; everything else is the run’s):
curl -sS -H "Authorization: Bearer $TAUOPT_TOKEN" "$TAUOPT_BASE_URL/v1/me"
{"email":"you@example.org","status":"active","created_at":"2026-10-20T05:38:35.635249Z","credential":"token","org":{"id":2,"slug":"u4","name":"you@example.org","kind":"personal","plan":"beta","caps":{"org":{"suggests_per_day":1000,"active_experiments":25,"default_token_expiry_days":null,"max_observations_per_experiment":4000,"timebox_off_allowed":false},"member":{"max_tokens":5,"inflight_per_user":8}},"usage":{"suggests_24h":0,"active_experiments":6,"live_tokens":3}},"providers":[{"id":"email","linked_at":"2026-10-27T18:48:32.137321Z","last_sign_in_at":"2026-10-27T18:48:32.137321Z"},{"id":"google.com","linked_at":"2026-10-28T18:30:48.962559Z","last_sign_in_at":"2026-10-28T18:30:48.962559Z"}]}
Run your first loop with curl#
The loop is five routes of the REST API: create an experiment, then ask for a suggestion and report an observation as many times as your budget allows, read the best point, and archive the experiment. The lines below, and every pasted answer after them on this page, were run in this order on the validation date against the hosted service at its 74e0202 build, the one prod served that day; the ids and the UTC timestamps are that run’s (an evening run in Pacific time, so its UTC timestamps read the following day; ids come back as strings, and the service accepts them back as numbers or as numeric strings), and each later line reads its id from a shell variable you set from the answer before it. The same three actions have one name per surface:
Action |
Python client |
REST route |
MCP tool |
|---|---|---|---|
ask for a point |
|
|
|
report a result |
|
|
|
read the best point |
|
|
|
Create an experiment: one parameter x in [0, 1], one metric y to minimize. The answer is the experiment object; its id is the handle for every later call.
curl -sS -H "Authorization: Bearer $TAUOPT_TOKEN" -H "Content-Type: application/json" -d '{"name":"first-loop","parameters":[{"name":"x","type":"double","bounds":[0.0,1.0]}],"metric":{"name":"y","objective":"minimize"}}' "$TAUOPT_BASE_URL/v1/experiments"
{"id":"74","name":"first-loop","parameters":[{"name":"x","type":"double","bounds":[0.0,1.0],"log_scale":false}],"metric":{"name":"y","objective":"minimize"},"metrics":null,"linear_constraints":null,"num_solutions":1,"observation_budget":null,"parallel_bandwidth":1,"suggestion_ttl_s":null,"metadata":null,"state":"active","created":"2026-11-01T00:08:37.663977+00:00","updated":"2026-11-01T00:08:37.663982+00:00","revision":0,"progress":{"observation_count":0,"best_observation":null,"expected_suggest_s":null,"open_suggestions":0,"frontier_count":null}}
Keep the experiment’s id in a variable (this run’s is 74):
EXP=74
Ask for a suggestion (an empty JSON body). The answer is an open suggestion: its assignments are the point to evaluate, its id is what you report against, and its provenance names the engine and the strategy that produced it.
curl -sS -H "Authorization: Bearer $TAUOPT_TOKEN" -H "Content-Type: application/json" -d '{}' "$TAUOPT_BASE_URL/v1/experiments/$EXP/suggestions"
{"id":"7541","experiment_id":"74","assignments":{"x":0.043668270111083984},"state":"open","created":"2026-11-01T00:08:37.916224+00:00","expired_at":null,"provenance":{"engine_version":"0.4.0","engine_sha":"3b99f408d829eca2fadbc9a994e1049b022d58d6","deps_fingerprint":"env_blake2b_de0470b809f54d8f","strategy":"sobol","config_hash":"blake2b_3f9b420b29870c18","policy":"default@v1","seed":3735339012350382171,"fallback_from":null,"timings":null,"center":null,"dedup":{"collision_count":0,"boundary_collisions":0,"boundary_invalid":0,"queue_fills":0,"reoptimize_attempts":0,"advances":0,"ladder_hops":["sobol"],"per_draft_origin":["sobol"],"exhaust_proof":false,"universe_size":null,"proof_method":null,"matched":null,"intent_refusals":[],"uniqueness_status":null,"requested_q":1,"unique_count":1,"authorized_replica_count":0,"unfilled_count":0},"selection":null,"fits":null,"work":null,"acq_events":null,"aggregation":null,"conditioning_events":null,"acq_init_filters":null,"fallback_attempts":null,"base_noise":null,"queue_wait_s":0.06,"lane":"worker","attempt":1},"intent":null,"tag":null}
For this walk only id, assignments and provenance.strategy matter; the rest of provenance is the audit receipt (Provenance and determinism), and its lane key names the path the ask took inside the service (worker, the queue’s worker pool; the only value since deploy 2), not the deployment. provenance.strategy is sobol here, and it stays sobol for the first few asks: the policy’s bootstrap, its opening space-filling asks before a Gaussian process has enough results to fit, lasts until the experiment holds max(4, 2 * dim) scored (non-failed) observations (four, on this one-dimensional space); then the Gaussian-process strategy gp_ei answers (Policies). Keep the suggestion’s id (7541 here), evaluate the point (this walk’s objective is (x - 0.3)^2, so x = 0.0437 scores 0.0657), and report the value against it:
SUG=7541
curl -sS -H "Authorization: Bearer $TAUOPT_TOKEN" -H "Content-Type: application/json" -d "{\"suggestion_id\":$SUG,\"value\":0.065706}" "$TAUOPT_BASE_URL/v1/experiments/$EXP/observations"
{"id":"4154","experiment_id":"74","suggestion_id":"7541","assignments":{"x":0.043668270111083984},"value":0.065706,"values":null,"value_stddev":null,"values_stddev":null,"failed":false,"metadata":null,"created":"2026-11-01T00:08:38.138561+00:00"}
Reporting closes the suggestion, which frees the experiment’s one parallel_bandwidth slot for the next ask. The run repeated the two calls once more (the second suggestion was x = 0.8754, reported as 0.331068); then it read the best point so far, the observed point with the lowest value:
curl -sS -H "Authorization: Bearer $TAUOPT_TOKEN" "$TAUOPT_BASE_URL/v1/experiments/$EXP/best_assignments"
{"count":1,"data":[{"assignments":{"x":0.043668270111083984},"value":0.065706}]}
best is always an observed point, never a prediction. The experiment object keeps the running counts (GET /v1/experiments/$EXP read "observation_count":2, "open_suggestions":0 and an expected_suggest_s estimate for the next ask after this run’s two rounds), and GET /v1/experiments?limit=50&offset=0 lists your active experiments newest first with count, the number that counts against the active-experiment cap.
Archive the experiment when the study is done. The answer is a 204 with no body, so -i shows the status line (every curl -sS on this page prints the body alone; add -i whenever you want the status and the headers, Retry-After included); the experiment stays readable with state deleted, its observations and best included, and it no longer counts against the cap:
curl -sS -i -X DELETE -H "Authorization: Bearer $TAUOPT_TOKEN" "$TAUOPT_BASE_URL/v1/experiments/$EXP"
HTTP/2 204
Read it back with GET /v1/experiments/$EXP: the answer carries "state":"deleted"; a second DELETE answers 404 not_found.
quickstart_rest.py, from the hosted examples, is the same loop as a script: it waits out a 429 and a 503 experiment_busy by their Retry-After, reads a 409 space_exhausted as completion, and refuses redirects. Each script on that page is a link to the file: download it into your working directory and run it with the two variables exported. It tunes two parameters (lr on a log scale and layers) against a toy score for thirty rounds, prints each round with the strategy that produced it, then the best point, and archives its experiment. The toy score is a negative penalty, maximized, with its optimum at 0, so every printed value is at or below 0; on the validation run the best of them sat within a thousandth of it (a run that lands on the optimum prints -0.0, a rounded best). The run on the validation date:
python quickstart_rest.py
experiment 93 created
[00] sobol value=-9.3870
[01] sobol value=-0.1255
[02] sobol value=-6.7911
[03] sobol value=-6.0470
[04] gp_ei value=-20.0000
[05] gp_ei value=-3.9288
...
[29] gp_ei value=-0.0010
{'lr': 0.0009372135836382558, 'layers': 4} -0.0008
experiment 93 archived
Twenty-three lines are elided. The checkpoint is the last two: layers is 4 and the best value is within a thousandth of 0; the lr digits and the exact per-round values depend on the run, since this loop pins no seed. The four sobol rounds are the policy’s bootstrap on a two-dimensional space; from the fifth round the Gaussian process answers. Twelve more scripts cover the other flows, from parallel workers to multiple metrics and constraints, and Run against the hosted service is the reference for driving the service from a script: the two timeouts, the suggestion TTL, recovery after a lost response, and the Python client for readers with repository access.
See it in the web app#
app.tauopt.com/experiments lists your active experiments with their name, id, state, progress against the budget, best value, open suggestions and timestamps; an archived experiment is readable by its address (/experiments/<id>) and no longer listed. Each experiment page opens on a Summary of the best point so far (the Pareto frontier for a multi-metric experiment), the counts of observations, failures, open suggestions and suggestions served, and the last observations; the History, Analysis, Suggestions and Properties tabs show the observations in order, the analysis of the metric over the history, the suggestions by state, and the experiment’s parameters, metrics, constraints and settings. The app is read-only by design: it never creates an experiment, asks or reports, so the loop stays with your scripts and agents, where the evidence is; Archive on the experiment page is its one write.
Connect an agent#
The MCP server at https://mcp.tauopt.com/mcp exposes the same experiment surface as twelve tools over MCP Streamable HTTP, authenticated with the same bearer token on every request (there is no OAuth flow). GET https://mcp.tauopt.com/health answers {"status":"ok"} without a token. The recipes below register it in the clients beta participants have used; Use the MCP server covers the tools, the wait budget on next_suggestion, the 401 table, the envelope a request outside a handshake must carry, and the stdio bridge, and MCP tools lists every tool with its arguments. Every client below handles the protocol’s lifecycle for you – the Python SDK discovers the modern protocol and sends its per-request fields on each call, a legacy client opens a session with initialize – and a script that sends single requests itself must put the protocol envelope in params._meta on each one and name the version, the method and, on a tool call, the tool in headers, as the how-to shows. With the headers sent but without the envelope the server answers 400 with a JSON-RPC error -32602 that names the two keys; without the headers the request takes the legacy session path and answers a plain 400 for the missing session.
Claude Code. With TAUOPT_TOKEN exported in your shell:
claude mcp add --transport http tauopt https://mcp.tauopt.com/mcp --header 'Authorization: Bearer ${TAUOPT_TOKEN}'
The single quotes pass ${TAUOPT_TOKEN} as a literal reference that Claude Code expands from its own environment at connection time, so the token never enters the command line. claude mcp get tauopt confirms the entry and prints the header as the stored ${TAUOPT_TOKEN} reference, never the token; in a session the tools appear as mcp__tauopt__create_experiment and siblings.
Claude (claude.ai, the Claude desktop app, Cowork). Custom connectors use remote MCP. On a Team or Enterprise plan an owner adds the connector for the organization (Organization settings > Connectors > Add > Custom > Web, the same fields as below); on a Pro or Max plan:
Customize > Connectors > “+ Add” > “Add custom connector”.
A name (
tauopt) and the URLhttps://mcp.tauopt.com/mcp, then Continue; the detected authentication settings are shown, Continue again.Under Authentication: “No sign in”.
Under Request headers:
Authorization, valueBearer tau_<your-token>(request headers are the connector’s fixed credentials, sent on every request); then Add.
The steps follow the Claude Help Center’s custom-connectors article as read on the validation date. Claude connects from Anthropic’s cloud, not from your machine, which is why the server is a public host.
Cursor. A project entry in the mcp.json under the project’s .cursor directory (or a global one under ~/.cursor); Cursor resolves ${env:...} references in url and headers, so the token can stay in your environment:
{
"mcpServers": {
"tauopt": {
"url": "https://mcp.tauopt.com/mcp",
"headers": {
"Authorization": "Bearer ${env:TAUOPT_TOKEN}"
}
}
}
}
Any other client that takes a servers map registers an HTTP server with one static header, as the MCP how-to’s generic entry shows; whether a client accepts a static header on a remote server is that client’s documentation to answer.
Python (the MCP SDK). mcp 2.x puts the header on the HTTP client it is handed and takes the transport from the URL. In a virtual environment, pip install mcp installs the SDK (and httpx2 with it); the service itself still needs nothing installed. The script below was run against the service on the validation date: it lists the tools, then drives one experiment through create, ask, report, best and archive. A tool result is JSON text in the result’s first content block, which is why the helper parses it.
import asyncio
import json
import os
import httpx2
from mcp import Client
from mcp.client.streamable_http import streamable_http_client
async def main() -> None:
http = httpx2.AsyncClient(
headers={"Authorization": f"Bearer {os.environ['TAUOPT_TOKEN']}"},
timeout=httpx2.Timeout(30.0, read=330.0),
)
async with http:
transport = streamable_http_client("https://mcp.tauopt.com/mcp", http_client=http)
async with Client(transport) as client:
tools = await client.list_tools()
print(sorted(t.name for t in tools.tools))
def result_of(call): # a tool result is JSON text in its first content block
return json.loads(call.content[0].text)
created = await client.call_tool(
"create_experiment",
{
"name": "mcp-loop",
"parameters": [{"name": "x", "type": "double", "bounds": [0.0, 1.0]}],
"metric": {"name": "y", "objective": "minimize"},
},
)
exp_id = int(result_of(created)["id"])
sug = result_of(await client.call_tool("next_suggestion", {"experiment_id": exp_id}))
x = sug["assignments"]["x"]
await client.call_tool(
"report_observation", {"experiment_id": exp_id, "suggestion_id": int(sug["id"]), "value": (x - 0.3) ** 2}
)
best = result_of(await client.call_tool("best_now", {"experiment_id": exp_id}))
print(best)
await client.call_tool("archive_experiment", {"experiment_id": exp_id})
asyncio.run(main())
['archive_experiment', 'best_now', 'close_suggestions', 'create_experiment', 'dismiss_suggestion', 'get_experiment', 'list_experiments', 'list_observations', 'list_suggestions', 'next_suggestion', 'report_observation', 'update_experiment']
{'count': 1, 'data': [{'assignments': {'x': 0.043668270111083984}, 'value': 0.0657059557478442}]}
The read timeout of 330 s covers the server’s longest allowance for one next_suggestion: max_wait_s plus 30 s, at most 330 s, and 90 s when max_wait_s is unset. Experiment and suggestion ids are integers in tool arguments and strings in results, so the script converts them on the way back in.
When the service says no#
Every refusal is a JSON body of the shape {"error":{"type":...,"message":...,"details":...}} with an HTTP status, and most carry a Retry-After header that names the wait in seconds; Errors reference has the whole table. These are the ones a beta loop meets, with what to do about each. The quoted bodies and counts are the validation run’s.
429 bandwidth_full (Retry-After 1). Every parallel_bandwidth slot of the experiment holds an open suggestion. This is the expected backpressure under parallelism rather than a fault: report or dismiss an open suggestion, or create the experiment with a larger parallel_bandwidth. A second ask on the experiment above, with its suggestion still open:
{"error":{"type":"bandwidth_full","message":"1 open suggestions >= parallel_bandwidth=1; observe or close one first"}}
GET .../suggestions?state=open lists what holds the slots, DELETE .../suggestions/<id> dismisses one, and DELETE .../suggestions dismisses them all (the walk’s answered {"closed":1}).
429 too_many_inflight_asks (Retry-After 1). A retry under the same Idempotency-Key already waits on one of your in-flight asks (the per-user waiter cap is one): wait for that answer instead of resending; details carries inflight, inflight_limit, queued and queued_limit (your joined waiters and their cap, 1) and joined: true, and the refused retry did no work. Asks beyond your in-flight bound are not refused: the bound is 8 unless your organization carries an override (GET /v1/me reports the effective value as org.caps.member.inflight_per_user), the asks past it wait in your queue for a worker, with X-Tauopt-Queued on their answer, up to eight waiting; the ninth waiting ask is too_many_queued below. Every answer served through the queue (a 201, or a typed refusal the ask ends in after the enqueue) and a too_many_queued carry X-Tauopt-Inflight, X-Tauopt-Inflight-Limit, X-Tauopt-Queued, X-Tauopt-Admission and X-Tauopt-Queue-Ms; a refusal from the prefix behind the door – a quota 429, the 409 idempotency_key_mismatch, the prefix-stage 503 – carries none of the five, and the door refusals queue_full and too_many_parked carry only X-Tauopt-Admission and X-Tauopt-Queue-Ms of them, with Retry-After 1; this 429 carries Retry-After alone. Workers that share a token keep their fan-in at eight concurrent asks; past that their asks wait in the queue, up to eight, and the ninth is refused as too_many_queued below (an ask counts against the token’s eight-in-flight cap only through its prefix – the checks before it parks in the queue – so a burst that arrives faster than the service can park it can meet credential_rate_limited first).
429 credential_rate_limited (Retry-After 1). Too many requests from one token: more than eight in flight at once, more than 40 a second (a burst of 80), or more than 15 history-sized reads a second (a burst of 30; a history-sized read is a GET of the experiment, its best point, its summary, a suggestion or observation listing, or the CSV export). It is answered before the request is looked at, so reads and observations meet it too. 120 reads of /v1/auth/check fired at once answered 26 with 200 and 94 with this 429; of seventeen asks fired at once from one token on the validation run, eight were admitted directly, one waited in the queue (X-Tauopt-Admission: queued) and eight answered this 429 before reaching the queue. Slow the caller down, or spread a fleet’s calls over time. The refused answer:
{"error": {"type": "credential_rate_limited", "message": "too many requests in flight from this credential"}}
429 rate_limited (Retry-After 1). The lane’s own bucket (150 requests a second with a burst of 300, every caller together), the same shape as above; wait the second.
429 quota_exceeded (Retry-After) and experiment_quota_exceeded (no Retry-After). The beta plan’s two quotas: 1,000 suggestions per rolling 24 hours, and 25 active experiments. The first names its wait; the second cannot, because waiting does not help: archive finished experiments. quota_unverifiable (Retry-After) means the quota store could not be read, so the ask was refused rather than admitted unchecked. GET /v1/usage/me reads both counters against their caps.
429 suggest_rate_limited (Retry-After 1). Asks faster than about ten a second from one user (a burst of twenty); wait the second.
429 too_many_queued and too_many_parked (each Retry-After 1). Eight of your asks already wait in your queue behind your in-flight bound (the per-user queue cap; X-Tauopt-Admission reads refused_queue), or your parked suggest permits reached your ceiling on the instance (refused_share): a capacity event, never a mark against you; details names the counts and the caps (inflight, inflight_limit, queued, queued_limit; parked, parked_share, max_parked). Wait the second and resend.
503 queue_full, experiment_busy and connection_pool_busy (each Retry-After 1). In that order: every suggest permit of the instance was held; another request holds the experiment – a running ask, usually; the refused request is an update, an archive or a dismissal (an ask waits for the lock on its worker instead and never answers this); every database connection on the instance is in use. The first two did no work on your behalf: resend after the second. connection_pool_busy can follow a committed write on a POST, so list what the server holds before resending an ask or an observation (the Python client retries it on every method but POST). X-Tauopt-Queue-Ms on an ask’s answer is how long the ask waited for a worker before it was leased, or before it was refused.
503 ask_deadline_exceeded (no Retry-After). Your max_wait_s expired before the ask produced a point, or, on the queue lane (provenance.lane worker), the service’s backstop above the engine ended a hung ask (details.point is backstop then); details carry where it stopped. For the engine’s refusal a re-ask repeats the same work, so only a larger max_wait_s, a cheaper strategy, or lower caps change the answer (Run against the hosted service); a backstop refusal is the service’s, not your settings’, and details.stage says where the bound was spent: at prefix (the service’s own 3 s bound on auth, quota and admission) or wait (the ask was not served inside its bound while it queued or ran; details.item_state says which, details.joined whether this was a resend that joined the original) no worker of yours was replaced and a resend is the whole remedy; at a worker’s stage (ask, deadline, start, worker_died, memory, shutdown) the ask was ended or never started behind the refusal and your next ask runs on a live worker inside its own budget (Troubleshooting walks the local driver’s stages).
409 space_exhausted. The experiment’s space has been fully explored, with the proof in details.dedup: completion, not an error. Read best and archive. A 409 unique_candidate_unavailable is not completion; it is a dedup refusal without a proof (Dedup and completion).
409 suggestion_expired and observation_cap_reached. The suggestion was closed by the experiment’s suggestion_ttl_s (24 hours by default) before the observation arrived (details.assignments lets you report the result as a free-form point), or the experiment holds its 4,000-observation cap (details.cap, details.count): archive it and start a new experiment, or dismiss the open suggestions you cannot report.
401 unauthorized. The token is missing, mistyped, revoked, expired, or minted for another deployment; the message is missing Bearer token or invalid token, and the scheme must be Bearer exactly. Mint a new token on the tokens page.
403 on sign-in. Your account is not active (account_pending or account_disabled): reply to your invite.
500 internal_error, all_rungs_failed or integrity_violation. A defect in the service or the engine rather than in your request. Report it by replying to your invitation email with the error body, the build.service_sha from GET /health, the UTC time and the experiment id; the traceback stays in the server log, so those four are what identify it.
Next steps#
The hosted examples: thirteen scripts over plain HTTP, one per flow.
Run parallel workers: share one experiment across workers with
parallel_bandwidth.Experiments and the ask loop: the mental model in one page.
Troubleshooting: every error string, by symptom.