> For the complete documentation index, see [llms.txt](https://docs.spiroq.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.spiroq.com/de/operator-api/operator-api/joby.md).

# Jobs und Idempotenz

Schreibzugriffe, die **am Automaten** ankommen sollen (fill, clear, layout, restock), müssen nicht im selben HTTP-Request enden. Die API legt einen **Job** an, wartet ungefähr **18 s** auf die Bestätigung vom Automaten und gibt dann 200, 207, 409 zurück — oder **202**, wenn es noch nicht fertig ist.

Eine Katalogänderung (`POST` / `PATCH /products`) wartet nicht auf einen Job am Automaten: Die Antwort 201 / 200 kommt sofort nach dem Speichern des Produkts. Der `Idempotency-Key` schützt beim Katalog in diesem Fall vor einem doppelten Anlegen.

***

## GET `/v1/jobs/:id`

`:id` ist die UUID des Jobs aus der Antwort des Schreibzugriffs. Fremd oder nicht vorhanden → `404` `Job not found`. Senden Sie `Idempotency-Key` bei GET nicht.

```bash
curl -sS "$SPIROQ_API/jobs/33333333-3333-3333-3333-333333333333" \
  -H "Authorization: Bearer $SPIROQ_TOKEN"
```

```json
{
  "job": {
    "id": "33333333-3333-3333-3333-333333333333",
    "kind": "fill",
    "status": "acked",
    "correlationId": "fill-aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "machine": {
      "id": "11111111-1111-1111-1111-111111111111",
      "serialNumber": "a1b2c3d4e5f6",
      "name": "Lobby-Automat"
    },
    "succeeded": ["11", "12"],
    "failed": null,
    "ackMessage": null,
    "publishedAt": "2026-09-14T10:15:02.000Z",
    "ackedAt": "2026-09-14T10:15:03.000Z",
    "createdAt": "2026-09-14T10:15:01.000Z"
  }
}
```

***

## Arten und Status

`kind`: `fill` | `clear` | `layout` | `restock_plan` | `catalog`

| `status`    | Bedeutung                                                                  |
| ----------- | -------------------------------------------------------------------------- |
| `queued`    | Angenommen, wartet auf den Versand an den Automaten                        |
| `published` | An den Automaten gesendet, Bestätigung steht aus                           |
| `acked`     | Fertig                                                                     |
| `partial`   | Ein Teil der Fächer ist fehlgeschlagen (fill / clear)                      |
| `failed`    | Der Vorgang ist fehlgeschlagen (Timeout, Automat hat nicht geantwortet, …) |

`succeeded` / `failed` sind Fachcodes (oder Details vom Automaten) oder `null`.\
Bei `failed` lesen Sie `ackMessage`. Typische Werte:

| `ackMessage`          | Bedeutung                                                                                             |
| --------------------- | ----------------------------------------------------------------------------------------------------- |
| `ack_timeout`         | Der Automat hat nicht rechtzeitig bestätigt (der Job hing länger als 5 min in `queued` / `published`) |
| `machine_offline`     | Der Automat war nicht online                                                                          |
| `mqtt_not_ready`      | Der Automat war nach der Auslagerung noch nicht bereit, das Layout anzunehmen (`ifStock=immediate`)   |
| `mqtt_publish_failed` | Die Änderung konnte nicht an den Automaten zugestellt werden                                          |

***

## HTTP-Codes beim Schreiben

| Status nach dem Warten (\~18 s) | HTTP                            | Body                                               |
| ------------------------------- | ------------------------------- | -------------------------------------------------- |
| `acked`                         | **200** (neues Produkt **201**) | Job; bei Restock zusätzlich `plan`                 |
| `partial`                       | **207**                         | Job + `error`, `code: "partial"`                   |
| `failed`                        | **409**                         | Job + `error` aus `ackMessage`, `code: "conflict"` |
| `queued` / `published`          | **202**                         | Job — fragen Sie `GET /v1/jobs/:id` ab             |

Das Layout kann **200** mit der Hülle `{ status: "stored_offline" / "unchanged", changed, job }` zurückgeben — siehe [Layout](/de/operator-api/operator-api/layout.md).

Bei **202** fragen Sie **alle 2–3 s** ab, höchstens ca. **5 Minuten**. Danach wechselt der Job zu `failed` (`ack_timeout`).

Derselbe POST/PUT mit **identischem** `Idempotency-Key` und **identischem** Body wartet nach 202 erneut auf das Ergebnis. Ändern Sie den Body nicht.

```bash
JOB_ID="33333333-3333-3333-3333-333333333333"

while true; do
  body=$(curl -sS "$SPIROQ_API/jobs/$JOB_ID" \
    -H "Authorization: Bearer $SPIROQ_TOKEN")
  status=$(printf '%s' "$body" | python3 -c "import sys,json; print(json.load(sys.stdin)['job']['status'])")
  echo "$status"
  case "$status" in
    acked|partial|failed) break ;;
  esac
  sleep 2
done
```

Setzen Sie das HTTP-Timeout des Clients **höher als 18 s** (empfohlen 30 s), damit die erste Antwort einschließlich 202 ankommt.

***

## Idempotenzschlüssel

```
Idempotency-Key: wms-fill-warehouse-42-2026-09-14T10:15:00Z
```

| Regel                                                                        | Verhalten                                                                |
| ---------------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| Pflicht bei `POST` / `PUT` / `PATCH`                                         | 1–200 Zeichen                                                            |
| Gilt **in Ihrem Konto**                                                      | ein anderer Betreiber kann denselben String haben                        |
| Gleicher Schlüssel + gleiches JSON, der Job hat bereits ein endgültiges HTTP | sofort dieselbe 200 / 201 / 207 / 409, ohne zweiten Vorgang am Automaten |
| Gleicher Schlüssel + anderer Body                                            | `409` `Idempotency-Key reused with a different body` + `jobId`           |
| Die gespeicherte Antwort war 202                                             | wird ignoriert; der Request wartet erneut                                |
| Älter als **24 h**                                                           | der Schlüssel startet einen neuen Vorgang                                |

Die Reihenfolge der Schlüssel im JSON und Leerzeichen sind unerheblich, **eine Wertänderung zählt**.

Empfohlene Form: `System + Bereich + Beleg-ID`, z. B. `sap-matdoc-4500012345-fill`.

***

## Zeitlimits

| Was                                                      | Zeit                                |
| -------------------------------------------------------- | ----------------------------------- |
| Warten des ersten HTTP auf die Bestätigung vom Automaten | ca. 18 s, dann 200/207/409 oder 202 |
| Layout `immediate` (Auslagerung + neues Produkt)         | bis ca. 90 s — deshalb oft 202      |
| Job im Status `queued` / `published`                     | 5 min → `failed` (`ack_timeout`)    |
| Gültigkeit von `Idempotency-Key`                         | 24 h                                |

***

## Fill-Ablauf aus Sicht Ihrer Anwendung

```mermaid
sequenceDiagram
  participant ERP as Ihr System
  participant API as Betreiber-API
  participant M as Automat

  ERP->>API: POST /stock/fill + Idempotency-Key
  Note over API,M: Zustellung an den Automaten
  alt Automat bestätigt innerhalb von ca. 18 s
    API->>ERP: 200 job
  else noch in Bearbeitung
    API->>ERP: 202 job
    ERP->>API: GET /jobs/:id
    API->>ERP: acked / partial / failed
  end
```
