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

# Feedback scores

> Score a model's answers from your code, your agent or the dashboard, and find the good and bad ones in Observability.

Every call to the inference API answers with a request id. Send a score for that id and it is kept with the request: it shows in the request's panel under **Models → Observability**, the page can list only the answers rated good or bad, and the summary above the table shows the share rated good.

A score can come from four places, and each keeps its own: your code (the API), an agent (the MCP tool `rate_request`), the playground and the dashboard. Sending again from the same place replaces what that place sent before.

## Endpoint

```
POST https://api.runcrate.ai/v1/feedback
```

Use the same API key you call models with. A key made for a coding agent, which can only call models, can send feedback too.

| Field | Type | Description |
| - | - | - |
| `request_id` | string | Required. The `X-Request-Id` response header of the call, or the completion's `id` (`chatcmpl-<request id>`) |
| `thumbs` | `"up"` or `"down"` | A good or bad answer: stored as `1` or `-1` |
| `rating` | integer, 1 to 5 | A rating: stored as a score from `-1` (1) through `0` (3) to `1` (5), and the rating itself is kept |
| `score` | number, -1 to 1 | Your own score, kept to three decimals |
| `label` | string, up to 64 characters | Optional. A short tag, such as `helpful` or `wrong_format` |
| `comment` | string, up to 1,000 characters | Optional. What was right or wrong with the answer |

Send exactly one of `thumbs`, `rating` or `score`. A send is the whole feedback: a `label` or `comment` you leave out the second time is cleared.

The answer is the feedback as it was saved:

```json theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
{
  "object": "feedback",
  "request_id": "req_6f1c2b9e4a7d4c0e9b1f3a5d7c9e2b4f",
  "source": "api",
  "score": 0.5,
  "rating": 4,
  "label": "helpful",
  "comment": null,
  "created_at": "2026-10-23T12:00:31.204Z",
  "updated_at": "2026-10-23T12:04:10.882Z"
}
```

A key scores the calls of its own environment. A request id that is not the key's workspace's and environment's, or is older than the 30 days requests are kept, answers `404` with the code `request_not_found`. A call can be scored a moment after its answer has come back. Each key can send up to 600 scores a minute, and a workspace 3,000, apart from model calls; past that the answer is `429`.

## Score an answer with the OpenAI SDK

The completion's `id` is `chatcmpl-` and the request id, so you can send it as it is. For the other endpoints, read the `X-Request-Id` header from the raw response.

```python Python theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
import requests
from openai import OpenAI

API_KEY = "rc_live_YOUR_API_KEY"
client = OpenAI(base_url="https://api.runcrate.ai/v1", api_key=API_KEY)

# The raw response carries the headers; .parse() gives the usual completion.
raw = client.chat.completions.with_raw_response.create(
    model="deepseek-ai/DeepSeek-V3",
    messages=[{"role": "user", "content": "Reply with a JSON object with a 'city' key: the capital of France."}],
)
request_id = raw.headers.get("x-request-id")
completion = raw.parse()

good = completion.choices[0].message.content.strip().startswith("{")
requests.post(
    "https://api.runcrate.ai/v1/feedback",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={
        "request_id": request_id,  # or completion.id
        "thumbs": "up" if good else "down",
        "label": None if good else "wrong_format",
    },
    timeout=10,
).raise_for_status()
```

```typescript TypeScript theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
import OpenAI from "openai";

const API_KEY = "rc_live_YOUR_API_KEY";
const client = new OpenAI({ baseURL: "https://api.runcrate.ai/v1", apiKey: API_KEY });

// .withResponse() gives the completion and the raw response with its headers.
const { data: completion, response } = await client.chat.completions
  .create({
    model: "deepseek-ai/DeepSeek-V3",
    messages: [{ role: "user", content: "Reply with a JSON object with a 'city' key: the capital of France." }],
  })
  .withResponse();
const requestId = response.headers.get("x-request-id"); // or completion.id

const good = completion.choices[0].message.content?.trim().startsWith("{") ?? false;
const res = await fetch("https://api.runcrate.ai/v1/feedback", {
  method: "POST",
  headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({ request_id: requestId, thumbs: good ? "up" : "down", label: good ? undefined : "wrong_format" }),
});
if (!res.ok) throw new Error(`Feedback was not saved: ${res.status}`);
```

## With plain HTTP

```bash curl theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
curl https://api.runcrate.ai/v1/feedback \
  -H "Authorization: Bearer $RUNCRATE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"request_id": "req_6f1c2b9e4a7d4c0e9b1f3a5d7c9e2b4f", "rating": 4, "label": "helpful"}'
```

```python Python theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
import requests

API_KEY = "rc_live_YOUR_API_KEY"

answer = requests.post(
    "https://api.runcrate.ai/v1/chat/completions",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={"model": "deepseek-ai/DeepSeek-V3", "messages": [{"role": "user", "content": "Summarise RFC 2119 in one line."}]},
    timeout=60,
)
request_id = answer.headers["X-Request-Id"]

requests.post(
    "https://api.runcrate.ai/v1/feedback",
    headers={"Authorization": f"Bearer {API_KEY}"},
    json={"request_id": request_id, "rating": 2, "comment": "Too long: three sentences, not one."},
    timeout=10,
).raise_for_status()
```

```typescript TypeScript theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
const API_KEY = "rc_live_YOUR_API_KEY";
const headers = { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" };

const answer = await fetch("https://api.runcrate.ai/v1/chat/completions", {
  method: "POST",
  headers,
  body: JSON.stringify({ model: "deepseek-ai/DeepSeek-V3", messages: [{ role: "user", content: "Summarise RFC 2119 in one line." }] }),
});
const requestId = answer.headers.get("X-Request-Id")!;

await fetch("https://api.runcrate.ai/v1/feedback", {
  method: "POST",
  headers,
  body: JSON.stringify({ request_id: requestId, rating: 2, comment: "Too long: three sentences, not one." }),
});
```

## From an agent

The [Runcrate MCP server](https://runcrate.ai/docs/mcp/tools.md) has a `rate_request` tool, so an agent can score the answers it gets back (an eval loop, a reviewer agent). Its scores are kept as the agent's, apart from your code's.

| Argument | Description |
| - | - |
| `request_id` | Required. The `X-Request-Id`, or the completion id |
| `thumbs` | `up` or `down` |
| `rating` | 1 to 5 |
| `score` | -1 to 1 |
| `label` | Optional short tag, up to 64 characters |
| `comment` | Optional note, up to 1,000 characters |

Send one of `thumbs`, `rating` or `score`.

## In the dashboard

* **Observability**: open a request and rate it with the thumbs under **Rating**, with a note if you like. The panel also lists what your code and your agents sent. The **Rating** filter lists only answers rated good, rated bad, or rated at all, and the summary shows the share rated good once anything in the selection has a rating.
* **Playground**: rate the answer under the result, or any run in **Recent runs**.

Pressing the chosen thumb again clears your rating.

## Reading feedback back

`GET https://www.runcrate.ai/api/v1/requests/<request id>/feedback` with your API key lists every place's feedback on a request, newest first.

## How scores read

* Above `0` is a good answer, below `0` a bad one, and `0` (a rating of 3) neither.
* A request rated in several places counts as rated good if any place rated it good, and as rated bad if any rated it bad.
* Feedback is kept as long as the request: 30 days.


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