sailing Foudra MRP
Referenz chevron_rightapi/index

Die API

JSON über HTTPS auf der eigenen Subdomain der Firma, authentifiziert mit einem API-Schlüssel als Authorization: Bearer fdr_…. Der Scope eines Schlüssels begrenzt ihn: read-Schlüssel dürfen nur GET, write-Schlüssel dürfen alles außer eine Bestellung genehmigen oder versenden, approve-Schlüssel dürfen auch das. Dezimalzahlen (Mengen, Geldbeträge) sind Strings – JSON-Zahlen sind Gleitkommazahlen, diese Werte nicht. Fehler kommen als {"error": "…"} mit aussagekräftigem Statuscode.

Eine Anwendung, die viele Firmen nutzen (Syncomatic, eine Smartphone-App), verwendet statt eines Schlüssels OAuth 2.0, damit jeder Benutzer sie selbst erlaubt und unter Verbundene Anwendungen wieder trennen kann. Siehe OAuth am Ende dieser Seite; ihre Zugriffstoken (fdo_…) werden genauso wie Schlüssel gesendet und unterliegen denselben Scopes.

Lesen

Endpunkt Liefert
GET /api/parts · /api/parts/{id} Artikel, seitenweise (?page=&per=); ?category=504 schränkt auf eine Kategorie ein, ?q= durchsucht Nummer, Name und Beschreibung; ein Artikel enthält on_hand
GET /api/parts/{id}/barcode.png Die Artikelnummer als Code-128-Barcode im PNG-Format, mit der Nummer darunter; ?scale=1–10, ?height= (Pixel), ?text=0 nur für die Striche. Das JSON jedes Artikels gibt diesen Pfad als barcode_url an
GET /api/parts/{id}/bom Die Stücklistenpositionen des Artikels: Komponente, Menge pro Einheit, Referenz, Arbeitsgang
GET /api/parts/{id}/routing Die Arbeitsgänge des Artikels, mit Summen
GET /api/parts/{id}/images Die Bilder des Artikels: ID, Dateiname, Kennzeichen für das Hauptbild
GET /api/suppliers Die Lieferanten
GET /api/parts/{id}/sourcing Wer den Artikel verkauft, unter welcher Nummer, zu welchem Preis
GET /api/demand?status=open&channel= Bedarfe mit Vertriebskanal, Referenz, Menge und versendeter Menge
GET /api/channels Die Vertriebskanäle
GET /api/channels/{code}/listings Jeder auf dem Kanal gelistete Artikel mit seiner Kanal-Artikelnummer; null bedeutet: noch nicht übertragen
GET /api/stock Eine Zeile pro Artikel, Lagerort und Charge (lot_id, lot_code, expires_at; nicht chargengeführter Bestand hat keine)
GET /api/lots?q= Chargen und Seriennummern, die neuesten zuerst: Code, Artikel, verfügbarer Bestand, Verfallsdatum, Lieferantencharge
GET /api/lots/{id}/barcode.png Der Code einer Charge oder Seriennummer als Code-128-PNG, wie bei einem Artikel; barcode_url im JSON jeder Charge
GET /api/sourcing?supplier=&supplier_part_number=&part_number= Welcher unserer Artikel einer Lieferantennummer entspricht, oder umgekehrt. Groß- und Kleinschreibung spielt keine Rolle
GET /api/purchase-orders?status=&number=PO-00004 Bestellungen mit Lieferant, Zwischensumme und Link. number findet eine Bestellung so, wie Menschen sie nennen
GET /api/purchase-orders/{id} Eine Bestellung mit ihren Positionen
GET /api/workstations · /api/workstations/{id} Arbeitsplätze mit Gruppe und Status
GET /api/workstations/{id}/next Der Auftrag, den dieser Arbeitsplatz jetzt erledigen soll, mit seinen Materialien – null, außer der Arbeitsplatz ist frei und ein Arbeitsgang ist bereit. ?material=<part id> beschränkt auf Aufträge, die diesen Artikel verbrauchen. Die Antwort enthält current_material und requires_material_change des Arbeitsplatzes
GET /api/work-orders?status=&number=WO-00023&external_ref= · /api/work-orders/{id} Aufträge; ein einzelner Auftrag enthält seine Arbeitsgänge sowie Materialien und Startbereitschaft jedes Arbeitsgangs. Jeder offene Auftrag trägt die Prognose der Vorwärtsterminierung: expected_start, expected_finish, late (Fertigstellung nach due_at) und delay_reasons; vergleichen Sie expected_finish mit due_at, um eine Verzögerung zu erkennen. Abgeschlossene und stornierte Aufträge haben keine Prognose (null)
GET /api/{parts,workstations,work-orders}/{id}/custom Das undurchsichtige JSON oder null

Schreiben

Endpunkt Body Bewirkt
POST /api/purchase-orders {supplier, currency?, exchange_rate?, expected_at?, external_ref?, notes?, lines:[{supplier_part_number | part_id, quantity, unit_price?}]} Legt einen Entwurf an; Positionen mit Lieferantennummer werden über die Bezugsquellen aufgelöst. Eine unbekannte Nummer lässt die ganze Anfrage scheitern, mit Nennung der Position. Ein Mensch genehmigt
PUT /api/workstations/{id}/status {"status": "idle|busy|blocked|offline", "note"?} Spiegelt den Zustand einer Maschine
POST /api/work-order-steps/{id}/start {"workstation_id"?} Übernimmt einen Arbeitsgang
POST /api/work-order-steps/{id}/complete {"quantity": "9", "consumed"?: [{"part_id", "quantity"}], "used"?: [{"part_id", "lots": ["R-2291"]}], "made"?: ["G0088"], "expires"?: "2027-03-31"} Bucht gefertigte Einheiten; entnimmt Materialien retrograd zur Stücklistenmenge oder, für jeden in consumed genannten Artikel, zur gemeldeten Ist-Menge; der letzte Arbeitsgang bucht den Zugang des Ergebnisses. Für chargengeführte Artikel: used nennt die verbrauchten Chargen oder Seriennummern, made die Seriennummern des Ergebnisses (eine pro Einheit, Pflicht) oder seine Charge (Standard: die Auftragsnummer), expires das Verfallsdatum einer neuen Zielcharge
POST /api/work-order-steps/{id}/scrap {"quantity": "9", "consumed"?: […], "used"?: […], "note"?} Ein Durchlauf ohne Ergebnis: Materialien verbraucht, kein Zugang, Zählerstand des Arbeitsgangs unverändert
POST /api/work-order-steps/{id}/pause Ein laufender Arbeitsgang mit Restmenge geht zurück auf ausstehend, der Fortschritt bleibt erhalten, der Arbeitsplatz wird freigegeben – so wird der nächste Auftrag neu gewählt. Die Druckverwaltung tut das nach jedem Druckbett
PUT /api/workstations/{id}/material {"part_id": 934} oder {"part_id": null} Womit der Arbeitsplatz gerüstet ist – das eingelegte Filament eines Druckers
PUT /api/tasks/{source} [{key, priority, message, href, actions?: [{key, label, tone?}]}] Ersetzt die Aufgaben dieser Quelle vollständig; alles Weggelassene gilt als erledigt. Quellnamen sind Slugs in Kleinbuchstaben. Eine Aufgabe kann bis zu sechs Aktionen anbieten – Schaltflächen, die ein Mensch drücken kann; ein Druck erledigt die Aufgabe mit diesem Schlüssel als outcome, den die Quelle zurückliest. Eine Aufgabe mit Ergebnis wird nie wieder geöffnet: Verwenden Sie für ein neues Vorkommen einen neuen Schlüssel
GET /api/tasks/{source} Die offenen Aufgaben dieser Quelle und die kürzlich erledigten mit einem outcome – so erfährt eine Integration, welche Schaltfläche gedrückt wurde
PUT /api/{kind}/{id}/custom Das JSON selbst Ersetzt den undurchsichtigen Block (höchstens 64 KB)
POST /api/parts {category_code, name, part_type, description?, notes?, unit_of_measure?, is_stocked?, minimum_stock?, lead_time_days?, tracking?, custom?} Legt einen Artikel an; die Lagernummer wird in der Kategorie vergeben. Liefert den Artikel
POST /api/parts/{id}/bom/lines {child_part_id, quantity, reference?, notes?, routing_step_id?} Fügt eine Stücklistenposition hinzu; die Regeln gegen Zyklen gelten
PUT /api/bom/lines/{id} {quantity?, reference?, notes?} Bearbeitet eine Position
POST /api/parts/{id}/routing/steps {name, group_code?, seq?, setup_minutes?, run_minutes_per_unit?, batch_size?, instructions?} Fügt einen Arbeitsgang hinzu; ein leeres seq nimmt den nächsten Zehnerschritt
POST /api/parts/{id}/images multipart, Feld file Lädt ein Bild hoch (höchstens 12 MB); das erste wird zum Hauptbild
PUT /api/parts/{id}/sourcing/{supplier} {supplier_part_number, unit_price?, currency?, product_url?, minimum_order_qty?, pack_size?, lead_time_days?, is_preferred?, notes?} Legt die Bezugsquelle des Lieferanten für den Artikel an (201) oder aktualisiert sie (200); nicht gesendete Felder behalten ihren Wert. Die erste Bezugsquelle eines Artikels wird bevorzugt
POST /api/demand {channel, reference, part_id | part_number | channel_part_number, quantity, due_at?, kind?, notes?} Bedarf eingegangen. Idempotent über (channel, reference): Eine Wiederholung, solange offen, aktualisiert Menge und Datum und antwortet mit 200; ein erstes Eintreffen antwortet mit 201. Eine geschlossene oder stornierte Referenz bleibt unberührt
POST /api/demand/{id}/ship {quantity?, location_id? | location_code?, lots?} Bedarf versendet. Bucht diese Menge (leer: den Rest) vom Lagerort oder vom größten Vorrat des Artikels ab, als Lagerbewegung mit Verweis auf den Bedarf; schließt den Bedarf, sobald alles versendet ist. lots nennt die versendeten Chargen oder Seriennummern, wo auch immer sie liegen: Pflicht für ein seriennummerngeführtes Produkt. 409, wenn nicht genug verfügbar ist
POST /api/demand/{id}/cancel Bedarf storniert
PUT /api/channels/{code}/listings/{part_id} {channel_part_number?, is_listed?, notes?} Listet einen Artikel auf dem Kanal oder speichert die Nummer des Kanals für ihn

Um benachrichtigt zu werden, wenn etwas geschieht, statt nachzufragen, siehe Webhooks.

Für einen Assistenten

Was ein Chat-Agent braucht, um eine Person auf Kurs zu halten: ein Aufruf für „Was braucht mich?“, Suchen nach den Namen, die Menschen verwenden, und die Schaltflächen. Jeder Eintrag trägt eine url, die ein Mensch anklicken kann.

Endpunkt Liefert
GET /api/brief Alles mit aktuellem Handlungsbedarf: offene Aufgaben, Zustände der Arbeitsplätze, Fertigungsaufträge im Entwurf und offene, Bestellungen, die auf Genehmigung warten, genehmigte, aber nicht versendete und überfällige, zu fixierende Planaufträge, nötige Umlagerungen, Fehlmengen und Aufträge, die laut Kapazitätsprüfung verspätet fertig werden. headline sagt dasselbe in wenigen Sätzen, das Dringendste zuerst
GET /api/tasks Jede offene Aufgabe, die dringendste zuerst, mit ihren actions
GET /api/planning Die Planungsseite: Planaufträge proposed und firmed (jeder sagt, because wovon), und die Terminplanung jeder Arbeitsplatzgruppe mit ihren verspäteten Aufträgen
GET /api/demand/{id}/progress „Wo ist meine Bestellung?“: der Bedarf, Bestand und eingehende Zugänge des Artikels, die Vorschläge des Planungslaufs für diesen Bedarf, die Fertigungsaufträge, die ihn herstellen, und welche davon verspätet terminiert sind. Zugänge gehören zum Artikel, nicht zum Bedarf: Zwei Bedarfe für denselben Artikel sehen denselben Bestand

Aktionen ausführen

Endpunkt Body Erfordert Bewirkt
POST /api/tasks/{id}/act {"action": "ready"} operator (Bediener) Drückt eine der Schaltflächen der Aufgabe
POST /api/tasks/{id}/snooze {"days": 2} operator (Bediener) Blendet die Aufgabe aus; eine Woche, wenn keine Zahl angegeben ist
POST /api/tasks/{id}/resolve operator (Bediener) Schließt die Aufgabe von Hand
POST /api/work-orders {part_number | part_id, location, quantity?, external_ref?, due_at?, priority?, notes?, output_location?, release?} planner (Planer) Legt einen Fertigungsauftrag an: als Entwurf oder freigegeben, wenn release true ist. location ist der Code des Lagerorts, von dem er Teile entnimmt (und an den er das Ergebnis bucht, sofern output_location nichts anderes sagt). external_ref ist Ihr Name für das, worum es beim Auftrag geht. Senden Sie einen Idempotency-Key, sonst legt eine Wiederholung einen zweiten Auftrag an
POST /api/work-orders/{id}/issue {part_number | part_id, quantity?, note?, lots?} operator (Bediener) Entnimmt einen Artikel für den Auftrag aus dem Lager, außerhalb seiner Stückliste; lots nennt seine Chargen oder Seriennummern. 409, wenn nicht genug da ist
POST /api/work-orders/{id}/release planner (Planer) Vom Entwurf zu freigegeben. Antwortet mit dem Auftrag und der Startbereitschaft jedes Arbeitsgangs
POST /api/work-orders/{id}/cancel planner (Planer) Storniert einen nicht abgeschlossenen Auftrag
POST /api/planning/run planner (Planer) Startet die Planung sofort. Antwortet mit den Vorschlägen
POST /api/planning/firm {"ids": [12, 13], "location": "BHILL"} planner (Planer) Macht aus Vorschlägen Fertigungsaufträge und Bestellungen im Entwurf. location (oder location_id) ist nötig, wenn eine Eigenfertigung darunter ist
POST /api/purchase-orders/{id}/cancel planner (Planer) Storniert die Bestellung
POST /api/purchase-orders/{id}/approve admin (Administrator), approve-Schlüssel Vom Entwurf zu genehmigt
POST /api/purchase-orders/{id}/send planner (Planer), approve-Schlüssel Bucht die Bestellung als versendet. Foudra übermittelt sie nicht an den Lieferanten

Für jeden dieser Aufrufe gelten vier Regeln:

Jede Antwort enthält summary, einen Satz, den man einem Menschen so wiedergeben kann, data und acted_as. Ablehnungen: 403 für den Scope eines Schlüssels oder die Rolle eines Benutzers, 404, 409, wenn sich die Sache nicht in einem Zustand befindet, der das erlaubt (die Meldung sagt, warum), 422 für eine ungültige Aktion oder einen wiederverwendeten Schlüssel.

Das Druckfarm-Muster

Fragen Sie /next?material=<loaded> ab, solange der Drucker frei ist – ist mit dem eingelegten Material etwas bereit? Wenn nicht, sagt /next ohne Filter, was der oberste Auftrag braucht; spielen Sie eine Aufgabe Filament wechseln ein und gehen Sie auf blockiert. Der Mensch setzt die Spule ein, setzt Loaded material (eingelegtes Material) auf der Arbeitsplatzseite und tippt auf Ready (bereit). Dann start; drucken; complete mit der Menge und dem tatsächlich verbrauchten Filament; status wieder frei. Ein fehlgeschlagener Druck ist scrap, kein Abschluss mit null. Das MRP weiß nichts über Drucker.

Schreibzugriffe über die API werden dem Benutzer zugeschrieben, der den Schlüssel angelegt hat, sofern X-On-Behalf-Of niemand anderen nennt (siehe oben).

OAuth

Für eine Anwendung, die für Benutzer beliebiger Firmen handelt. Anwendungen werden von Foudra registriert (fragen Sie über den Support): Sie erhalten eine client_id, ein Geheimnis für eine serverseitige Anwendung, und Ihre exakten Redirect-URIs werden hinterlegt. Der Ablauf ist der Authorization Code Grant mit PKCE (RFC 6749, RFC 7636), und PKCE mit S256 ist für jeden Client Pflicht.

  1. Leiten Sie den Browser zu https://foudra.com/oauth/authorize?response_type=code&client_id=…&redirect_uri=…&scope=read%20write&state=…&code_challenge=…&code_challenge_method=S256. Der Benutzer gibt an, welche Firma, meldet sich dort an und sieht, was Sie angefordert haben.
  2. Bei Allow (zulassen) kommt der Browser zurück zu redirect_uri?code=…&state=…; bei Deny (ablehnen) zu ?error=access_denied. Ein Code gilt 10 Minuten und funktioniert einmal.
  3. POST https://foudra.com/oauth/token, formularkodiert, Client-Authentifizierung per HTTP Basic (ein öffentlicher Client sendet stattdessen client_id im Formular): grant_type=authorization_code&code=…&redirect_uri=…&code_verifier=…. Die Antwort:
{"access_token": "fdo_…", "token_type": "Bearer", "expires_in": 3600,
 "refresh_token": "fdor_…", "scope": "read write",
 "company": {"slug": "acme", "name": "Acme Widgets", "api_base_url": "https://acme.foudra.com"}}

Rufen Sie die API unter api_base_url mit Authorization: Bearer fdo_… auf: Das Token funktioniert nur dort. Ein abgelaufenes oder widerrufenes Token erhält 401 mit WWW-Authenticate: Bearer error="invalid_token".

  1. Erneuern Sie vor oder nach Ablauf der Stunde mit grant_type=refresh_token&refresh_token=fdor_…. Refresh-Token rotieren: Jede Verwendung liefert ein neues und verbraucht das alte. Wird ein verbrauchtes Refresh-Token vorgelegt, gilt das als Diebstahl und widerruft jedes Token dieser Anmeldung; der Benutzer muss Sie erneut erlauben. Speichern Sie daher das neue Refresh-Token, bevor Sie das neue Zugriffstoken verwenden, und wiederholen Sie eine Erneuerung nie mit dem alten. Ein 90 Tage lang nicht verwendetes Refresh-Token läuft ab.
  2. POST https://foudra.com/oauth/revoke mit token=… (RFC 7009), wenn der Benutzer auf Ihrer Seite trennt. Das Widerrufen eines Refresh-Tokens widerruft auch seine Zugriffstoken.

Scopes sind die Scopes der API-Schlüssel: read, write, approve, wobei jeder den vorherigen einschließt. Was Sie erhalten, ist durch Ihre Registrierung und die Rolle des Benutzers begrenzt (Betrachter und Bediener read; Planer write; Administratoren und Inhaber approve), und die Begrenzung wird bei jeder Anfrage erneut angewendet, sodass ein Benutzer, der herabgestuft oder entfernt wird, Ihren Zugriff mitnimmt. Das Feld scope der Token-Antwort sagt, was gewährt wurde. Schreibzugriffe werden dem Benutzer zugeschrieben, und Aktionen melden "acted_as": {"user": …, "application": …}. X-On-Behalf-Of funktioniert wie bei Schlüsseln.

Fehler des Token-Endpunkts folgen RFC 6749: {"error": "invalid_grant", "error_description": "…"} mit Status 400 oder invalid_client mit 401.