---
title: "Upload a product image."
method: POST
path: "/api/v1/products/{id}/images"
tags: ["Products"]
---

# Upload a product image.

`POST /api/v1/products/{id}/images`

## Request

- Base URL: `https://app.tracepass.eu`
- URL: `https://app.tracepass.eu/api/v1/products/{id}/images`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://app.tracepass.eu/api/oauth/authorize`, token URL `https://app.tracepass.eu/api/oauth/token`, scopes: `passports:write`)

Upload a single image file via `multipart/form-data` (field
name `file`) and append it to the product's `imageUrls`
array. Returns the resulting public R2 URL plus the full
updated array.

PNG / JPG / WebP only, max 5 MB per file, max 20 images
per product. **No Idempotency-Key support** — multipart
bodies aren't safely hashable; the client should check
existence and skip if retrying.

## Response `201`

Image uploaded.

- UploadImageResponse
  - `imageUrl` string, uri, required
  - `imageUrls` string[], required

## Other responses

- `400`: No file provided.
- `401`: Missing or invalid API key.
- `404`: Resource doesn't exist or is in a different workspace.
- `413`: Image larger than 5 MB.
- `415`: Unsupported MIME type, or the file's bytes don't match the declared image type.
- `422`: Product already has 20 images (max), or the virus scanner found malware in the file.
- `429`: Daily v1 write or passport-read budget exhausted. Resets at 00:00 UTC.
- `503`: The virus scanner is briefly unavailable; nothing was stored. Retry shortly.

---

[API](https://skmtc.dev/tracepass/apis/tracepass-api.md) · [All operations](https://skmtc.dev/tracepass/apis/tracepass-api/llms.txt) · [OpenAPI document](https://skmtc.dev/tracepass/apis/tracepass-api/revisions/60ddeb25638b?raw)
