Zum Inhalt

Übersicht

NoyesStorage ist so konzipiert, dass er eigenständig mittels App betrieben werden kann, ohne eine Integration zu erfordern. Soll der NoyesStorage automatisiert mit anderen Systemen kommunizieren, steht eine Schnittstelle (engl. Application Programming Interface, API) zur Verfügung. Dieser Abschnitt bietet eine Schnellstartanleitung zur Interaktion mit NoyesStorage über seine API und ermöglicht so die Integration von NoyesStorage in Anwendungsfälle wie:

  • die Übergabe von Bestelldaten aus einem Online-Shop an NoyesStorage,
  • das automatische Drucken von Rechnungen, wenn Kundenaufträge kommissioniert werden, und
  • die Synchronisation des ERP-Systems mit den Lagerbeständen in NoyesStorage.

Die NoyesStorage-API basiert auf REST, verwendet das JSON-Datenformat für Anfragen und Antworten und nutzt gängige HTTP-Methoden (siehe Tabelle unten), um eine konsistente und vorhersehbare Schnittstelle bereitzustellen. Integrationen nutzen die öffentliche REST-API unter /api/v2/… (OpenAPI unter /api/openapi.json), z. B. POST /api/v2/requests für Abruf-, Kommissionier- und Nachschubaufträge.

Die API, ihre Endpunkte und Parameter sind ausführlich online dokumentiert unter
docs.noyes-tech.com.

HTTP Kommunikation in der NoyesStorage API

Methode Erklärung
POST Create new resources or send data to NoyesStorage (e.g., submitting a fulfillment request). Example: /api/v2/requests
GET Retrieve data from NoyesStorage (e.g., checking the status of requests). Example: /api/v2/requests
PUT Update existing resources.
DELETE Remove resources from NoyesStorage.

Standard HTTP Antworten und Status der NoyesStorage API

Status Erklärung
200 OK The request succeeded.
201 Created A new resource was successfully created.
202 Accepted The request has been accepted but is being processed asynchronously.
400 Bad Request The request had a syntax error that could not be resolved.
401 Unauthorized Authentication is required.
404 Not Found The requested resource does not exist.
500 Internal Server Error The server encountered an error.

Um die Schnittstelle vor böswilligen Nutzern zu schützen, benötigen alle Endpunkte eine Authentifizierung über einen API-Schlüssel. Jede Anfrage muss diesen API-Schlüssel im Header enthalten, andernfalls wird die Anfrage abgelehnt. Zusätzlich werden nur Anfragen akzeptiert, die über HTTPS gesendet werden. Dies stellt sicher, dass die Daten vor der Übertragung verschlüsselt und nur von der NoyesStorage-API entschlüsselt werden können. Anfragen, die über unverschlüsseltes HTTP oder ohne gültigen API-Schlüssel gesendet werden, schlagen fehl.

API-Key verwalten

Hinweis

Nur verfügbar für Admin-Nutzer.

  • Öffnen Sie in der NoyesStorage App das Menü Einstellungen und wählen Sie API-Key.
  • Bestehende Schlüssel können Sie dort einsehen und kopieren; erstellen Sie bei Bedarf einen neuen Schlüssel für Integrationen.
  • Bewahren Sie den Schlüssel sicher auf und teilen Sie ihn nur mit autorisierten Systemen.

API-Key-Verwaltung in der UI

Authentifizierung und API-Key

Produktive Integrationen authentifizieren jede Anfrage mit einem API-Key, der in der NoyesStorage App ausgegeben wurde. Senden Sie den Schlüssel als Bearer Token im HTTP-Header Authorization:

Authorization: Bearer <api-key>

Behandeln Sie API-Keys wie Passwörter: nicht in Quellcode einchecken, nur in Secret Stores oder geschützten Umgebungsvariablen speichern und bei Verdacht auf Weitergabe in der App löschen und neu erstellen.

Filtern, Suchen, Sortieren und Pagination

Listen-Endpunkte wie GET /api/v2/skus, GET /api/v2/inventory_view, GET /api/v2/requests, GET /api/v2/jobs und GET /api/v2/events unterstützen je nach Ressource Filterparameter, Suche, Sortierung und Pagination.

Filter verwenden das Schema field__operator=value. Häufige Operatoren sind __eq für exakte Treffer, __gte und __lte für Bereiche sowie __ilike für teilweise, groß-/kleinschreibungsunabhängige Treffer. Bei __in wird der Query-Parameter pro Wert wiederholt:

GET /api/v2/inventory_view?level_id__in=1&level_id__in=2

Kommagetrennte Listen wie ?level_id__in=1,2 werden nicht als mehrere Werte interpretiert.

Der Parameter search nutzt field:value-Ausdrücke. Die Suche ist teilweise und groß-/kleinschreibungsunabhängig. Die unterstützten Suchfelder sind endpunktspezifisch und in der search-Parameterbeschreibung des jeweiligen Endpunkts aufgeführt:

GET /api/v2/skus?search=id:tee

Mehrere Suchbegriffe können mit den großgeschriebenen Operatoren AND und OR kombiniert werden. Eine Ebene von Klammern wird unterstützt:

GET /api/v2/inventory_view?search=sku_id:tee AND (box_id:2 OR carrier_id:2)

Mit *:value wird über alle für den jeweiligen Endpunkt unterstützten Suchfelder gesucht:

GET /api/v2/inventory_view?search=*:tee

Der Parameter sort_by akzeptiert kommagetrennte Feldnamen. Ein Feld ohne Vorzeichen oder mit + sortiert aufsteigend, - sortiert absteigend:

GET /api/v2/requests?sort_by=created_at,-updated_at

Pagination verwendet page und size. Die Seitennummerierung beginnt bei page=1; ohne abweichende Anfrage liefert die API standardmäßig page=1 und size=50:

GET /api/v2/skus?page=1&size=50

Funktionsweise der Anfrageausführung

Das NoyesStorage-System arbeitet mit einer Warteschlange. Ein spezifisches Arbeitspaket kann durch das Senden einer Anfrage angefordert werden. Intern wird diese Anfrage in mehrere Jobs aufgeteilt, die nacheinander ausgeführt werden. Sowohl Anfragen als auch Jobs folgen einem vordefinierten Lebenszyklus, der durch ihren Status ausgedrückt wird.

Requests

Anfragen (engl. Requests) können an den NoyesStorage gesendet werden. Diese Anfrage wird dann intern in eine Reihe von Jobs übersetzt. Die folgende Tabelle listet die verfügbaren Anfragen und deren Beschreibungen auf. Über die API können Fetch, Fulfillment und Replenishment Anfragen an den NoyesStorage gesendet werden, um die Aus- und Einlagerung automatisiert abzuwickeln.

Requests im NoyesStorage

Request Description
FULFILLMENT A Logistic Request consisting of Pick-Entities (ProductEntity).
REPLENISHMENT A Logistic Request consisting of Refill-Entities (ProductEntity).
FETCH A Logistic Request consisting of Fetch-Carrier-Entities.
RFID_MAINTENANCE A System Control Request to write and/or check RFID Tags on a specified Level.
ONBOARD A System Control Request to add a Noyes Bot to any Level.
OFFBOARD A System Control Request to remove a specific Noyes Bot from a Level.
PAUSE A System Control Request to remove a specific Noyes Bot from a Level.
RESUME A System Control Request to allow all Bots to get new commands after a PAUSE Request.
CHARGING A System Control Request to move a Noyes Bot to a Charging Station.
UNCHARGING A System Control Request to remove a Noyes Bot from a Charging Station.
UPDATE_CONTENT_CODE An Inventory Update Request to update the content code of a carrier.
RECHARGE A System internal Request sent from the Charging Manager to the Store Orchestrator.
LEVEL_INITIALIZATION An internal Request Type used for initializing each Level of a Storage by connecting to the bots, updating the Database, and clearing the Highway.

Jobs

Jobs sind die einzelnen Aufgaben, die im System ausgeführt werden. Jede Anfrage kann mehrere Jobs generieren, die jeweils einem vordefinierten Lebenszyklus folgen. Die folgende Tabelle liefert einen Überblick über die im NoyesStorage verwendeten Jobs.

Jobs im NoyesStorage

Job Description
BUFFERING Moves a Carrier to a Buffer Space on the same Level.
RETRIEVING Moves a Carrier to a Balcony on the same Level.
STORING Moves a Carrier from the Balcony.
PICKING Lets the user pick a given amount out of a Box.
PICKING_ALERT Lets the user handle fulfillment quantities outside the NoyesStorage when the external picking feature is enabled.
REFILLING Lets the user refill a given amount into a Box.
CHECKING Lets the user check a given Carrier Label.
PACKING Lets the user check the summary of a Request after all Entities have been worked on.
ONBOARDING Lets the user onboard a Bot to any level.
ONBOARDING_BD Adds a Bot to the Database and informs all Brain Components about it.
OFFBOARDING Moves a Bot to the Balcony.
OFFBOARDING_BD Lets the user remove a Bot from the Balcony.
OFFBOARDING_BC Moves a Bot to the Balcony.
PAUSE Ensures no Bot gets new commands.
RESUME Allows all Bots to get new commands after a PAUSE.
MOVING_BOT Moves a Bot to a target location/rotation/lifting state.
CONNECT_TO_BOTS Establishes a connection to all Bots that are in the Database.
REFRESH_LEVEL Updates the digital twin in the Bot Coordinator to the Database.
UNCHARGE_ALL Removes all Bots from the Charging Station.
CLEAR_AUTOBAHN Removes all Carriers from the Highway.
CHARGING Moves a given bot to a Charging Station.
UNCHARGING Removes a given bot from the Charging Station.
CHECK_RFID_TAG Checks the values written on an RFID Tag at the given Location and Rotation.
WRITE_RFID_TAG Writes values to an RFID Tag at the given Location and Rotation.
CHANGE_RFID_WRITING_MODE Changes whether or not a bot should ignore the values on an RFID Tag while driving.

Hinweis zu PICKING_ALERT

PICKING_ALERT erscheint nur, wenn die externe Kommissionierung für fehlende oder außerhalb des NoyesStorage zu bearbeitende Fulfillment-Mengen aktiviert ist. In diesem Fall können angenommene Fulfillment-Requests strukturierte Warnhinweise zu fehlenden oder extern zu bearbeitenden Mengen enthalten, und nachgelagerte Job-Daten können PICKING_ALERT-Jobs enthalten.

Job States

Jeder Job durchläuft einen Lebenszyklus, der durch seinen Status definiert ist. Die folgenden Statusübergänge sind in der Tabelle dargestellt.

Job States in the NoyesStorage System

State Description
ACCEPTED Job is ACCEPTED. These transitions are allowed: EXECUTING, ERROR, CANCELLING, ABORTED.
EXECUTING Job is EXECUTING and waiting to be triggered. These transitions are allowed: ERROR, CANCELLING, ABORTED.
SUCCEEDED Job is SUCCEEDED. No transitions are allowed. This is a final state.
CANCELLING Job is CANCELLING. A user requested this job to be cancelled. These transitions are allowed: CANCELLED, ABORTED.
CANCELLED Job is CANCELLED. A user requested this job to be cancelled. The CANCELLING was successful. This is a final state.
ABORTED Job is ABORTED due to a system problem. The system will try to recover from this. This is a final state.

Quickstart: Ein einfaches Integrationsbeispiel

Dieser Abschnitt bietet eine einfache Anleitung zur Nutzung der API und konzentriert sich auf zentrale Interaktionen über Endpunkte. Die Beispiele werden in Python demonstriert, jedoch können auch andere Sprachen wie JavaScript, Java oder C# verwendet werden.

Anforderungen

  1. Python 3.7+ (oder eine ähnliche Programmiersprache, die HTTP-Requests senden kann).
  2. API Keys für die Authentifizierung an der NoyesStorage API.

Im Folgenden finden Sie ein Beispiel in Python, das zeigt, wie die Integration mit der API von NoyesStorage – insbesondere dem Retrieval-Endpunkt – erfolgen kann. Nach der vollständigen Implementierung wird jeder Abschnitt im Detail erläutert.

import json
from typing import Dict
import time
import uuid
import requests
import logging
from threading import Timer

"""
Examples of Noyes B2B payload

v1/core/retrieval/{request_id}

{
  "priority": 10,
  "desired_time": "2024-10-08T07:50:36.064257",
  "source": "",
  "request_type": "FULFILLMENT",
  "external_id": "string",
  "total_entity_count": 0,
  "entities": [
    {
      "entity_type": "Product",
      "sku_id": "string",
      "quantity": 0
    },
    {
      "entity_type": "Carrier",
      "carrier_id": "string",
      "content_codes": [
        0
      ]
    }
  ]
}
"""

class SimpleERPIntegration():
    """ An example class that sends requests to the Noyes B2B API and 
    repeatedly checks the status of those requests until they are finished.
    """
    REQ_UPDATE_PERIOD = 10  # seconds

    def __init__(self, store_keys: Dict, b2b_url: str) -> None:
        self._request_dicts: Dict[uuid.UUID, Dict[str]] = {}
        self._store_keys = store_keys
        self.b2b_url = b2b_url

    def send_request(self, payload: Dict) -> str:
        headers = self._store_keys 
        logging.info(f'sending request {payload["external_id"]} via b2b')
        send_time = time.time()
        rsp = requests.post(self.b2b_url + 'core/retrieval', headers=headers, data=json.dumps(payload), timeout=1)
        assert rsp.status_code == 202, f'got bad response {rsp} {rsp.reason} {rsp.text}'
        logging.info(f'sent request {payload["external_id"]} request_id={rsp.json()["request_id"]} via b2b in {time.time()-send_time:.2f}')
        self.subscribe_to_updates(rsp.json()['request_id'])
        return rsp.json()['request_id']

    def subscribe_to_updates(self, request_id: uuid.UUID):
        """Creates a timer for this request which repeatedly asks the B2B for updates.
        self._check_request_callback(request_id) will be called at a rate of self.REQ_UPDATE_PERIOD.
        """
        self._request_dicts[request_id] = {}
        self._request_dicts[request_id]['timer'] = Timer(interval=self.REQ_UPDATE_PERIOD, function=lambda: self._check_request_callback(request_id))
        self._request_dicts[request_id]['last_request_update'] = ''
        self._request_dicts[request_id]['last_job_update'] = '[]'

    def unsubscribe_from_updates(self, request_id: uuid.UUID):
        """
        Cancels the timer created for this request_id in subscribe_to_updates.
        """
        if request_id in self._request_dicts.keys():
            if 'timer' in self._request_dicts[request_id].keys():
                self._request_dicts[request_id]['timer'].cancel()
            self._request_dicts[request_id] = None
            del self._request_dicts[request_id]

    def _check_request_callback(self, request_id: uuid.UUID):
        """
        Calls the Noyes B2B endpoint for the status of the specified request.
        Processes the response in _process_request_updates.
        Unsubscribes from request updates if the request is completed.
        """
        url = self.b2b_url + f'core/retrieval/{str(request_id)}'
        try:
            rsp = requests.get(url, headers=self._store_keys, timeout=0.9*self.REQ_UPDATE_PERIOD)
            assert rsp.status_code == 200, f'got bad response {rsp} {rsp.reason} {rsp.text}'
        except requests.exceptions.ReadTimeout as e:
            logging.warn(f'got error while checking requests {e}.')
            return
        request_update = json.loads(rsp.text)
        self._process_request_updates(request_update)

        if request_update['status'] in ['FAILED', 'SUCCEEDED', 'ABORTED']:
            self.unsubscribe_from_updates(request_id)

    def _process_request_updates(self, request_update):
        """
        Determines when a subscribed request has updated information and provides a placeholder
        for user-defined request update callbacks.
        """
        # Parse out different portions of the message.
        request_id = request_update['request_id']
        request_updates = {k: v for k, v in request_update.items() if k not in ['jobs', 'content_code_updates', 'last_updated']}
        dump = json.dumps(request_updates)
        if request_id not in self._request_dicts.keys():
            self.subscribe_to_updates(request_id=request_id)
            logging.warn(f'got request update for {request_id} but it wasn\'t expected, tracking now but that\'s weird')

        if dump != self._request_dicts[request_id]['last_request_update']:
            pass  # Place your request_update callback here.

        self._request_dicts[request_id]['last_request_update'] = dump

Erläuterungen zum Code

  1. Klasseninitialisierung
    Die Klasse SimpleERPIntegration verwaltet alle Interaktionen mit der NoyesStorage-API. Sie wird mit folgenden Parametern initialisiert:
  2. store_keys: Ein Dictionary, das die für die Authentifizierung erforderlichen Schlüssel enthält (z. B. store_id, tenant_id, api_key).
  3. b2b_url: Die Basis-URL für die API.

Beispiel:

erp_integration = SimpleERPIntegration(
    store_keys={'store_id': 'YOUR_STORE_ID', 
                'tenant_id': 'YOUR_TENANT_ID', 
                'api_key': 'YOUR_API_KEY'},
    b2b_url='https://demo-b2b.noyes-tech.com/'
)

  1. Anfrage senden (send_request)
    Diese Methode wird verwendet, um eine neue Entnahmeanfrage an das NoyesStorage-System zu senden.
  2. Verwendeter Endpunkt: POST /core/retrieval
  3. Die Methode erstellt ein Payload und sendet eine POST-Anfrage an den Endpunkt /core/retrieval.
  4. Bei Erfolg gibt die API den Statuscode 202 Accepted und eine request_id zurück.

Wichtige Punkte: - Der Endpunkt /core/retrieval ist die Hauptschnittstelle zur Erstellung neuer Entnahmeaufträge. - Die zurückgegebene request_id ist wichtig, um den Status der Anfrage zu verfolgen.

  1. Abonnieren von Updates (subscribe_to_updates)
    Nach dem Abschicken einer Anfrage wird eine regelmäßige Abfrage eingerichtet (alle 10 Sekunden), um den Status der Anfrage und der zugehörigen Jobs zu verfolgen.

  2. Überprüfung des Anfragestatus (_check_request_callback)
    Diese Methode prüft den Status der Anfrage, indem sie eine GET-Anfrage an den Endpunkt /core/retrieval/{request_id} sendet. Die Antwort enthält den Fortschritt (z. B. SUCCEEDED, FAILED oder ABORTED).

  3. Verarbeitung von Updates (_process_request_updates)
    Die von der API empfangenen Updates werden in dieser Methode verarbeitet. Sie protokolliert den Status und löst je nach Zustand der Anfrage erforderliche Callbacks aus. Sobald der Job abgeschlossen ist (egal ob SUCCEEDED, FAILED oder ABORTED), beendet das System die Abfrage der API nach weiteren Updates.


Mögliche Erweiterungen

Dieses einfache Integrations-Setup dient als Vorlage zur Nutzung der NoyesStorage-API und kann erweitert werden, um die Integration von NoyesStorage in Ihre Produktionsumgebung anzupassen.

Im Folgenden sind einige Beispiele aufgeführt, wie Kunden die API nutzen, um NoyesStorage in ihre Prozesse zu integrieren:

Drucken von Rechnungen für kommissionierte Anfragen

Eine Erweiterung, die von einem unserer Kunden genutzt wird, umfasst das automatische Erstellen und Drucken von Rechnungen für Anfragen, die aktuell kommissioniert werden. Diese Rechnungen werden vor dem Verpacken in die Kundenbestellung gelegt.

Im obigen Beispiel kann dies erreicht werden, indem die Methode _process_request_updates so modifiziert wird, dass der Rechnungsdruck ausgelöst wird, sobald eine Anfrage in den Status EXECUTING übergeht.

def _process_request_updates(self, request_update):
    request_id = request_update['request_id']
    request_status = request_update['status']

    if request_status == "EXECUTING":
        self.print_invoice_for_request(request_update)

    logging.info(f'Update for request {request_id}: {request_status}')

def print_invoice_for_request(self, request_update):
    invoice_data = f"Invoice for request {request_update['request_id']}\n"
    invoice_data += f"Items: {request_update['entities']}\n"
    invoice_data += f"Total Quantity: {sum(item['quantity'] for item in request_update['entities'])}\n"

    logging.info(f"Printing invoice:\n{invoice_data}")

Mit dieser Erweiterung wird eine Rechnung automatisch erstellt und protokolliert (oder gedruckt), sobald eine Anfrage in den Status EXECUTING übergeht.

Automatische Bestätigung eines Picks über einen Scanner

Eine zusätzliche Erweiterung umfasst die Verwendung eines Handscanners zur automatischen Bestätigung von Picks. Wenn der Scanner den Barcode eines Artikels liest, kann die Integration den Pick für die entsprechende Anfrage am NoyesStorage automatisch bestätigen.

Hierfür muss die Methode _process_request_updates angepasst werden, um zu erkennen, wann ein Artikel gescannt wurde. Anschließend kann eine Anfrage zur Bestätigung des Picks über den entsprechenden Endpunkt gesendet werden (z. B. POST /v1/core/requests/{request_id}/jobs/{job_id}/trigger).

def _process_request_updates(self, request_update):
    request_id = request_update['request_id']
    request_status = request_update['status']

    if request_status == "EXECUTING" and self.is_item_scanned(request_update):
        self.confirm_pick(request_id)

    logging.info(f'Update for request {request_id}: {request_status}')

def is_item_scanned(self, request_update):
    scanned_item_sku = "scanned_item_sku_example"  # Example from the scanner
    return any(item['sku_id'] == scanned_item_sku for item in request_update['entities'])

def confirm_pick(self, request_id):
    url = self.b2b_url + f'/core/retrieval/{str(request_id)}/confirm'
    try:
        rsp = requests.put(url, headers=self._store_keys)
        assert rsp.status_code == 200, f"Failed to confirm pick: {rsp.status_code}"
        logging.info(f'Pick confirmed for request {request_id}')
    except requests.exceptions.RequestException as e:
        logging.error(f'Error confirming pick: {e}')

Weitere Anwendungsfälle für eine API-Integration

  • Bestandsanpassung: Automatische Aktualisierung der Bestandsführungssysteme basierend auf den abgeschlossenen Anfragen am NoyesStorage.
  • Verfolgung von Bestellungen: Synchronisieren der NoyesStorage-Anfragen mit einem externen Bestellverfolgungssystem für Echtzeit-Updates.
  • Benachrichtigungssystem: Auslösen von SMS- oder E-Mail-Benachrichtigungen, wenn kritische Jobs abgeschlossen oder verzögert werden.
  • Automatisierte Nachbestellung: Überwachen der Lagerbestände und automatisches Erstellen von Nachschubanfragen basierend auf festgelegten Schwellenwerten.

Diese Beispiele zeigen, wie sich die Integrationsvorlage an spezifische Anforderungen anpassen lässt. Die NoyesStorage-API bietet die nötige Flexibilität, um Prozesse über Systemgrenzen hinweg zu automatisieren und zu optimieren. Dieses Beispiel verdeutlicht, wie die Integration der NoyesStorage-API eingerichtet, Anfragen gesendet und deren Status verfolgt werden kann. Die Einrichtung kann erweitert werden, um Prozesse wie die Rechnungserstellung, automatische Bestätigungen oder Nachbestellungen zu individualisieren.