---
title: Produktbild hochladen
description: Multipart-Upload eines einzelnen Bilds zum Produkt. PNG/JPG/WebP, ≤5 MB, max 20 Bilder. Kein Idempotency-Key (Multipart nicht sicher hashbar).
canonical: "https://www.tracepass.eu/de/docs/upload-product-image"
locale: de
source: "https://www.tracepass.eu/de/docs/upload-product-image"
---

# Produktbild hochladen

> Multipart-Upload eines einzelnen Bilds zum Produkt. PNG/JPG/WebP, ≤5 MB, max 20 Bilder. Kein Idempotency-Key (Multipart nicht sicher hashbar).

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

Lädt eine einzelne Bilddatei hoch (multipart/form-data, Feldname `file`) und hängt sie an das `imageUrls`-Array des Produkts an. Nützlich, wenn keine CDN-URL bereitsteht — das Bild landet in unserem R2-Bucket und die öffentliche URL wird in der Antwort zurückgegeben.

Nur PNG / JPG / WebP, max. 5 MB pro Datei, max. 20 Bilder pro Produkt. **Kein Idempotency-Key-Support** — Multipart-Bodies sind nicht sicher hashbar. Client sollte Existenz prüfen + bei Retry überspringen. Das passende `DELETE /api/v1/products/{id}/images/{index}` entfernt ein einzelnes Bild per nullbasiertem Index.

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Authorization` | header | string | yes | `Bearer <token>` — entweder ein `tp_` API-Schlüssel (Developer → API Keys; am einfachsten, für Server-zu-Server) oder ein OAuth-2.0-Access-Token (Developer → OAuth Apps; für nutzerautorisierte Apps, scoped und widerrufbar). Die Authentication-Seite enthält den vollständigen OAuth-Flow und die Scope-Liste. |
| `Content-Type` | header | string | yes | `multipart/form-data` mit der von Ihrem HTTP-Client gesetzten Boundary. |
| `id` | path | ObjectId | yes | Produkt-ID. |
| `file` | body | binary (PNG / JPG / WebP, ≤ 5 MB) | yes | Bild-Bytes, gesendet als `file`-Formularfeld. |

## Examples

```bash
curl -sS -X POST \
  https://app.tracepass.eu/api/v1/products/6650a1b2c3d4e5f6a7b8c9d0/images \
  -H "Authorization: Bearer tp_REDACTED_xxxxxxxxxxxx" \
  -F "file=@./battery-hero.jpg"
```

```typescript
import { readFile } from "node:fs/promises";

const file = await readFile("./battery-hero.jpg");
const form = new FormData();
form.set("file", new Blob([file], { type: "image/jpeg" }), "battery-hero.jpg");

const res = await fetch(
  `https://app.tracepass.eu/api/v1/products/${id}/images`,
  {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.TRACEPASS_API_KEY}` },
    body: form,
  },
);
if (!res.ok) throw new Error(`Upload failed: ${res.status}`);
const { imageUrl, imageUrls } = await res.json();
```

```python
import os, requests

with open("battery-hero.jpg", "rb") as fh:
    res = requests.post(
        f"https://app.tracepass.eu/api/v1/products/{product_id}/images",
        headers={"Authorization": f"Bearer {os.environ['TRACEPASS_API_KEY']}"},
        files={"file": ("battery-hero.jpg", fh, "image/jpeg")},
    )
res.raise_for_status()
data = res.json()
print(data["imageUrl"])
```

## Responses

### 201 — Hochgeladen

```json
{
  "imageUrl": "https://r2.tracepass.eu/<companyId>/products/<id>/images/<imageId>.jpg",
  "imageUrls": [
    "https://existing-image-1.jpg",
    "https://r2.tracepass.eu/<companyId>/products/<id>/images/<imageId>.jpg"
  ]
}
```

### 400 — Keine Datei

```json
{ "error": "No file provided. Send as multipart/form-data with field name 'file'." }
```

### 413 — Zu groß

```json
{ "error": "Image must be under 5MB" }
```

### 415 — Nicht unterstützt

```json
{ "error": "Unsupported MIME type — accepts image/png, image/jpeg, image/webp" }
```

### 422 — Limit erreicht

```json
{ "error": "Product already has 20 images (max). Delete one first." }
```

## Related

- [Produkt aktualisieren](https://www.tracepass.eu/de/docs/update-product.md)
- [Produkt anlegen](https://www.tracepass.eu/de/docs/create-product.md)
