# Konventionen

> Antwortformat, Paging, Geldbeträge, Prozente, Datumsangaben, IDs, Upserts, Rate Limits und Versionsverwaltung.

## Anfragen und Antworten

- Anfragen und Antworten sind JSON. Senden Sie `Content-Type: application/json` mit einem Body.
- Feldnamen sind camelCase: `listPrice`, `accountCode`, `nextOffset`.
- Abfrageparameter sind gewöhnliche Zeichenketten: `?limit=20&status=new`.

Jede erfolgreiche Antwort ist ein Objekt mit `data`:

```json
{
  "data": [],
  "nextOffset": 50,
  "currency": "GBP"
}
```

| Feld         | Wenn es vorhanden ist                          | Bedeutung                                                                                         |
| ------------ | ---------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `data`       | Immer                                          | Das Objekt oder die Liste, nach der Sie gefragt haben.                                            |
| `nextOffset` | Listen, die paginiert sind                     | Geben Sie es als `offset` weiter, um die nächste Seite zu erhalten. `null` auf der letzten Seite. |
| `currency`   | Antworten mit Preisen in der Workspace-Währung | ISO 4217-Code für jeden Geldbetrag in `data`.                                                     |

Bestellungen tragen ihr eigenes `currency`-Feld, das festgelegt wird, wenn die Bestellung aufgegeben wurde.

## Paging

Listen nehmen `limit` und `offset` entgegen:

| Parameter | Standard    | Maximal                               |
| --------- | ----------- | ------------------------------------- |
| `limit`   | das Maximum | 50 für Produkte, 100 für alles andere |
| `offset`  | 0           | keine                                 |

Weiterhin Anfragen mit `offset=nextOffset` stellen, bis `nextOffset` `null` ist:

```js
let offset = 0
const all = []
while (offset !== null) {
  const res = await fetch(
    `https://tradecatalog.app/api/v1/products?limit=50&offset=${offset}`,
    {
      headers: { Authorization: `Bearer ${process.env.TRADECATALOG_API_KEY}` },
    },
  )
  const page = await res.json()
  all.push(...page.data)
  offset = page.nextOffset
}
```

Bei `/products` wird `nextOffset` gesetzt, sobald eine Seite vollständig ist. Wenn die Kataloggröße ein exaktes Vielfaches von `limit` ist, gibt die letzte Anfrage eine leere `data`-Liste mit `nextOffset: null` zurück.

Einige Listen sind begrenzt. `GET /orders` umfasst die 200 neuesten passenden Aufträge und `GET /price-changes` die 100 neuesten.

## Geld

Geld ist eine **Ganzzahl in Einheiten der Währung** (Minor Units), sodass es keine Rundungsfehler gibt:

| Wert   | Währung | Bedeutet                  |
| ------ | ------- | ------------------------- |
| `1234` | GBP     | £12.34                    |
| `1850` | EUR     | €18.50                    |
| `500`  | JPY     | ¥500 (keine Mikroeinheit) |

Durch die Anzahl der Dezimalstellen der Währung teilen, um sie anzuzeigen. In JavaScript übernimmt `Intl.NumberFormat` dies für Sie:

```js
const format = (minor, currency) => {
  const f = new Intl.NumberFormat('en-GB', { style: 'currency', currency })
  const digits = f.resolvedOptions().maximumFractionDigits
  return f.format(minor / 10 ** digits)
}
format(1234, 'GBP') // "£12.34"
```

Preise sind so festgelegt, wie der Lieferant sie in seinem Preislisten hat. Ob dies Steuern beinhaltet, ist die `taxLabel` des Workspaces auf [`GET /workspace`](/docs/api/workspace): z. B. „ex VAT“ (vor Steuern), „inc GST“ (inkl. Steuern) oder leer, wenn der Lieferant keine Steuerausdrücke anzeigt.

## Prozentsätze

Prozentsätze sind **Basispunkte**: Hundertstel eines Prozents, als Ganzzahlen.

| Wert    | Bedeutet |
| ------- | -------- |
| `1250`  | 12,5%    |
| `3500`  | 35%      |
| `10000` | 100%     |

## Daten

Daten sind ISO 8601-Strings in UTC, wie `2026-09-27T08:15:00.000Z`. Die eigene Zeitzone des Workspaces ist `timezone` auf [`GET /workspace`](/docs/api/workspace).

## IDs

IDs sind UUID-Strings. Wo immer ein Endpunkt einen **Kunden** annimmt, können Sie die Kunden-ID oder den Kontocode des Kunden (case-insensitiv) übergeben, je nachdem, was Ihr System hat:

```bash
curl "https://tradecatalog.app/api/v1/prices?customer=OAK01&sku=HX-8510" \
  -H "Authorization: Bearer $TRADECATALOG_API_KEY"
```

## Erstellen und aktualisieren (PUT)

`PUT /products` und `PUT /customers` erstellen oder aktualisieren in einem Aufruf, bis zu 100 Zeilen gleichzeitig:

- Produkte werden anhand von **SKU** (case-insensitiv) abgeglichen.
- Kunden werden anhand von **Kontocodes**, dann **E-Mail-Adressen**, dann **Namen** abgeglichen.

Beide sind sicher wiederholbar. Das Senden der gleichen Zeilen zweimal ergibt das gleiche Ergebnis; der zweite Aufruf meldet sie nur als aktualisiert.

Ein `PUT` schreibt die Felder, die Sie senden. Einige optionale Produktfelder werden gelöscht, wenn Sie sie weglassen, andere bleiben erhalten. Jede Endpunktseite listet auf, welche.

## Begrenzungen

Jeder Schlüssel kann **120 Anfragen pro Minute** machen. Über diesem Limit erhalten Sie `429 rate_limited` mit `Retry-After: 60`. Warten Sie und versuchen Sie es erneut. [Fehler](/docs/api/errors#retrying) enthält einen Wiederholungshelper.

So viel Batch-Schreibvorgänge wie möglich: ein `PUT` mit 100 Produkten zählt als eine Anfrage.

## Versionsverwaltung

Die Version ist im Pfad enthalten: `/api/v1`.

- Innerhalb von v1 führen wir nur **additive** Änderungen durch: neue Endpunkte, neue optionale Parameter, neue Felder in Antworten. Schreiben Sie Ihren Code so, dass er Felder ignoriert, die er nicht kennt.
- Eine Breaking Change erhält eine neue Version (`/api/v2`), wobei v1 weiterhin läuft und die Änderung im [Changelog](/docs/changelog) angekündigt wird.
