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

# User feedback

> Capture user feedback and link it to project telemetry.

Send `POST /v1/projects/{project_id}/feedback` with an API key that has `events:ingest`. The key must belong to the same project. A `read`, `write`, or `errors:write` scope alone does not grant capture access.

```bash theme={null}
curl -X POST "https://api.squasher.ai/v1/projects/$SQUASHER_PROJECT_ID/feedback" \
  -H "Authorization: Bearer $SQUASHER_API_KEY" \
  -H "Content-Type: application/json" \
  --data-binary @feedback.json
```

Example `feedback.json`:

```json theme={null}
{
  "message": "Checkout stopped after I selected delivery.",
  "contact_email": "reader@example.com",
  "user_id": "user-123",
  "url": "https://example.com/checkout",
  "environment": "production",
  "release": "1.2.3",
  "service": "checkout",
  "session_id": "session-123",
  "trace_id": "0123456789abcdef0123456789abcdef",
  "metadata": { "step": "delivery", "cart_items": 2 }
}
```

A successful request returns `201` with `id`, `project_id`, and `created_at`. The response does not repeat the message or contact details.

| Field                           | Limit                                        |
| ------------------------------- | -------------------------------------------- |
| `message`                       | Required; 1–4,000 characters after trimming  |
| `contact_email`                 | Valid email address; 254 characters          |
| `name`, `environment`           | 128 characters each                          |
| `user_id`, `release`, `service` | 256 characters each                          |
| `url`                           | HTTP or HTTPS URL; 2,048 characters          |
| `session_id`, `replay_id`       | 128 characters each                          |
| `trace_id`                      | 32 hexadecimal characters                    |
| `error_group_id`                | UUID of an error group in the same project   |
| `metadata`                      | At most 32 fields and 16 KiB of encoded JSON |

Metadata keys have 1–64 characters. Values can be strings of up to 2,048 characters, finite numbers, booleans, or `null`. Nested objects and arrays are not accepted. The full request body must be at most 32 KiB. Unknown top-level fields are rejected.

Feedback stores telemetry IDs as links. It does not copy trace or replay data. Session, replay, and trace IDs can refer to telemetry that arrives later. An error group must already exist in the same project. Feedback is removed when its project is deleted.

Invalid fields return `400`; missing credentials return `401`; missing scope or project access returns `403`; an oversized body returns `413`.

The CLI accepts the same JSON file:

```bash theme={null}
squasher feedback create --project "$SQUASHER_PROJECT_ID" --body-file feedback.json --idempotency-key "feedback-unique-submission-id"
```

The typed client provides `client.feedback.create(params)` with the same fields and response.

Capture permits 20 new records per rolling minute and 1,000 per rolling day per project. Excess requests return `429` with `Retry-After` set to the number of seconds until capacity returns. All keys for a project share these limits.

Send an `Idempotency-Key` header with 1–128 printable ASCII characters to retry safely. The same key and payload return the original `201` response without using quota. A different payload with the same key returns `409`. Keys are scoped to the project and remain reserved while the feedback record exists. The TypeScript client creates a stable key for automatic retries; pass `{ idempotencyKey: "your-key" }` as the second argument to `feedback.create` to reuse a key across calls.

## Read and manage feedback

`GET /v1/projects/{project_id}/feedback` requires `errors:read` and returns `{ "data": [...], "next_cursor": null }`. Results are ordered newest first. Use `limit` (1–100, default 50) and pass `next_cursor` as `cursor` for the next page. Keep the same filters when you request the next page.

Filter by `status` (`new`, `reviewed`, `archived`), `environment`, `release`, `service`, or `error_group_id` with exact values. `url` matches a case-insensitive literal substring of the submitted URL. `contact` matches a case-insensitive literal substring of the name, email, or user ID. Contact fields are visible only to project readers.

`GET /v1/projects/{project_id}/feedback/{feedback_id}` returns one record. `PATCH` on the same path accepts a `status` value of `new`, `reviewed`, or `archived` and requires `errors:write`. The response contains the updated record. Records include the captured fields, status, and creation/update timestamps. A missing record or a record in another project returns 404.

```sh theme={null}
squasher feedback list --project "$SQUASHER_PROJECT_ID" --status new --contact example
squasher feedback get FEEDBACK_ID --project "$SQUASHER_PROJECT_ID"
squasher feedback update FEEDBACK_ID --project "$SQUASHER_PROJECT_ID" --status reviewed
```

```typescript theme={null}
const page = await client.feedback.list({ status: "new", limit: 25 });
if (page.data[0]) {
  const record = await client.feedback.get(page.data[0].id);
  await client.feedback.update(record.id, { status: "reviewed" });
}
```

A replay ID and a session ID are different identifiers. The detail response includes `replay_session_id`, resolved from the submitted `replay_id` in the same project. It is `null` when the ID is invalid or the replay is missing, deleted, or belongs to another project. Use this resolved session ID to open the replay. List and update responses do not resolve replay links.

Reuse the same idempotency key and payload when retrying a submission after an uncertain response. Use a new key for each new submission.

## Browser requests

The capture endpoint accepts cross-origin POST requests with API-key authentication. OPTIONS preflight does not require a key. Browser clients can send JSON, SDK headers, and `Idempotency-Key`, and read `Retry-After` on quota errors. Cookie credentials are not supported.
