# API reference: screenshot uploads

Attaching an image to feedback is a two-step flow: upload the file here first, then include the returned screenshot object in the
`screenshots` array of your [POST /api/feedback](/docs/api/feedback) call.

## Request

```text title=Endpoint
POST https://usero.io/api/screenshots
Content-Type: multipart/form-data
```

- **CORS:** `Access-Control-Allow-Origin: *`, callable directly from browsers and apps.
- **Auth:** the `clientId` form field identifies your project. No API key.

### Form fields

| Field        | Type   | Required | Description                                         |
| ------------ | ------ | -------- | --------------------------------------------------- |
| `screenshot` | file   | yes      | The image. Any `image/*` MIME type, 10MB max.       |
| `clientId`   | string | yes      | Your project id. Must belong to an existing client. |

One file per request. The widget allows up to 3 screenshots per feedback; if you need several, send several uploads.

## Examples

```bash title=curl
curl -X POST https://usero.io/api/screenshots \
  -F "screenshot=@./bug.png" \
  -F "clientId=YOUR_CLIENT_ID"
```

```javascript title=JavaScript
const form = new FormData()
form.append('screenshot', fileInput.files[0])
form.append('clientId', 'YOUR_CLIENT_ID')

const res = await fetch('https://usero.io/api/screenshots', {
	method: 'POST',
	body: form, // do NOT set Content-Type yourself, the browser adds the boundary
})
const { screenshot } = await res.json()
```

```swift title=Swift
let boundary = UUID().uuidString
var request = URLRequest(url: URL(string: "https://usero.io/api/screenshots")!)
request.httpMethod = "POST"
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")

var body = Data()
body.append("--\(boundary)\r\nContent-Disposition: form-data; name=\"clientId\"\r\n\r\nYOUR_CLIENT_ID\r\n".data(using: .utf8)!)
body.append("--\(boundary)\r\nContent-Disposition: form-data; name=\"screenshot\"; filename=\"bug.png\"\r\nContent-Type: image/png\r\n\r\n".data(using: .utf8)!)
body.append(pngData) // your image bytes
body.append("\r\n--\(boundary)--\r\n".data(using: .utf8)!)
request.httpBody = body

let (data, _) = try await URLSession.shared.data(for: request)
```

```python title=Python
import requests

res = requests.post(
    "https://usero.io/api/screenshots",
    files={"screenshot": open("bug.png", "rb")},
    data={"clientId": "YOUR_CLIENT_ID"},
)
print(res.json())
```

## Response

```json
{
	"success": true,
	"screenshot": {
		"fileName": "client_abc/1717740000000-x7k2p.png",
		"url": "https://usero.io/api/screenshots/client_abc/1717740000000-x7k2p.png",
		"fileSize": 48211,
		"width": 1280,
		"height": 800,
		"mimeType": "image/png"
	}
}
```

## Attach it to feedback

Pass the whole `screenshot` object inside the `screenshots` array when you submit the feedback:

```bash title=curl
curl -X POST https://usero.io/api/feedback \
  -H "Content-Type: application/json" \
  -d '{
    "clientId": "YOUR_CLIENT_ID",
    "comment": "Button overlaps the footer, screenshot attached",
    "screenshots": [
      {
        "fileName": "client_abc/1717740000000-x7k2p.png",
        "url": "https://usero.io/api/screenshots/client_abc/1717740000000-x7k2p.png",
        "fileSize": 48211,
        "width": 1280,
        "height": 800,
        "mimeType": "image/png"
      }
    ]
  }'
```

```javascript title=JavaScript
await fetch('https://usero.io/api/feedback', {
	method: 'POST',
	headers: { 'Content-Type': 'application/json' },
	body: JSON.stringify({
		clientId: 'YOUR_CLIENT_ID',
		comment: 'Button overlaps the footer, screenshot attached',
		screenshots: [screenshot], // the object from the upload response
	}),
})
```

## Errors

| Status | Body                                       | Meaning and fix                                                                       |
| ------ | ------------------------------------------ | ------------------------------------------------------------------------------------- |
| 400    | `{"error": "Screenshot file is required"}` | The `screenshot` form field is missing.                                               |
| 400    | `{"error": "Client ID is required"}`       | The `clientId` form field is missing.                                                 |
| 400    | `{"error": "File must be an image"}`       | The MIME type is not `image/*`.                                                       |
| 400    | `{"error": "File too large (max 10MB)"}`   | The file exceeds 10MB.                                                                |
| 403    | `{"error": "Domain not allowed"}`          | Your client has a domain allowlist and this origin is not on it.                      |
| 404    | `{"error": "Invalid client ID"}`           | No client with that id exists. Check [Find your clientId](/docs/find-your-client-id). |
| 500    | `{"error": "Failed to upload screenshot"}` | Something broke on our side. Safe to retry.                                           |
