---
title: Refusal codes
description: Every code Felspar returns when it refuses a request or can't read evidence, what it means and how to fix it.
sidebar:
  order: 3
---

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](/guides/retries-and-attempts).

## 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](/concepts/subjects-and-escalations#identity-modes). 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 |
