> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stefanbrain.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Follow the content contract in AGENTS.md.
> Treat text as the canonical explanation; video and screenshots enhance it.
> Do not publish unverified product behavior or duplicate an existing canonical article.

# Agent feedback

> Tell the StefanBrain team where the Developer API got in your way, and read the reply over REST, MCP, the SDK, or the CLI.

When an endpoint or tool fails, returns the wrong thing, or lacks something you need, your agent can report it in one call. The StefanBrain team reads every report and replies on it. The reply comes back to the agent through the API and its next MCP connection.

* Sending and reading reports is free. It never uses the API wallet or the plan pool.
* On REST, an API key with any scope can send and read reports, and so can any OAuth token.
* Each account can send 60 reports in any rolling hour.

## When to send a report

Report a problem when:

* An endpoint or tool failed, or returned the wrong result.
* An error did not say how to fix the request.
* The docs were missing, wrong, or unclear.
* You needed something the API does not do.
* A call was too slow, timed out, or hit a rate limit sooner than you expected.

Send one report per problem as soon as you hit it, while you still have the request, the error code, and the id. The team answers each report on its own.

## Report fields

Send a JSON object with a `summary`. Every other field is optional, but each one you include helps the team reproduce the problem.

| Field | What to send |
| - | - |
| `summary` | One line: what went wrong, or what you needed. Up to 300 characters. Without it, the first line of `details` becomes the summary. |
| `category` | One of the categories below. The default is `other`. |
| `impact` | One of the impact levels below. |
| `details` | What you tried, what you expected, and what happened. Paste the call and the error text. Up to 8,000 characters. |
| `suggestion` | What would have helped: a parameter, a clearer error, or a doc line. Up to 2,000 characters. |
| `endpoint` | The REST endpoint, such as `POST /api/developers/v1/runs`. Up to 300 characters. |
| `tool` | The MCP tool, such as `ask_stefanbrain`. Up to 120 characters. |
| `error_code` | The error code you received, such as `invalid_output_config`. Up to 120 characters. |
| `request_id` | The `req_`, `run_`, `job_`, or `chat_` id of the call that failed. Up to 120 characters. |
| `agent` | Your agent or client and its version, such as `Claude Code 2.1`. Up to 200 characters. |

| `category` | Use it when |
| - | - |
| `bug` | Something broke or returned the wrong result. |
| `docs` | The docs were missing, wrong, or unclear. |
| `confusing` | It worked, but a name, parameter, or error message misled you. |
| `missing_feature` | You needed something the API does not do. |
| `performance` | A call was too slow, timed out, or was rate-limited sooner than you expected. |
| `other` | Anything else. |

| `impact` | Use it when |
| - | - |
| `blocked` | You could not finish the task. |
| `worked_around` | You finished, but had to work around the problem. |
| `minor` | The problem caused small friction. |

## How intake handles imperfect reports

Intake fixes what it can instead of refusing, so a stuck agent is not stopped by its own report. The report's `adjustments` array lists every change.

* Text over its field's limit is trimmed.
* An unknown `category` is filed as `other`. Common words map to a category: `error` becomes `bug`, `documentation` becomes `docs`, and `slow` becomes `performance`.
* An unknown `impact` is left empty. Common words map here too, such as `blocking` to `blocked` and `workaround` to `worked_around`.
* In `category` and `impact`, case does not matter, and spaces or hyphens count as underscores. `Worked around` is filed as `worked_around`.
* A value that looks like a credential is replaced with `[redacted:<kind>]`.

On REST, intake also accepts a looser body:

* A value that is not a string, such as a number or an object, is stored as its JSON text.
* Fields intake does not recognize are kept with the report. If their JSON is over 4,000 characters, they are dropped instead, so put long text in `details`.

REST intake also reads these alternative field names:

| Field | Also read from |
| - | - |
| `summary` | `title`, `message`, `feedback` |
| `details` | `description` |
| `tool` | `tool_name`, `toolName` |
| `error_code` | `errorCode` |
| `request_id` | `requestId` |

The only refusal is a report with no `summary` and no `details`. It returns `400 invalid_feedback`, and the message shows a valid body.

## Limits and cost

* Reports never use the API wallet or the plan pool, so an account with an empty wallet or a spent plan pool can still send one.
* On REST, each report counts as one request against your [rate limits](/developers/rate-limits). Reading reports does not.
* On MCP, each `send_feedback` or `list_feedback` call counts as one request.
* An account can send 60 reports in any rolling hour. The next one returns `429 feedback_rate_limited` with a `Retry-After` header in seconds. Wait that long, or combine what is left into one report.

## Send a report over REST

`POST /api/developers/v1/feedback` stores the report and returns `201`:

```bash theme={"system"}
curl -X POST https://stefanbrain.com/api/developers/v1/feedback \
  -H "Authorization: Bearer stefan_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "summary": "POST /v1/runs returned 500 when output_config had a json_schema",
    "category": "bug",
    "impact": "blocked",
    "details": "A sync run with a three-property strict schema returned HTTP 500 with no error code. The same message without output_config completed.",
    "endpoint": "POST /api/developers/v1/runs",
    "agent": "acme-pipeline 1.4"
  }'
```

The response is the stored report plus `guidance`, which says where the reply will appear:

```json theme={"system"}
{
  "object": "feedback",
  "id": "fb_7c9e6679f4e14b5c8d3a2b1c0d9e8f7a",
  "status": "open",
  "category": "bug",
  "impact": "blocked",
  "summary": "POST /v1/runs returned 500 when output_config had a json_schema",
  "details": "A sync run with a three-property strict schema returned HTTP 500 with no error code. The same message without output_config completed.",
  "suggestion": null,
  "endpoint": "POST /api/developers/v1/runs",
  "tool": null,
  "error_code": null,
  "request_id": null,
  "agent": "acme-pipeline 1.4",
  "adjustments": [],
  "reply": null,
  "replied_at": null,
  "created_at": "2026-10-08T15:04:05.000Z",
  "updated_at": "2026-10-08T15:04:05.000Z",
  "guidance": "Received. The StefanBrain team reads every report and replies on it: read the reply with GET /api/developers/v1/feedback/fb_7c9e6679f4e14b5c8d3a2b1c0d9e8f7a (MCP: list_feedback). Replies to your recent reports also arrive in the MCP server instructions the next time you connect."
}
```

Keep the `fb_` id to read the reply.

## Send a report with the SDK

Since SDK 0.5.0, `client.feedback` sends reports and reads the team's replies.

```ts theme={"system"}
import StefanBrain from "@stefanbrain/sdk";

const client = new StefanBrain();

const receipt = await client.feedback.send({
  summary: "POST /v1/runs returned 500 when output_config had a json_schema",
  category: "bug",
  impact: "blocked",
  endpoint: "POST /api/developers/v1/runs",
  agent: "acme-pipeline 1.4",
});

// Later: read the reply on this report, or list every resolved report.
const report = await client.feedback.get(receipt.id);
if (report.reply) console.log(report.status, report.reply);

const { feedback } = await client.feedback.list({ status: "resolved" });
```

`client.feedback.send` takes the report fields in camelCase: `errorCode` and `requestId` send `error_code` and `request_id`. It is never retried automatically, because a retry could file the report twice. If a send fails without a response, check `client.feedback.list` before you send the report again.

`client.feedback.list({ status, limit })` and `client.feedback.get(feedbackId)` are reads, so the SDK retries them like other reads.

## Send a report with the CLI

Since CLI 0.5.0, `sb feedback` sends reports and reads the team's replies. By default, it prints JSON on `stdout`, like other `sb` commands.

```bash theme={"system"}
sb feedback send "POST /v1/runs returned 500 when output_config had a json_schema" \
  --category bug \
  --impact blocked \
  --endpoint "POST /api/developers/v1/runs"

sb feedback list --status resolved --limit 10
sb feedback get fb_7c9e6679f4e14b5c8d3a2b1c0d9e8f7a
```

`sb feedback send` also takes `--details`, `--suggestion`, `--tool`, `--error-code`, `--request-id`, and `--agent`. For a longer report, put the JSON body in a file and pass `--input report.json`, or `--input -` to read standard input. The file uses the REST field names, and flags override its fields. The CLI skips any other field and warns on `stderr`.

If `sb feedback send` fails after a server error or a timeout, the report may already be filed. Check `sb feedback list` before you run it again. Add `--pretty` for readable text instead of JSON.

## Send a report over MCP

The StefanBrain MCP server instructions tell your assistant to call `send_feedback` when a tool or endpoint fails, returns the wrong thing, or lacks something it needs.

`send_feedback` takes the same fields as the REST body, and `summary` is required. `category` and `impact` accept any text and are mapped the same way as on REST.

```json theme={"system"}
{
  "summary": "ask_stefanbrain returned upstream_unavailable for every call with file_ids",
  "category": "bug",
  "impact": "blocked",
  "tool": "ask_stefanbrain",
  "details": "Staged two PDFs with stage_file, then passed both upload ids in file_ids. Three calls in five minutes returned the same error. The same message without file_ids answered.",
  "agent": "Claude Code 2.1"
}
```

It returns the same receipt as the REST endpoint. `list_feedback` returns your reports with the team's replies. It takes an optional `status` and a `limit` from 1 to 50, 20 by default:

```json theme={"system"}
{
  "status": "resolved",
  "limit": 10
}
```

## Read the team's reply

Each report starts as `open`. A reply from the team sets one of the other statuses:

| `status` | Meaning |
| - | - |
| `open` | No reply yet. |
| `acknowledged` | The team has seen the report and is working on it. |
| `resolved` | Fixed. The reply says what changed. |
| `wont_fix` | The team will not change it. The reply says why. |

The reply reaches your agent in three ways:

* **REST, SDK, and CLI:** `GET /api/developers/v1/feedback/{feedback_id}` returns one report with `reply` and `replied_at`. `GET /api/developers/v1/feedback` lists your reports, newest first, and accepts `status` and `limit` (1–100, default 20).
* **MCP tool:** `list_feedback` returns the same reports.
* **MCP connection:** the next connection's server instructions end with up to 3 replies to this account's reports from the last 14 days, newest first. Each reply is shortened to 300 characters there; `list_feedback` shows the full text.

A report belongs to the account that sent it. Any key or OAuth token on that account can read it, and other accounts get `404 feedback_not_found`.

## Keep secrets out of reports

Never put API keys, OAuth tokens, or passwords in a report. Intake replaces values that look like credentials, such as `stefan_sk_` keys, bearer tokens, and JWTs, and notes each one in `adjustments`. It cannot recognize every secret, such as a short password.

Each report also carries your account email and plan, and your client's name and version or user agent. It names the API key, by name and last four characters, or the OAuth connector.

## Errors

| Status | Code | What to do |
| - | - | - |
| `400` | `invalid_feedback` | Send a `summary`, or `details` whose first line can serve as one. The message shows a valid body. |
| `400` | `invalid_json` | Send the body as a JSON object. |
| `400` | `invalid_status` | Use `open`, `acknowledged`, `resolved`, or `wont_fix`, or omit `status`. |
| `400` | `invalid_feedback_id` | Pass the `fb_` id from the receipt or the list. |
| `400` | `use_feedback_endpoint` | `send_feedback` and `list_feedback` cannot run through `POST /api/developers/v1/tools/{tool_name}`. Use `/api/developers/v1/feedback`. |
| `413` | `request_too_large` | The body is over 256 KB. Send the key lines of a long log in `details`, up to 8,000 characters. |
| `404` | `feedback_not_found` | This account has no report with that id. List yours with `GET /api/developers/v1/feedback`. |
| `429` | `feedback_rate_limited` | The account sent 60 reports in the last hour. Wait the number of seconds in `Retry-After`, or combine what is left into one report. |

On MCP, `send_feedback` returns failures as a tool error with `isError: true`. A blank `summary` with no `details` gets an invalid-input message that shows a valid body. A rate limit says how many milliseconds to wait.

Authentication, scope, and request-limit failures use the shared codes in [Errors and retries](/developers/errors-and-retries). The generated REST reference has the exact request and response schemas.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.