15 · Errors, refusals and troubleshooting¶
A request can fail in a handful of ways that each call for a different response: a corrected field, a key, a different pair of choices, a smaller file, a wait. This chapter is every error the service returns, every refusal with the reason it exists, the exceptions the Python client raises for them, and the integration failures teams meet most, the commonest first.
Errors¶
Every error is a JSON body with a code and a message, and the HTTP status says how to respond to it:
400 wants a corrected field, 401 a key, 403 a grant your plan holds, 409 a different combination of
choices, 410 a new solve, 413 a smaller file or a second request, 429 a wait, 503 the same request
again shortly. Generated from the service's own error classes:
| Status | Code | When |
|---|---|---|
| 400 | invalid_request |
Schema violation: unknown mode, unknown config key, operand from another mode. |
| 400 | signup_link_invalid |
An expired, used or unknown link. |
| 401 | unauthenticated |
No key, a malformed header, or a key that does not verify. |
| 403 | google_signin_refused |
An ID token that fails a check, or an unverified Google address. Names the check. |
| 403 | not_entitled |
The key is valid and does not carry the grant this request needs. |
| 403 | not_in_tier |
The key's tenant is on a tier that does not include this. |
| 403 | package_not_entitled |
The package feed, asked by a credential without the packages grant. |
| 403 | provider_host_not_allowed |
A caller-named model endpoint that the worker may not call. |
| 403 | test_key_restricted |
A qls_test_ key asked for something only a live key may have. |
| 409 | contract_error |
A billing request this tenant's state does not allow: billing_not_available, subscription_exists or brl_price_not_eligible. HTTP 409, the code is the slug. |
| 409 | idempotency_key_reused |
The same idempotency key, submitted with a different request. |
| 409 | refused |
A well-formed request whose combination of choices cannot answer the question. |
| 409 | signup_address_refused |
A disposable or undeliverable address. |
| 410 | artifact_purged |
The artefact existed and its bytes no longer do. |
| 413 | input_too_large |
The caller sent, or declared, more bytes than their cap allows. |
| 413 | payload_too_large |
The caller asked for more in one response than one response may carry. |
| 422 | generation_unusable |
The model answered, and what it answered cannot be used. |
| 429 | quota_exhausted |
A limit on the key is spent, or a concurrency cap is full. |
| 429 | signup_rate_limited |
Sign-up or sign-in past a per-address, per-IP or per-domain limit. |
| 502 | provider_error |
The customer's model provider refused or failed a call made on their behalf. |
| 503 | billing_unavailable |
A billing route on a host with no Stripe keys. |
| 503 | queue_saturated |
A queue class holds more pending work than its ceiling. |
| 503 | service_unavailable |
A store this request cannot run without is unreachable right now. |
A 429 that clears by itself carries Retry-After. 429 quota_exhausted does not clear by itself: it
names the meter, what was used, the cap, and when it resets (chapter 16).
Refusals¶
A refusal is a 409 refused with a refusal slug: a well-formed request whose combination of choices
cannot answer the question it asks. Each is refused by name, because the alternative in every case is a
run that answers a question nobody asked and looks exactly like one that did. A refusal before the job is
queued costs nothing; one when the job runs is a job that finished with the refusal as its answer.
| Refusal | Refused | Why |
|---|---|---|
enumerate_needs_checkable_profile |
before the job is queued | A count of the contexts that break a policy is only worth reporting when the run producing it is one an auditor can check. Neither profile offers a certificate, so the count would be unfalsifiable. Upgraded silently would be worse: the caller asked for a cheap run and would be billed for a proof. |
enumerate_outside_guardrail |
before the job is queued | There is one obligation to enumerate the contexts of, and only that mode names one. |
batch_repair_needs_certificate |
before the job is queued | A row owes a repair when it expects SAT or STALL and has not waived the seat belt -- SAT and STALL equally, because failing to rule an outcome out compels the same response as proving it reachable. Such a row is gated on the repair's invariants, so a run that cannot produce one cannot answer it. Left to run, the gate reports the absence of a repair nobody looked for: a row whose verdict matched exactly still fails, and the build goes red on a correct policy. |
batch_certificate_required_unsatisfiable_config |
before the job is queued | Such a row is discharged by a refutation a third party can CHECK, and a config setting certificate_detail none leaves nothing to check, as does one setting enable_phase2 false, which leaves no refutation derived. |
sequence_needs_chain |
before the job is queued | The cumulative question over one step is the single-turn question, and answering it silently would let a caller believe they asked something else. |
pins_belong_in_the_table |
before the job is queued | A property's pins belong in its row of the table, where they are reviewed with it. On the command line they would apply to every question at once, which is the over-pinning failure the table exists to make visible. An enumeration proposes pins rather than taking them, and the pin search is what decides which pins an obligation needs. |
suggested_table_not_promoted |
before the job is queued | This table still carries the placeholders an enumeration leaves behind. A candidate row is a proposal: the id names one question for as long as any record refers to it, and the expectation is the claim being checked, and neither is something derivable from the policy. Running the table with them blank would be promoting a proposal into a commitment by submitting it, which is the review step this whole layer exists to make cheap. |
forest_profile_keys_are_sealed |
before the job is queued | The forest profile's keys are what an answer on this mode means, and this mode will not run with them edited -- except oracle_unsat_admissible, which a request may turn off to accept only derived refutations. The endgames matter most: with Phase 3 and Phase 4 both off a stall means nothing at all. Choose the profile or choose the keys; on this mode they are the same choice. |
root_bound_needs_closure |
before the job is queued | max_roots > 1 requires enable_phase3; only the closure endgame enumerates a variety. A request for several roots without it cannot be honoured, and returning one anyway would misreport the result. |
emission_needs_rails_config |
when the job runs | An unphased answer cannot distinguish a check on the way in from a check on the way out; a whole-message property over a streaming flow is a proof about a message that never exists at run time. |
bad_context_phase |
before the job is queued | A phase the rails cannot name would register a wire the policy never mentions, and pin it to a value nothing reads. A property table's context takes the same form and is held to the same rule. |
forest_region_needs_default |
when the job runs | Fixed and free ask different questions -- one is about the neighbours of a record, the other about a whole population -- and a tool that picked one would be choosing the author's question for them. |
forest_feature_needs_domain |
when the job runs | An unbounded feature yields counterexamples outside anything the model will ever see: a negative balance, an age of four hundred. Those findings are true about the model and useless to its owner. |
forest_domains_not_confirmed |
when the job runs | Bounds inferred from a sample are facts about the sample. Whether a bill may be negative or an age is fixed is a claim about the world, and a person confirms it before a verdict rests on it. |
forest_anchor_outside_domain |
when the job runs | A region built round an impossible point asks about impossible neighbours, and a one-hot group with two members set, or none, is not a record. |
forest_region_needs_anchor |
when the job runs | A robustness property is robust around a record, and a fixed feature or a ball is fixed to or centred on one. Without the record the question has no subject. |
forest_unsupported_construct |
when the job runs | Equality and set-membership splits, preprocessing inside the graph, a non-monotone transform, or a Treelite key the pinned 4.x dump does not write: each is refused by name rather than approximated. |
forest_model_mismatch |
when the job runs | A table carrying a model_sha256 that is not this model's is refused, because a table reviewed against one model is not evidence about another. |
obligation_not_on_that_rail |
when the job runs | Refused rather than answered, naming the rails it is consulted on. A check placed at the wrong end of the pipeline is not the control it resembles, and answering the unqualified question instead would report it as one. |
pin_names_the_obligation |
when the job runs | The obligation is pinned to 0 to ask whether the action can fire without it, and a conditioning pin on the same variable is written afterwards into the same wire. The pin wins, so the run answers the opposite question under that row's id -- and the verdict, the certificate and the audit entry all stay internally consistent about the question they think was asked, so nothing downstream can catch it. |
seatbelt_waiver_missing |
before the job is queued | Waiving the seat belt is the one table edit that turns a red row green without touching the policy, so it must leave a sentence behind saying why: a sweep that cannot fail is not evidence. On an UNSAT row there is nothing to waive -- no repair is produced -- so the field would read as a constraint the run is honouring while it honours nothing, and would become load-bearing the day the row's expectation changed. max_sds beside a waiver is refused for the same reason. |
ir_version_unsupported |
when the job runs | Refused before any structural read, naming the window. Above it, the reader is behind the translator that wrote it. |
ir_version_retired |
when the job runs | Refused before any structural read, naming the window. Above it, the reader is behind the translator that wrote it; below it, the record is retired and the message names the translator version to re-emit with. |
session_steps_mismatch |
when the job runs | Refused naming both depths. A record unrolled to two steps cannot answer a question about three, and answering anyway would report a proof about a circuit nobody described. |
session_steps_unrecorded |
when the job runs | A 1.0.0 record states no depth at all, so a question naming one is refused as unrecorded; asked without, it is answered at whatever depth it was built to and the report says so. |
phase_not_recorded |
when the job runs | A record carries no rails, so the obligation cannot be placed at either end of the pipeline. |
colang_version_ambiguous |
when the job runs | Which dialect a policy is in decides what every line of it means. Nothing of it is parsed, and the refusal names the lines that disagree. |
colang2_construct_unsupported |
when the job runs | Refused by name with its line, and no verdict is given. The cone is every flow that registers the action, every flow that builds a gate of its guard, and every unread line that could invoke it -- naming it, or an execute, await, start, send, run, activate or bot line; a construct outside it is listed as a warning and the verdict stands, because it cannot change the answer. |
colang_unread_in_cone |
when the job runs | The same rule as for Colang 2: an unread line is a missing edge, and a missing edge can make a policy look safe that is not. A line in a flow outside the cone is a warning and the verdict stands; a line outside every flow is in every cone. |
graph_schema_invalid |
when the job runs | The export is what is verified, so one the reader cannot hold to its schema is refused before anything is compiled, naming the first violation. |
graph_catalogue_mismatch |
when the job runs | Refused naming the tool. A tool the catalogue does not declare has no check, no confirmation wording and no classification, so nothing a verdict on it said would be about the tool that runs. |
graph_recursion |
when the job runs | Subgraphs are inlined at each use, and one that contains itself has no depth to inline to. |
graph_too_large |
when the job runs | Subgraphs are inlined at each use, so a graph is as large as it is once inlined: a subgraph used twice at each of twenty levels is a million nodes from forty declared. Refused above 4096 inlined nodes, naming the node that passed the bound. |
The client's exceptions¶
The Python client turns each error into an exception carrying what you need to act on it:
| Exception | When | What it carries |
|---|---|---|
LicenceRequired |
no API key, or no licence activated on this machine | names stalwart licence activate and where to get a key |
SelectorUnavailableError |
a plan or a tool check with no runtime and no router_url |
which of the two to set up |
PersonalDataError |
personal data in a request bound for our servers; nothing was sent | .findings: each match's kind and where it is, never its value |
ConfigurationError |
a bad base URL, or a runtime that is not there | |
AuthenticationError |
401 |
|
NotEntitledError |
403 |
.entitlement: the grant the key lacks |
RefusedError |
409 refused |
.slug, .explanation, .stage |
QuotaExceededError |
429 on a monthly meter; never retried |
.meter, .used, .limit, .resets_at |
ConcurrencyLimitError, RateLimitError |
a 429 that clears by itself, raised after the retries |
.current, .limit |
InputTooLargeError |
413 |
.limit_bytes |
JobFailedError |
a job that ended failed |
.cause, .stderr_tail |
DigestMismatch |
a download that does not hash to its digest | |
StreamError |
a stream that cannot be resumed | |
ServiceError |
5xx, after the retries |
.request_id |
A verdict is never an exception: SAT and STALL are answers (chapter 6).
The client retries reads, plans and checks on a dropped connection, a timeout, a 5xx and a 429 that
clears by itself, honouring Retry-After. A solve is submitted under an idempotency key the client derives,
so a retried submission is one job.
The agent package raises its own errors, each saying what to change in the words of agent.yaml
(chapter 30).
Troubleshooting¶
The failures teams meet most often, the commonest first.
- A stream arrives all at once, at the end. A proxy between you and us is buffering the response:
a two-second solve looks the same buffered or not, so it shows only on long ones. Turn buffering off
for the path, or let the client fall back to polling, which it does by itself after two failed streams
(chapter 9).
stalwart doctorchecks whether streaming gets through. SelectorUnavailableErroron a plan or a tool check. Plans and checks run on your own machine: activate the licence once (stalwart licence activate), or name your enterprise router withQLS_ROUTER_URL. Our hosted service answers neither (chapter 38).LicenceRequired. Nothing in the package works without a key and an activated licence, except activating it,stalwart doctorand checking a certificate. SetQLS_API_KEY, then runstalwart licence activateonce on the machine.PersonalDataError. Something bound for our servers, such as a catalogue description, a rule sheet or a prompt, holds an e-mail address, a telephone number, a card number or another identifier, and nothing was sent. The error names the kind and the place; take the value out, or list your own identifiers withown_identifiers=if the match is yours to send.- A verdict of
SATread as a failure, or a failing table read as a pass. A finding is a successful job: gate onverdict.statusand on a table'shard_failures, never on the HTTP status or the exit code (chapter 11). 409 refusedwithcolang_unread_in_cone. The compiler could not read a line the question can reach, and a verdict over it would mean nothing. The refusal names the line (chapter 6).403 not_entitledornot_in_tier. Your key lacks the grant, or your plan the feature. The body names which; chapter 40 says what each plan includes.429 quota_exhausted. A monthly meter is spent. The body names it and its reset date; the console's Usage page shows each meter against its cap.410 artifact_purged. The artefact was deleted at the end of its retention window. Its digest and its record remain; re-run the job to get it again (chapter 16).409 idempotency_key_reused. The same idempotency key was sent with a different request. Use a new key for a different request; the same key with the same request returns the existing job.
Two failures that tell you little, on purpose¶
- The console's sign-in failure is one page with one sentence, whatever the cause: an expired link, an unknown address, a revoked key, a test key. Telling them apart would tell someone guessing which part of their guess to keep (chapter 5).
- The API reference's Test Request panel sends a request only to the host that served the page. A server edited in the panel to point anywhere else is refused by the page's security policy, and the panel says nothing about why (chapter 18).
The limits¶
- An error says what failed and what to change; it does not say what the run would have found.
- A refusal is about the combination you asked for, not about your policy.