# API reference: POST /api/feedback

Create one feedback item. This is the same endpoint the widget uses, and it is open to direct calls from any language.

## Request

```text title=Endpoint
POST https://usero.io/api/feedback
Content-Type: application/json
```

- **CORS:** `Access-Control-Allow-Origin: *`. Call it straight from browsers, mobile apps, desktop apps, or servers.
- **Auth:** the `clientId` in the body identifies your project. No API key, no auth header.
- **Abuse control:** if your client has a domain allowlist configured in Settings, requests from other origins get a `403`. New
  clients have no allowlist, so everything is accepted.

## Required fields

`clientId` is always required. On top of that, every submission needs a `rating` (1 to 4), a non-empty `comment`, or both.
Everything else is optional.

## Fields

| Field             | Type    | Required                | Description                                                                                                                                                                                                                       |
| ----------------- | ------- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `clientId`        | string  | yes                     | Your project id, starts with `client_`. See [Find your clientId](/docs/find-your-client-id).                                                                                                                                      |
| `rating`          | integer | one of rating / comment | 1 to 4: 1 Terrible, 2 Bad, 3 Good, 4 Amazing.                                                                                                                                                                                     |
| `comment`         | string  | one of rating / comment | Free-text feedback. Must be non-empty if no rating is sent.                                                                                                                                                                       |
| `userEmail`       | string  | no                      | The submitter's email, must be a valid address. Lets you reply and groups feedback by person in the dashboard. An empty string is treated as absent.                                                                              |
| `pageUrl`         | string  | no                      | URL the feedback was given on. If omitted, the server falls back to the `Referer` header.                                                                                                                                         |
| `pageTitle`       | string  | no                      | Title of the page the feedback was given on.                                                                                                                                                                                      |
| `referrer`        | string  | no                      | The page the user arrived from.                                                                                                                                                                                                   |
| `environment`     | string  | no                      | Tags the feedback with an environment (for example `staging`). See the warning below before sending this.                                                                                                                         |
| `screenshots`     | array   | no                      | Screenshot objects returned by [POST /api/screenshots](/docs/api/screenshots). Each item: `fileName` (string), `url` (string), `fileSize` (number), `mimeType` (string), `width` (number, optional), `height` (number, optional). |
| `metadata`        | object  | no                      | Arbitrary JSON attached to the feedback, 10KB max when serialized. Good place for app version, OS, build number, or feature flags.                                                                                                |
| `sessionReplayId` | string  | no                      | 1 to 128 characters. Links the feedback to a session replay recorded by the [session replay plugin](/docs/widget/session-replay).                                                                                                 |
| `replayOffsetMs`  | integer | no                      | Non-negative millisecond offset into the linked replay at which the feedback was given.                                                                                                                                           |

> [!WARNING] **Omit `environment` for your default environment.** Do not send a placeholder like `"no-env"` or `"default"`. The
> dashboard treats an absent environment as the default; a literal placeholder string creates a separate environment and your
> feedback will not appear in the default inbox.

## Examples

Minimal submission:

```bash title=curl
curl -X POST https://usero.io/api/feedback \
  -H "Content-Type: application/json" \
  -d '{"clientId": "YOUR_CLIENT_ID", "rating": 3}'
```

```javascript title=JavaScript
await fetch('https://usero.io/api/feedback', {
	method: 'POST',
	headers: { 'Content-Type': 'application/json' },
	body: JSON.stringify({ clientId: 'YOUR_CLIENT_ID', rating: 3 }),
})
```

```swift title=Swift
var request = URLRequest(url: URL(string: "https://usero.io/api/feedback")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: [
    "clientId": "YOUR_CLIENT_ID",
    "rating": 3,
])
let (data, _) = try await URLSession.shared.data(for: request)
```

```python title=Python
import requests

requests.post(
    "https://usero.io/api/feedback",
    json={"clientId": "YOUR_CLIENT_ID", "rating": 3},
)
```

Full-field submission:

```bash title=curl
curl -X POST https://usero.io/api/feedback \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "YOUR_CLIENT_ID",
    "rating": 2,
    "comment": "Export to CSV times out on large projects",
    "userEmail": "jamie@example.com",
    "pageUrl": "https://yourapp.com/projects/42/export",
    "pageTitle": "Export project",
    "referrer": "https://yourapp.com/projects/42",
    "environment": "staging",
    "metadata": {
      "appVersion": "2.4.1",
      "os": "macOS 15.2",
      "build": 1842
    }
  }'
```

```javascript title=JavaScript
await fetch('https://usero.io/api/feedback', {
	method: 'POST',
	headers: { 'Content-Type': 'application/json' },
	body: JSON.stringify({
		clientId: 'YOUR_CLIENT_ID',
		rating: 2,
		comment: 'Export to CSV times out on large projects',
		userEmail: 'jamie@example.com',
		pageUrl: window.location.href,
		pageTitle: document.title,
		referrer: document.referrer,
		environment: 'staging',
		metadata: { appVersion: '2.4.1', os: navigator.platform, build: 1842 },
	}),
})
```

```swift title=Swift
var request = URLRequest(url: URL(string: "https://usero.io/api/feedback")!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONSerialization.data(withJSONObject: [
    "clientId": "YOUR_CLIENT_ID",
    "rating": 2,
    "comment": "Export to CSV times out on large projects",
    "userEmail": "jamie@example.com",
    "environment": "staging",
    "metadata": [
        "appVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "unknown",
        "os": ProcessInfo.processInfo.operatingSystemVersionString,
    ],
])
let (data, _) = try await URLSession.shared.data(for: request)
```

```python title=Python
import requests

requests.post(
    "https://usero.io/api/feedback",
    json={
        "clientId": "YOUR_CLIENT_ID",
        "rating": 2,
        "comment": "Export to CSV times out on large projects",
        "userEmail": "jamie@example.com",
        "environment": "staging",
        "metadata": {"appVersion": "2.4.1", "build": 1842},
    },
)
```

For native apps, `metadata` is the place for app version, OS version, and build number, so every report arrives with the context
you need to reproduce it.

## Response

Success:

```json
{ "success": true, "feedbackId": "abc123" }
```

## Errors

| Status | Body                                                                | Meaning and fix                                                                                                                   |
| ------ | ------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| 400    | `{"error": "Invalid data provided", "issues": {"rating": ["..."]}}` | Validation failed. `issues` names each failing field. The most common cause: neither `rating` nor a non-empty `comment` was sent. |
| 400    | `{"error": "Metadata too large (max 10KB)"}`                        | Serialized `metadata` exceeds 10KB. Trim it.                                                                                      |
| 403    | `{"error": "Domain not allowed"}`                                   | Your client has a domain allowlist and this request's origin is not on it. Add the origin in Settings, or clear the allowlist.    |
| 500    | `{"error": "Internal server error"}`                                | Something broke on our side. Safe to retry.                                                                                       |

## Related

- [Screenshot uploads](/docs/api/screenshots): two-step flow to attach images
- [Session replay plugin](/docs/widget/session-replay): where `sessionReplayId` comes from
- [Find your clientId](/docs/find-your-client-id)
