Fernsteuerungs-Schnittstelle

Diese Seite wird aus dem laufenden System erzeugt: Pfade, Verfahren und die verlangten Rechte kommen aus dem Router, nicht aus einer gepflegten Liste. Ein neuer Endpunkt erscheint hier von selbst, ein entfernter verschwindet – eine handgeschriebene Doku wäre nach dem zweiten Endpunkt falsch.

Basis
{{ $basis }}
Anmeldung
Authorization: Bearer <token>
Grenze
{{ $limit }} Anfragen/Minute je Token

Erste Anfrage

curl -s {{ $basis }}/ping \
  -H "Authorization: Bearer <token>" \
  -H "Accept: application/json"

/ping verlangt kein besonderes Recht und antwortet mit Name und Rechten des Tokens – damit lässt sich prüfen, was ein Zugang darf, bevor man ihn einbaut. Token verwalten unter API-Zugänge.

Antwortform

Jede Antwort hat data für den Inhalt und – wo sinnvoll – meta für alles, was ihn einordnet: Seitenzahlen, Stichtage, Zählungen. Ein Fehler kommt als fehler mit einem Satz Klartext, weil die Meldung erfahrungsgemäß ungefiltert in einem Protokoll landet.

{
  "data": [ … ],
  "meta": { "seite": 1, "gesamt": 2897 }
}
{
  "fehler": "Für diese Filiale ist keine
   Lieferanschrift hinterlegt."
}

Statuscodes: 200 gelesen · 201 angelegt · 202 Lauf gestartet · 401 Token fehlt oder ungültig · 403 Recht fehlt · 409 bereits erledigt oder läuft schon · 422 Eingabe unbrauchbar · 429 zu viele Anfragen.

Rechte

@foreach($abilities as $schluessel => $beschreibung) @php $schreibt = str_contains($schluessel, ':write') || str_contains($schluessel, ':run'); @endphp @endforeach
{{ $schluessel }} {{ $beschreibung }}

Gelb markierte Rechte verändern etwas. orders:write löst verbindliche Bestellungen aus und ist bewusst kein Teil eines allgemeinen Schreibrechts.

@if($ohneText->isNotEmpty())

{{ $ohneText->count() }} Endpunkte ohne Beschreibung. Sie sind unten trotzdem aufgeführt – eine unbeschriebene Route wegzulassen wäre die schlechtere Lösung. Text ergänzen in config/api.php → beschreibungen: {{ $ohneText->implode(', ') }}

@endif
@foreach($endpunkte->groupBy(fn ($e) => $e['recht'] ?? 'ohne besonderes Recht') as $recht => $gruppe)

@if($recht === 'ohne besonderes Recht') Ohne besonderes Recht @else {{ $recht }} @endif

{{ $gruppe->count() }} Endpunkte
@foreach($gruppe as $e)
@foreach($e['methoden'] as $m) {{ $m }} @endforeach {{ $e['pfad'] }}
@if($e['text'])

{{ $e['text']['zweck'] ?? '' }}

@foreach(['filter' => 'Filter', 'rumpf' => 'Rumpf', 'antwort' => 'Antwort', 'wirkung' => 'Wirkung', 'schutz' => 'Schutz', 'hinweis' => 'Hinweis'] as $k => $label) @if(! empty($e['text'][$k]))

{{ $label }}: {{ $e['text'][$k] }}

@endif @endforeach @else

Noch keine Beschreibung hinterlegt.

@endif
@endforeach
@endforeach

Die Herleitung einer Menge

GET /replenishment/proposals/{id} liefert nicht nur die Zwischenwerte, sondern je Schritt die Formel mit eingesetzten Werten. Das ist die Antwort auf die Frage, die bei jeder angezweifelten Menge zuerst kommt: „1,65 × 0,1795 × √3 = 0,51" begründet einen Sicherheitsbestand, „safety_stock: 0.51" tut das nicht.

{
  "data": {
    "artikel":  { "sku": "10005245-3304", "name": "Big Bottle Aroma – Crazy Cactus …" },
    "filiale":  { "id": 6, "name": "Erfurt Hauptbahnhof" },
    "ergebnis": { "bestellmenge": 1, "wird_bestellt": true, "grund": null },
    "schritte": [
      { "nr": 3, "titel": "Sicherheitsbestand", "ergebnis": 0.51, "einheit": "Stück",
        "formel":    "z × Prognosefehler × √(Verkaufstage)",
        "rechnung":  "1.65 × 0.1795 × √3 = 0.51",
        "werte":     { "z_faktor": 1.65, "servicegrad": 95, "statistisch": 0.51 },
        "bedeutung": "Puffer gegen Schwankung. Weicht „statistisch\" vom Ergebnis ab, hat ein
                      gepflegter Mindest- oder Displaybestand angehoben." }
    ]
  }
}

Neun Schritte: Nachfrage → Schutzzeitraum → Sicherheitsbestand → Meldebestand → Zielbestand → Bestandsposition → Rohbedarf → Gebinderundung → Verfügbarkeit und Verteilung. Wird nichts bestellt, steht der Grund im Ergebnis und Schritt 9 zeigt, woran es lag – leeres Zentrallager, Lieferant ohne Bestand oder Kürzung bei knapper Ware. Die Werte stammen unverändert aus dem Rechenlauf und werden nicht neu berechnet: eine zweite Rechnung könnte von der ersten abweichen.

Anstoßbare Läufe

POST /jobs mit {"job": "…"}. Es gibt bewusst keine Möglichkeit, beliebige Konsolenbefehle auszuführen – das wäre eine Fernsteuerung des Servers, keine Schnittstelle dieser Anwendung.

@foreach($jobs as $name => $j) @endforeach
job Wirkung Dauer Argumente
{{ $name }} {{ $j['beschreibung'] ?? '' }} {{ $j['dauer'] ?? '' }} {{ ! empty($j['args']) ? implode(', ', $j['args']) : '–' }}

Beispiele

# Tageslage aller Filialen: wer muss bestellen?
curl -s "{{ $basis }}/replenishment/summary" -H "Authorization: Bearer $TOKEN"

# Offene Positionen einer Filiale, nur bestellbare
curl -s "{{ $basis }}/replenishment/proposals?branch_id=10&only_orderable=1" \
  -H "Authorization: Bearer $TOKEN"

# Warum genau diese Menge? Die komplette Herleitung zu einer Position
# (die id kommt aus der Liste oben)
curl -s "{{ $basis }}/replenishment/proposals/91898" -H "Authorization: Bearer $TOKEN"

# Artikel mit Abwärtstrend, absteigend nach Absatz ausgewertet
curl -s "{{ $basis }}/articles?trend=abwaerts&per_page=50" -H "Authorization: Bearer $TOKEN"

# Vor dem Bestellen IMMER die Vorschau lesen - die Lieferanschrift entscheidet,
# in welcher Filiale die Ware ankommt
curl -s "{{ $basis }}/riccardo/10/preview" -H "Authorization: Bearer $TOKEN"

# Bestellung auslösen (verbindlich, nicht zurückzurollen)
curl -s -X POST "{{ $basis }}/riccardo/10/order" -H "Authorization: Bearer $TOKEN"

# Auftragsbestätigung einliefern
curl -s -X POST "{{ $basis }}/order-confirmations" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"reference":"FIL-8-20260907","confirmed_at":"2026-09-08",
       "positions":[{"sku":"10000250-3198","quantity_confirmed":2}]}'

# Bestandsabzug anstoßen
curl -s -X POST "{{ $basis }}/jobs" \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"job":"sortiment:abzug"}'