Refusal codes
Every code Felspar returns when it refuses a request or can't read evidence, what it means and how to fix it.
When Felspar refuses a request, the response names a stable snake_case code, the path of the field at fault, and a fixed fix text saying what to send instead. Every mistake in a request is listed together in issues[], so an agent can correct everything in one pass. Refusals never repeat your submitted values or source text.
A refused request decided nothing and doesn’t use an attempt.
Request refusals
| Code | Status | Meaning | Fix |
|---|---|---|---|
invalid_input |
400 | The request doesn’t match the format. Each issue has a path and code. | Follow input.jsonSchema from the brief. |
invalid_body |
400 | The body isn’t JSON or content parts, or is over the size limit. | Send JSON (up to 2 MiB) or content parts (up to 4 MiB). |
unknown_question |
400 | This question isn’t in the released job. | Use the question keys from the brief. |
source_class_not_accepted |
400 | This question doesn’t accept this source class. | Send an excerpt from a source class the question’s evidence policy lists. |
value_not_in_excerpt |
400 | The quoted words don’t state this typed value. | Quote the exact words that state it, or send the text without a typed value. |
duplicate_excerpt |
400 | The same words from the same source were sent twice for one question. | Send each excerpt once. |
duplicate_source_locator |
400 | Two excerpts name the same source and locator with different words. | Give each passage its own locator (for example the URL with the section after #). The same words may answer several questions. |
label_needs_mapping |
400 | A label was sent for an answer-choice or yes/no question. | Send the words the source says, not a label. |
value_not_allowed |
400 | A typed value isn’t a finite number or a YYYY-MM-DD date. |
Send a number for a number question, a date for a date question. |
answer_choice_needs_excerpt |
400 | An answer choice was asserted directly where Felspar classifies it. | Send it in a facts request with the source text. |
text_too_long |
400 | An excerpt is over 2,000 characters. | Send the shortest passage that states the fact. |
too_many_excerpts |
400 | More than 3 excerpts for one question. | Send at most 3. |
too_many_questions |
400 | More than 64 questions in one request. | Send at most 64. |
request_too_large |
400 | The facts are over 512 KiB. | Send shorter excerpts. |
job_mismatch |
400 | This release serves a different job. | Use the job and release from the brief. |
attempt_limit_reached |
400 | This case has used its decision attempts. Carries the escalation the attempt limit names, when the release has one. |
Don’t ask again: escalate with that escalation (report its id exactly), or with the next step from your last result. |
subject_id_invalid |
400 | The case ID (or a related ID) uses characters a subject ID can’t. | Send the ID as the job’s subject describes it: letters, digits and . _ : @ - only. |
unknown_relationship |
400 | related names a relationship the job’s subject doesn’t have. |
Use a relationship listed under subject.related in the brief, or leave related out. |
related_subject_required |
400 | A question you answered is about a related record, and no record was sent. | Send that record’s ID under related.<relationship>, and quote that record for the question. |
too_many_related |
400 | More than 16 related record IDs. | Send at most 16. |
one_related_id |
400 | A list was sent for a relationship that is one record. | Send its ID as a single string under related.<relationship>. |
related_id_required |
400 | Several records were sent for a relationship, and an excerpt doesn’t say which one it comes from. | Name the record in the excerpt’s about. |
unknown_related_id |
400 | An excerpt’s about names a record not sent under related. |
Send that record’s ID under related, or correct about. |
invalid_type, invalid_union, invalid_value, invalid_format, unrecognized_keys, too_big, too_small |
400 | A field at this path doesn’t match the facts format. | Follow input.jsonSchema from the brief. |
release_changed |
409 | The release changed since you read the brief. Carries the current digest. | Read the brief again and resend with the current release digest. |
execution_id_reused |
409 | This execution ID was already used for a different request. | Use a new ID, or resend the original request unchanged. |
no_current_release |
409 | Backtest: the job has no current release to compare with. | Release a first version, then backtest later changes. |
rate_limited |
429 | Too many decision requests in the last minute for this caller (limit: "caller") or workspace (limit: "workspace"). Carries retryAfterSeconds and a Retry-After header. |
Wait, then resend the same request with the same execution ID. Send all of an entity’s questions in one request; spread large runs over time. |
unauthenticated |
403 | No valid credential. | Sign in, or send the agent key. |
forbidden |
403 | This caller may not run this job on this release, or the release isn’t available. | Check the job, the release and the caller’s access. An agent needs the job added on the workspace page. |
access_denied |
403 | This caller may not perform this action. | Check your role. |
operation_unavailable |
403 | Anything not deliberately described. | Retry later; if it persists, contact Felspar. |
Output-check refusals
From check_output and POST /v0/jobs/{job}/output-checks.
| Code | Status | Meaning | Fix |
|---|---|---|---|
decision_not_found |
404 | No decision by this caller with this execution ID on this job. | Send the executionId from your own get_job_decision result for this job. |
decision_not_decided |
409 | That decision has no outcome, so nothing is written for it. | Follow its next step instead. |
no_output_checks |
404 | The job’s release declares no output checks. | Use the decision’s next step; nothing is checked before sending. |
output_not_required |
409 | The decided outcome doesn’t ask for this output. Lists the ones it does under required. |
Send one of those outputs, or none when the list is empty. |
output_too_large |
413 | The text is longer than this output allows. Carries maxBytes. |
Shorten it and check again. |
Identity refusals
How a case is identified depends on the job’s identity mode. A request whose case ID doesn’t fit it is refused before anything is keyed or counted, so fixing it is not an attempt. All are 400, and each carries the path to fix.
| Code | Meaning | Fix |
|---|---|---|
entity_id_required |
The job identifies each case by the record’s own ID, and none was sent. | Send it as entityId (entity_id over MCP). |
case_id_derived |
The job derives each case’s ID from its fields or text, and a different ID was sent. | Leave entityId out, or send the case.id from an earlier result unchanged. |
unknown_case_id |
This case ID wasn’t issued to you for this job. | Leave entityId out on a first decision; afterwards send exactly the case.id Felspar returned. |
issued_case_limit |
You opened 200 new cases on this job in the last 24 hours. | Reuse the case.id of the case you are working on instead of starting a new one. |
identity_field_required |
A field the job’s identity lists is missing. | Send every listed field under identity. |
identity_field_unknown |
A field isn’t one of the job’s identity fields. | Use the names the brief’s identity lists. |
identity_field_invalid |
A field value uses characters an ID can’t. | Use letters, digits and . _ : @ - only, as its example shows. |
content_required |
The job identifies each case by its text, and that text wasn’t sent. | Send the text being judged under the question the identity names. |
Source refusals
| Code | Status | Meaning | Fix |
|---|---|---|---|
source_access_required |
403 | Your own connection can’t read a document behind this object. Carries provider and access: not_connected, reconnect, missing_scopes, no_access, not_a_document or unavailable. |
Connect or reconnect on the Sources page, or ask the document’s owner to share it with you. |
source_read_failed |
403 or 422 | Felspar couldn’t read the document’s current version with your connection. | If not_connected or reconnect, connect or reconnect Google Drive on the Sources page; if no_access, ask the owner to share it; otherwise try again later. |
source_text_not_allowed |
403 | An MCP client tried to submit text as a source. | Call add_current_version with a sourceObjectId from list_sources. |
source_not_connected |
404 | This source wasn’t added from a connected document. | Use a sourceObjectId from list_sources. |
MCP-only
| Code | Meaning |
|---|---|
invalid_arguments |
The tool’s arguments don’t match its input schema. Each issue has a path and problem. |
| HTTP 401 | Missing, unknown or revoked credential. The client fails to connect. |
Why a question stays open
These aren’t refusals: the request was accepted, but a question couldn’t be answered from its evidence. They appear on result.explanation.needs[].excerpts[].reasons and drive result.retry.
| Reason | Meaning |
|---|---|
value_not_found |
No number or date in the text |
value_ambiguous |
More than one candidate number or date |
unit_mismatch |
The number’s unit doesn’t match the question |
not_integer |
The question needs a whole number |
probability_below_threshold |
The best answer wasn’t likely enough |
probability_tie |
Two answers were equally likely |
normalization_abstained |
The text doesn’t say |
classification_unsupported |
The pack has no classification material for this question |
classification_failed |
Classification didn’t finish within the job’s deadline |
classification_disabled |
Classification is temporarily switched off |
classification_ceiling_reached |
The workspace or caller reached its daily classification ceiling |
classification_allowance_exhausted |
The job’s own classification allowance couldn’t cover this request |