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

# Agent Feedback

> Add a submit_feedback tool to your MCP server so AI agents using your product can report bugs, request features and leave praise in your Notra inbox.

AI agents such as Claude Code, Cursor and ChatGPT increasingly use your product through your MCP server. Agent Feedback gives those agents a `submit_feedback` tool inside your server, and gives you an inbox in the Notra dashboard to triage what comes in.

## How it works

1. Copy your **feedback URL** from the **Feedback** page in your dashboard (in the GEO sidebar under Visibility).
2. Register the feedback tool in your MCP server with `@usenotra/geo/feedback`, or POST to the URL from your own tool implementation.
3. Feedback shows up in the Feedback inbox, where you can filter by status and kind and mark items as triaged, resolved or archived.

## Your feedback URL

Every organization has its own feedback URL:

```
https://api.usenotra.com/v1/feedback/{organizationSlug}
```

It needs no token or API key, so it can live directly in your server code. Submissions are rate limited per source IP and per organization (see [Rate limits](#rate-limits)).

Reading and triaging feedback through the API requires an API key with `feedback.read` or `feedback.write`. `POST /v1/feedback` (without a slug) also accepts an API key with `feedback.write` for server-side integrations that already hold one.

## Add the tool to your MCP server

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
npm install @usenotra/geo
```

```ts server.ts theme={"theme":{"light":"github-light","dark":"github-dark"}}
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerFeedbackTool } from "@usenotra/geo/feedback";

const server = new McpServer({ name: "acme", version: "1.0.0" });

registerFeedbackTool(server, {
  url: "https://api.usenotra.com/v1/feedback/acme",
  productName: "Acme",
});
```

`registerFeedbackTool` adds a `submit_feedback` tool that accepts `message`, `title`, `kind`, `sentiment` and `contextUrl`, posts it to Notra and returns a short confirmation the agent can relay to the user ("Thanks, the feedback was sent to the team." or "This feedback was already recorded."). Options:

| Option                    | Notes                                                                                                                                                           |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `url`                     | Your feedback URL. Required.                                                                                                                                    |
| `productName`             | Used in the tool description so agents know what the feedback is about.                                                                                         |
| `toolName`, `description` | Override the default name and description.                                                                                                                      |
| `defaults`                | Values merged into every submission. Accepts `agentClient`, `agentModel`, `toolVersion`, `projectId` and `metadata`, for example `{ agentClient: "acme-mcp" }`. |
| `fetch`, `timeoutMs`      | Transport overrides for tests and custom runtimes. The default timeout is 10 seconds.                                                                           |
| `onError`                 | Called with the error when a submission fails. The tool still returns an `isError` result to the agent.                                                         |

The package also exports `createFeedbackToolHandler(options)` and `feedbackToolInputSchema` if you register tools yourself, and `submitFeedback(input, { url })` for use outside MCP. `submitFeedback` posts a single entry and resolves with `{ id, deduplicated }`; failures throw a `FeedbackSubmitError` with a `status` property.

## REST endpoint

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://api.usenotra.com/v1/feedback/acme \
  -H "Content-Type: application/json" \
  -d '{
    "message": "The search tool times out when the query has quotes.",
    "contextUrl": "https://docs.example.com/api/search"
  }'
```

The endpoint returns `202 Accepted` with the stored entry:

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "feedback": {
    "id": "fb_123",
    "projectId": null,
    "source": "api",
    "kind": "bug",
    "sentiment": "negative",
    "status": "new",
    "title": "Search tool times out on quoted queries",
    "message": "The search tool times out when the query has quotes.",
    "agentClient": null,
    "agentModel": null,
    "toolVersion": null,
    "userAgent": "curl/8.7.1",
    "contextUrl": "https://docs.example.com/api/search",
    "externalId": null,
    "idempotencyKey": null,
    "metadata": null,
    "resolvedAt": null,
    "createdAt": "2026-09-02T10:00:00.000Z",
    "updatedAt": "2026-09-02T10:00:00.000Z"
  },
  "deduplicated": false
}
```

Pass an `idempotencyKey` to make retries safe: submitting the same key twice for the same organization returns the original entry with `deduplicated: true`. An unknown organization slug returns `404`.

### Automatic classification

Only `message` is required. When `title`, `kind` or `sentiment` are omitted, Notra fills them in with a small model before storing the feedback, so the inbox stays readable and filterable even if the agent only sends free text. Values you pass explicitly are always kept. Classification runs through the same model gateway as the rest of Notra, so your organization's zero data retention setting applies to it. If classification fails or no eligible model is available, the feedback is still stored, with `kind: "other"` and no title or sentiment.

### Fields

| Field                                      | Required | Notes                                                                                                               |
| ------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------- |
| `message`                                  | yes      | Up to 4000 characters.                                                                                              |
| `title`                                    | no       | Short summary, up to 200 characters. Written automatically when omitted.                                            |
| `kind`                                     | no       | `bug`, `feature`, `praise`, `question` or `other`. Classified automatically when omitted.                           |
| `sentiment`                                | no       | `negative`, `neutral` or `positive`. Classified automatically when omitted.                                         |
| `source`                                   | no       | `mcp`, `api` or `sdk`. Defaults to `api`. Set it if you want to tell channels apart in the inbox.                   |
| `agentClient`, `agentModel`, `toolVersion` | no       | Identify the agent that submitted the feedback. Up to 200 characters each.                                          |
| `userAgent`                                | no       | Up to 2048 characters. Defaults to the request's `User-Agent` header.                                               |
| `contextUrl`                               | no       | The page or resource the feedback is about. Must be a valid URL, up to 2048 characters.                             |
| `externalId`                               | no       | Your own identifier for the user or session, up to 200 characters.                                                  |
| `idempotencyKey`                           | no       | Up to 200 characters. Deduplicates retries per organization.                                                        |
| `projectId`                                | no       | File the feedback under one of your GEO projects. Returns `404` if the project does not belong to the organization. |
| `metadata`                                 | no       | Arbitrary JSON object, up to 8 KB.                                                                                  |

## Read and triage feedback

With an API key that has `feedback.read`, list entries with optional `status`, `kind` and `projectId` filters. Results are newest first and paginate with `page` and `limit` (default 25, max 100). See [Pagination](/api/pagination).

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.usenotra.com/v1/feedback?status=new&kind=bug&limit=50"
```

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "feedback": [ { "id": "fb_123", "status": "new", "kind": "bug", "...": "..." } ],
  "pagination": {
    "limit": 50,
    "currentPage": 1,
    "nextPage": null,
    "previousPage": null,
    "totalPages": 1,
    "totalItems": 1
  }
}
```

`GET /v1/feedback/{feedbackId}` returns one entry as `{ "feedback": ... }`. To move an item through triage, `PATCH /v1/feedback/{feedbackId}` with `feedback.write` and a `status` of `new`, `triaged`, `resolved` or `archived`. Setting `resolved` stamps `resolvedAt`.

```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X PATCH "https://api.usenotra.com/v1/feedback/fb_123" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "resolved" }'
```

## Rate limits

Submissions to your feedback URL are limited to 30 requests per 10 minutes per IP address and 200 requests per hour per organization. Because your MCP server is the one posting, a hosted server that serves many users from a single IP shares the IP budget; local (stdio) servers get a budget per user machine. `POST /v1/feedback` with an API key is limited to 120 requests per minute per key instead. Responses include the standard `RateLimit-*` headers described in [Rate limits](/api/rate-limits).
