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

# Send 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 now 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. Notra rate limits submissions per source IP and per organization, as described in [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

<CodeGroup>
  ```bash npm theme={"system"}
  npm install @usenotra/geo
  ```

  ```bash yarn theme={"system"}
  yarn add @usenotra/geo
  ```

  ```bash pnpm theme={"system"}
  pnpm add @usenotra/geo
  ```

  ```bash bun theme={"system"}
  bun add @usenotra/geo
  ```
</CodeGroup>

```ts server.ts theme={"system"}
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`. The tool posts the input 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."). It takes these 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. |

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

## REST endpoint

```bash theme={"system"}
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={"system"}
{
  "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, because 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 missing, 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. Notra always keeps values you pass explicitly. 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, Notra still stores the feedback 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 come newest first and paginate with `page` and `limit` (default 25, max 100), as described in [Pagination](/docs/api/pagination).

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

```json theme={"system"}
{
  "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`, and setting `resolved` stamps `resolvedAt`.

```bash theme={"system"}
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

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


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