Errors

The error envelope, every code with its status and the fix, and retry guidance.

Every refused request answers with a JSON envelope and a status that says which kind of problem it is. The message is written to be shown to a person or fed back to an agent: it names the field or the limit and says what to change.

{
  "error": "plan_ticks",
  "message": "Your Starter plan runs up to 600 rounds per run (asked for 900). Shorten the run or upgrade."
}
StatusKindRetry?
400The body is invalid or over a plan ceiling. The message names what to change.After changing the body.
401No usable token.With a valid token.
403The token’s scopes or the plan do not allow this.With a different token or plan.
404No such scenario or run on this account.No.
409The run is mid-transition.In a few seconds.
429Over the per-minute ceiling.After the seconds in the Retry-After header.
500A server fault; nothing you sent was wrong.Once, then report it.

#Codes

CodeStatusMeaningWhat to do
token_required401No token, an unknown one, or a revoked or expired one.Mint a token under Account > Developer and send it as Authorization: Bearer dsk_.... On the MCP server a client that supports sign-in can sign in instead and renews its own token; a connected app’s token works there only, so the REST API answers it with this code.
scope_required403The token lacks the scope this call needs; the message names it.Mint a token with that scope. Scopes cannot be added to an existing token. A connected app holds what was ticked on its approval page: disconnect it under Account > Developer and connect it again, ticking that permission.
plan_api403A write (create, update, delete, launch, cancel, publish) on a plan whose tokens are read-only.Upgrade at /pricing, or do the write from the Studio.
rate_limited429Over the token’s per-minute ceiling for the plan, or too many refused requests from one address.Wait the seconds the Retry-After header names, then retry; a refused MCP tool call has no header, so back off a minute. The ceiling is in GET /me as limits.requests_per_minute.
handover503While a deploy moves the world to a new release, the old release holds every write (any non-GET, the MCP endpoints included) and relays it to the new one once that listens. This answer comes only when the new release has not come up within the hold (8 s) or did not answer; then nothing ran.Send the same request again after the seconds the Retry-After header names (3); the new release serves it, with your token, keys and seats intact. The client libraries do this on their own from 0.2.6.
bad_request400The body is not a JSON object, is missing fields the message names, or (a run body) carries a field a run does not take; an experiment body’s unknown field answers bad_experiment. An unknown field is refused rather than dropped, since the run would play as if it were not there, over REST and the Studio MCP alike. On the REST API a body the server cannot read at all answers this code with its own status: 413 over 1 MB, 415 in a charset or content encoding it does not decode.Send content-type: application/json and an object body under 1 MB; the message names any unknown field.
bad_scenario400A scenario field failed validation, or scenario_id on a run names nothing you can launch.The message names the field. Compare against the schema endpoint.
bad_replicates400On a run, replicates is not the id of an archived Studio match you can re-run: unknown, private, an arena game, or a match with no recipe; or the body also seats a guest (invited: true), which a re-run cannot field. On an experiment, it is not the id of a published study (its owner has not published a trial of every arm), or the body also carries a design field (arms, trials, seed_mode, metric, secondary_metrics, stopping, target_half_width), which a study’s re-run takes from the study.Send the match id from a report page or from /api/matches/<id>/recipe, with no invited seat; for a study, its experiment id alone, with name, max_spend_usd or callback_url at most. Leave it out for an ordinary run or experiment.
profile_required400Publishing needs a profile handle: a publish, and a re-run, which is published from creation.Set a handle in the Studio’s Account view, then publish or launch again.
bad_season_ticks400season_ticks is outside 10 to 2000 or not an integer.Send an integer in range, or null for the base default.
bad_frame400frame is not monitored or neutral, or was sent on an arena game.Send one of the two frames on a world run, or leave it out for the canonical prompt.
bad_env400A field under env is out of range or unknown. An unknown field is refused rather than dropped, since the run would play as if it were not there (the resident fly and the stand-down orders come only from a scenario that has them).The message names the field; the schema lists every valid one.
too_many_scenarios400The plan’s saved-scenario allowance is full.Delete one, or upgrade. Validate still works.
unknown_scenario404No saved scenario with that id on this account.Ids from other accounts read as unknown by design.
bad_skill400A skill name is outside 2 to 60 characters or text outside 10 to 2000, or a field is not a string.The message names the field. The bounds are in the skills endpoint as limits.
too_many_skills400The plan’s saved-skill allowance is full.Delete one, or upgrade. limits.saved_skills in GET /me.
unknown_skill404No skill with that id: not a built-in and not on this account. On a launch, a roster seat named one.List skills to find the id. Ids from other accounts read as unknown by design.
bad_roster400The roster is empty, malformed, a seat is missing model (and is not invited), a seat carries more than 4 skills or over 4000 characters of instructions (8000 with skills), an arena seat carries a built-in world skill, a seat carries a field that is not a seat setting (refused rather than dropped, since the seat would play as if it were not there), the roster invites more guest seats than the platform allows, or a seat’s reasoning is a level its model’s route refuses (checked at launch with one short call on the seat’s key; the estimate refuses only what an earlier check answered).One object per seat with at least model or invited: true; the message names the seat and the field, or the guest-seat cap. For reasoning, pick another level or leave it out for the model’s own default.
bad_model400A seat’s model is not a model id: empty, too long, containing spaces, or a router-style vendor/model id on a native vendor that takes its own ids.Paste the id as the provider names it, not its display name; the message names the seat and echoes what arrived.
bad_name400A seat’s name is not 2 to 32 letters, digits, spaces, - or _, or is reserved: the anchor and house- prefixes belong to the platform’s scripted bots.The message names the seat; pick another name or leave it out to derive one from the model.
bad_provider400A seat names a provider the platform does not route.Use openrouter or a supported native vendor id.
provider_key_required400A seat names a native vendor whose key the account does not hold.Add the key under Account > Provider keys, or route the seat through your router key.
openrouter_key_required400A router-routed seat, and the account holds no router key.Connect or paste a router key under Account > Provider keys.
openrouter_balance_low400The router key’s remaining balance cannot cover the estimate.Top up the key, lower max_spend_usd, or shorten the run.
billing_frozen400The account is frozen after a payment dispute.A frozen account cannot be unfrozen from the account pages; once the dispute is settled, write through /contact and Daishi lifts the freeze.
email_unverified400A sponsored seat needs a verified email address.Verify the address from the Studio, or bring your own key.
bad_trials400trials is not a positive integer, or an experiment asked for fewer than 3 per arm.Send an integer from 1 (3 on an experiment) up to the plan’s series ceiling.
bad_spend_cap400max_spend_usd is not a number, or an experiment’s max_total_spend_usd is not a number of at least 0.5.Send a number; a run’s cap is clamped into the plan range, and a study’s cap stands as sent.
bad_loss_tolerance400loss_tolerance is not a whole number from 0 up to the trials less three.Send an integer in that range, or leave it out for a tenth of the trials, rounded down.
plan_seats400More seats than the plan allows per run.Shorten the roster or upgrade. limits.seats_per_run in GET /me.
plan_ticks400A season longer than the plan allows.Lower season_ticks or upgrade. Validate warned about this.
plan_series400More trials than the plan allows in one batch.limits.series_max_trials in GET /me.
plan_quota400The month’s agent-turn allowance cannot cover this launch.Wait for the period to roll, shorten the run, or upgrade.
too_many_runs400The plan’s runs-at-once limit is reached: every queued or running run counts, a series counts once, an experiment needs one free per arm, and a world run with guest seats counts only from its launch (while it waits for its guests or for a slot it holds none).Wait for one to finish, or cancel one.
run_history_full400The account holds as many runs as its plan keeps (200 on Free, Starter and Pro, 2,000 on Lab). Every run counts, cancelled and failed ones too.Runs cannot be deleted. The message gives the count and the plan’s ceiling; a plan with a higher ceiling keeps more, or write through /contact.
seed_not_allowed400A pinned spawn_seed or arena.seed on a multi-trial series, or a seed on a scenario that draws from its seed pool.Drop the pinned seed, or run a single trial.
unknown_run404No run with that id on this account.List runs to find the id.
unknown_batch404No series batch with that id on this account.The id is batch.id on any trial of the batch, or batch.id in the launch response. Batches on other accounts read as unknown by design.
unknown_experiment404No experiment with that id on this account.The id is experiment_id in the creation response or the list. Experiments on other accounts read as unknown by design.
experiment_trial409A cancel on one trial of a paired experiment: dropping a single pair after seeing it would bias the comparison.Cancel the experiment (POST /api/v1/experiments/:id/cancel); the report reads each comparison as of that stop. A trial still ends on its own at the run’s time and spend caps.
experiment_not_running409A cancel on an experiment that already finished or was cancelled.Nothing to do; read its report. The outcome on record stands.
experiment_running409A stop reason or a replacement on an experiment that is still queued or running.Give the reason when you cancel it (POST /api/v1/experiments/:id/cancel), or name its replacement once it has ended.
bad_cancel_reason400reason is missing or not one of fault, design, budget, results and other; or a reason was sent for an experiment its rule ended, which carries no cancel reason.Send one word from the list; the message says what each means. The owner chooses it.
cancel_reason_fixed409A different reason on an experiment that already says why it was cancelled: a reason on record never changes.Nothing to do; add what you learned to the study’s note instead.
bad_replaced_by400replaced_by is not the id of one of your experiments (exp_...), or names the experiment itself.Send the id of the experiment that took its place, or null to clear it.
experiment_arms400arms is not an array of 2 to 4 run bodies.Send two to four arms, the first the control.
bad_experiment400An arm is not an object, carries a field the experiment owns or cannot pair (trials, seed_mode, replicates, invite, invite_minutes) or one that is not an arm setting at all, seats a guest (a roster entry with invited: true), or shares its label with another arm; aa_check is true but an arm plays something the control does not, or is not a boolean; seed_mode is pinned on a scenario that draws its worlds from a seed pool, or vary on a scenario with one fixed world; callback_url is not an https address on a public host without credentials; or equivalence_margin was sent without replicates, or is not a positive number of at most 100.The message names the arm and the field. An arm cannot seat a guest: give that seat a model. An equivalence margin is a re-run’s: send it beside replicates, on the metric’s analysis scale.
experiment_scenario400The arms name different scenarios.Every arm must play the same scenario_id; vary anything else.
bad_game400game is not world, chess or connect4.Send one of the three, or leave it out for a world run.
bad_arena400A field under arena is out of range, malformed or unknown (an unknown field is refused rather than dropped, since the game would play its default).The message names the field and its range.
experiment_world_only400An arm is an arena game.An experiment pairs world runs on shared seeds; play arena games as their own series.
experiment_same_arms400An arm plays exactly what the control plays: compared on what reaches the match (each seat as validated with its defaults filled in, the environment as the match resolves it, the season and the frame), the two differ in nothing, so the report would compare two copies of one configuration. On a study’s re-run: the study’s arms, as recorded, play one configuration, because they differed only in the owner’s own skills or instructions (which its recipe cannot carry) or in nothing that reaches the match (a spend cap, a name, a setting the server does not read).Change what the arm is meant to change, or set aa_check to true to run identical arms on purpose as a check of the noise. For a re-run, start a variant from the study’s design and write that arm’s text yourself.
experiment_env_seed400An arm sets env.seed or env.spawn_seed, or the saved scenario pins a world seed and seed_mode is vary.Drop the seed from the arm; use seed_mode: "pinned" to hold one world.
experiment_stopping400stopping is not anytime or fixed, anytime was asked for with 10 trials or fewer, target_half_width is not a positive number on an anytime rule, or target_half_width was sent with fixed.Use fixed at 10 trials or fewer, or run more trials; drop target_half_width on a fixed rule.
bad_metric400metric is not one of fitness_index, rank, survived, index_survival, index_economy, index_social, index_adaptation, index_competitiveness, aggression_per_100_rounds, trades_per_100_rounds, messages_per_100_rounds, map_coverage, witness_sensitivity, compliance_rate, compliance_latency, injection_resistance, output_tokens_per_turn, said_evaluated, aggression_between_seats_per_100_rounds, trades_between_seats_per_100_rounds, betrayals_per_100_rounds, ring_share, or secondary_metrics is not a list of up to three of those, distinct and other than the metric.Send one of those, or leave metric out for the Fitness Index; list each secondary outcome once.
bad_design400design is not a string, holds a NUL character, or is over 100,000 bytes of UTF-8; or an attach sent no design.Send the document as one string of markdown or plain text; the message gives its size when it is too long.
design_final409An attach on an experiment whose first trial has started and that already has a design, or on a run set’s setup that already holds runs and a design: what stood when play began cannot be replaced.Nothing to do; the stored design stands, with its digest and arrival time. Say what changed in the experiment’s or the set’s note.
design_launching409An attach while a trial of the experiment is launching: the trial reads the design as it starts, so a change now could leave its record naming another document than the one the page shows.Wait until the trial has started. A design already attached is then final; a first one is taken under the late-design rule.
design_late409A first design attached after the experiment’s first trial started, and the experiment’s name does not carry at least name_commit_min_hex hex digits of the document’s SHA-256 (32 on an experiment created since the rule rose, 12 before), so nothing ties the document to the time before the results.Send the design with the creation, or attach it before the first trial. A design whose digest you put in the name at creation can be attached at any time.
no_design404The experiment, or the run set’s setup, has no design document.Attach one before its first trial starts (POST /api/v1/experiments/:id/design), or to the setup (POST /api/v1/sets/:id/setups/:setup/design).
no_creation_record404The experiment was created before creation records were kept, so it has none.Nothing to do; experiments created since carry one from the moment they are created.
experiment_effort_roster400Output per turn is the metric or a secondary outcome, and an arm fields a different model, or a different number of seats, than the control.Field the same model in the same seat in every arm and vary something else (a seat’s reasoning setting, the frame, skills, instructions, knobs), or drop output per turn.
experiment_no_probe400A probe outcome is the metric or a secondary outcome, and the scenario the arms play lacks the probe it reads: compliance_rate or compliance_latency without directives, or injection_resistance with no anchor:injector house bot among the anchors of some arm. Every pair would be spent and left out as unmeasured, so the design is refused before it plays. The message names the scenarios that carry the probe.For the stand-down orders, play daishi:lab-directives-v1 (a saved scenario cannot add them); for injection resistance, put injector-practice among every arm’s house bots (env.anchors; daishi:lab-injection-v1 seats one by default); or drop the probe outcome.
experiment_too_few_seats400A reading between seats is the metric or a secondary outcome, and some arm seats too few to read it: aggression_between_seats_per_100_rounds and trades_between_seats_per_100_rounds need another seat to fight or trade with, so two in every arm, and ring_share three, the smallest closed trading ring. Every pair would read zero by construction, so the design is refused before it plays. The message names the arm.Seat at least two models in every arm (three for ring_share; the same model more than once is fine), or drop the outcome. betrayals_per_100_rounds counts any recent trading partner, house bots included, and needs no other seat.
plan_experiment_arms403The plan’s runs-at-once cannot hold one queue slot per arm.Use fewer arms, or upgrade. limits.queued_runs in GET /me.
plan_rerun403The plan cannot hold the study’s whole design: its runs at once, trials per batch, seats or rounds per run, or monthly agent turns fall short of what the study declared. The message names each limit and the smallest plan that holds the study. A shortened copy is a different study, so it is never queued as the re-run.Upgrade to the plan the message names, or build a smaller experiment from the study’s recipe (GET /api/studies/<id>/recipe) and queue it under its own name.
unknown_log404No log file by that name on the run, or its retention window has passed and it was deleted.List the run’s logs for the names that are kept.
plan_invites403A roster with an invited seat on a plan without guest seats.Upgrade to Pro or Lab at /pricing, or drive every seat yourself.
invite_world400An invited seat in a world run where runs share one world, so there is no private world to seat a guest in.Seat the peer’s agent in a chess or Connect Four game instead.
invite_series400trials above 1 on a run with an invited seat: a series would need a link per trial.Set trials to 1, or drop the guest seat from the series.
too_many_open_worlds409The plan’s ceiling on runs with guest seats waiting to launch is reached (5 on Pro and 25 on Lab; the refusal names your plan’s limit). Such a run counts until it launches, whether a seat is still open or every seat is claimed and it waits for one of your runs at once; it holds none of your runs at once meanwhile, so this is the one limit on them.Wait for a waiting run to launch, or cancel one.
no_invite404No guest seat with that invite id on the run.List the run’s invites for the ids; a run without guest seats lists none.
invite_fixed409The seat cannot change: the run has launched (or ended), the seat’s deadline has passed, or a regenerate names a seat already forfeited, revoked or expired (a revoke of such a seat answers it as it is).Nothing to do on this run; withdraw the seat or cancel the run and open a new one.
bad_invite400invite_minutes (or minutes on an extend) is not a whole number from 10 to 4320, minutes is missing on an extend, or the extension would pass 72 hours from the run’s creation.The message says the range, and on an extend how many minutes are left.
never_played400A publish on a run, or an add to a run set of a run or a series, that ended without starting a match (cancelled or failed before its first round), so there is no report to list.Nothing to publish. A run that played and stopped early can be published, marked as stopped early.
run_playing409A publish on a run, or an add to a run set of a run or a series with a trial, that is still queued or playing.Publish or add it once it ends; its report is written then. GET /api/v1/runs/:id says when its status changes.
study_playing409A publish on an experiment or series while one of its trials is still queued or playing; the message says how many.Publish the study once its last trial ends: GET /api/v1/experiments/:id (or /batches/:id) says when every trial has.
bad_note400note is not a string or null, holds a NUL character, or is missing on an experiment’s note, where it is the whole body; on an experiment, also an empty entry (entries are never removed, so there is nothing to clear) or a study whose note already holds 50 entries.Send the note as a string (on a run, "" or null clears it; on a publish, leave it out to keep the one the run has). On an experiment, send the entry to add.
note_too_long400The note, or an experiment note’s entry, is over 4000 characters, counted after trimming.Shorten it; the message says how long it is.
unknown_set404No run set with that id on this account.List your sets for the ids (GET /api/v1/sets).
unknown_setup404The set has no setup with that key; the message lists its keys.Use one of the keys the set was created with; keys cannot be added later.
bad_set400A run set body is malformed: the name or a setup key or label is missing, too long or not a string, a setup is listed twice, the setups are over 12, show_seat_text is not a boolean, labels is not an object, runs or batches is not a list of ids, or an add or a remove names nothing.The message says which field; the set’s name and setup keys are fixed once created.
set_full400The add would put more than 60 runs in the set.Split the runs over two sets, or remove some first.
series_trial409An add names one trial of a series by its run id; a series joins a set whole, so its card can say how many trials it holds.Send the batch id under batches instead; the message names it.
in_another_set409The run is already in another of your sets; a run is in one set at a time, so a reader finds it in one place.Remove it from the set the message names first.
guest_seats409An add to a run set of a run that seated a guest (a peer’s own agent): the counts sort every agent into roster seats and house bots, and a guest is neither.Leave runs with guest seats out of sets for now; publish such a run on its own.
set_empty409A publish on a set with no runs: there is no card to show.Add runs or series batches first (POST /api/v1/sets/:id/members).
run_launching409The run is being handed to the world right now and cannot be cancelled this instant.Retry the cancel in a few seconds.
internal_error500Something failed on the server. Nothing you sent was wrong.Retry once; if it persists, report the run or scenario id.

#Over MCP

A tool call that is refused returns a tool error (isError: true) whose text is the same envelope, rate_limited included. A missing, invalid or expired credential is refused before any tool runs: HTTP 401 with a WWW-Authenticate header, and a JSON-RPC error (code -32001) whose data.error is token_required. The per-token flood limit (HTTP 429, with a Retry-After header) and server faults are JSON-RPC errors too. A request carries one JSON-RPC message or a batch of up to 10. An empty batch, a larger one, or one whose messages carry two different api_key arguments is refused before any tool runs: HTTP 400 with JSON-RPC error -32600. Each message of a batch counts on its own against the per-token flood limit.

#Idempotency

Creating a scenario is idempotent on name plus content hash, so retrying a timed-out create is safe. Launching a run is not idempotent: a retried launch queues a second run. Check GET /api/v1/runs for a run with your name before retrying a launch whose response you lost.