Zum Inhalt

API-Referenz

Diese Referenz richtet sich an Integratoren. Alle Endpunkte liegen unter

https://api.businesscentral.dynamics.com/v2.0/{tenant}/{environment}/api/altenbrand/mcIntegration/v1.0/companies({companyId})/
Endpunkt Methode Zweck
labelRequests POST, GET Etikett anfordern, Ergebnis lesen
shipmentLabels GET Etiketten lesen (ohne Dokumente, ohne Adressen)
shipmentLabels({id})/Microsoft.NAV.cancel POST Sendung beim Carrier stornieren
shipmentLabels({id})/Microsoft.NAV.refreshTracking POST Zustellstatus beim Carrier abfragen
shipmentLabelDocuments({id}) GET Label, Retourenlabel, Zolldokument, COD-Label als Base64
shipmentTrackingEvents GET Sendungsverlauf lesen: jede Erfassung des Zustellers
shipmentPackages GET Pakete einer Mehrkolli-Sendung mit Sendungsnummer je Karton
shipmentProviders GET Versanddienstleister und ihre Fähigkeiten lesen
shipmentProviders({id})/Microsoft.NAV.getProducts POST Produkte des Dienstleisters lesen
shipmentProviders({id})/Microsoft.NAV.getLabelArchive POST Paketscheine und Zolldokumente vieler Aufträge als ZIP
shipmentProviders({id})/Microsoft.NAV.closeOutDay POST Tagesabschluss beim Zusteller samt Begleitdokument
shipmentProviders({id})/Microsoft.NAV.getManifestDocument POST Begleitdokument eines früheren Tagesabschlusses
shipmentProviders({id})/Microsoft.NAV.validateSalesOrder POST Beleg vorab prüfen, ohne Etikett und ohne Carrier-Aufruf

Anfrage statt Direktschreiben

Etiketten werden nie über die API eingefügt oder geändert. Der Client legt eine Anfrage an; die App prüft sie, erstellt das Etikett über den Hub und schreibt das Ergebnis auf die Anfrage zurück. Das schützt Lizenzprüfung, Versandregeln und Tracking vor Umgehung.


Etikett anfordern

POST …/labelRequests
Content-Type: application/json

{
  "externalReference": "WMS-2026-000481",
  "source": "WMS",
  "salesOrderNo": "VA-2026-01187",
  "providerCode": "DHL",
  "weightGrams": 1850,
  "mode": "Sync",
  "servicesJson": "[{\"code\":\"AGE_CHECK\",\"parameter1\":\"A18\"},{\"code\":\"INSURANCE\",\"parameter1\":\"2500.00\",\"parameter2\":\"EUR\"}]"
}
{
  "id": "5f0c…",
  "entryNo": 42,
  "status": "Completed",
  "shipmentLabelId": "b71e…",
  "shipmentLabelEntryNo": 10342,
  "trackingNo": "00340434161094015902",
  "errorMessage": ""
}

Felder der Anfrage

Feld Pflicht Bedeutung
externalReference ja Referenz des aufrufenden Systems, zusammen mit source eindeutig
source empfohlen Kennung des Systems, z. B. WMS oder SHOP
salesOrderNo / salesShipmentNo genau eines Verkaufsauftrag oder gebuchte Verkaufslieferung
providerCode nein Versanddienstleister; leer = Versandart des Belegs, Standarddienstleister oder der einzige aktivierte
labelType nein Outbound (Standard) oder Return für ein Retourenetikett
weightGrams nein Paketgewicht; 0 = berechnetes Beleggewicht
productCode nein Zustellerservicecode, wählt das Carrier-Produkt
mode nein Sync oder Async; leer = Standardmodus der Einrichtung
servicesJson nein JSON-Array mit Zusatzleistungen, siehe unten
packagesJson nein JSON-Array der Pakete einer Mehrkolli-Sendung, siehe unten. Nur senden; gelesen werden die Pakete über shipmentPackages
customsJson nein Zollinhaltserklärung, siehe unten. Nur senden
shipToName … shipToPhone nein Ersetzen die Lieferadresse des Belegs
lengthCm, widthCm, heightCm nein Paketmaße

Retourenetikett

POST …/labelRequests

{
  "externalReference": "SHOP-RET-4711",
  "source": "SHOP",
  "salesOrderNo": "VA-2026-01187",
  "providerCode": "DHL",
  "labelType": "Return"
}

Ein Retourenetikett entsteht allein aus dem Beleg. Gewicht, Produktcode, Zusatzleistungen, Maße und abweichende Lieferadresse werden nicht übernommen, sondern mit einem Fehler abgewiesen: Ein stillschweigend ignoriertes Feld wäre schlimmer als eine klare Absage. Die Sperre „ein offenes Etikett je Beleg" greift nicht, denn ein Retourenetikett gehört zu einem Beleg, der sein Hinsendungsetikett bereits hat. Der Dienstleister muss Retouren unterstützen; das steht in supportsReturnLabels des Endpunkts shipmentProviders. Die Retourensendungsnummer kommt in trackingNo zurück, das Dokument als returnLabelBase64.

Zusatzleistungen

servicesJson ist ein JSON-Array aus Objekten mit code, parameter1 und parameter2. Die Codes sind carrier-spezifisch und stammen aus dem jeweiligen Connector. Ein Connector wertet das Feld nur aus, wenn er das Hub-Event OnApplyLabelServiceSelections abonniert. Tut er das nicht, wird servicesJson stillschweigend ignoriert: Die Anfrage endet mit Completed, das Etikett trägt die Leistung aber nicht.

Zusteller providerCode Wertet servicesJson aus Codes
DHL Paket DHL ja 17 Codes
DPD (Webservice) DPD ja 6 Codes
DPD Austria DPDAT ja 2 Codes
Rhenus RHENUS ja 2 Codes: Fix-Termin, Avise
GLS GLS ja 8 Codes
UPS UPS ja 5 Codes
Hermes HERMES nein keine Leistungen je Etikett
DHL Express DHLX nein keine Leistungen je Etikett

Parameter sind auf je 50 Zeichen begrenzt. Ein Code, den der Connector nicht kennt, ein fehlender Pflichtparameter oder ein Parameter im falschen Format beenden die Anfrage mit einem Fehler; es entsteht kein Etikett mit stillschweigend fehlender Leistung.

Beträge: DHL, GLS und UPS erwarten den Punkt als Dezimaltrenner (2500.00). DPD und DPD Austria lesen den Betrag im Zahlenformat der Sitzung, also in der Sprache der Microsoft-Entra-Anwendung. Legen Sie die Anwendung mit Sprache Englisch an und senden Sie 2500.00; in einer deutschen Sitzung würde 2500.00 als 250000 gelesen.

Mehrkolli-Sendungen

Reist eine Sendung in mehreren Kartons, beschreibt packagesJson die Kartons. Der Zusteller vergibt dann eine Sendungsnummer je Karton.

POST …/labelRequests

{
  "externalReference": "WMS-2026-000481",
  "source": "WMS",
  "salesOrderNo": "VA-2026-01187",
  "providerCode": "DHL",
  "packagesJson": "[{\"weightGrams\":2500,\"lengthCm\":40,\"widthCm\":30,\"heightCm\":20,\"packageContent\":\"Bücher\",\"referenceNo\":\"WMS-4711-1\",\"lines\":[{\"salesLineNo\":10000,\"itemNo\":\"1000\",\"quantity\":2}]},{\"weightGrams\":1800}]"
}
Feld je Paket Pflicht Bedeutung
weightGrams ja Gewicht dieses Kartons
lengthCm, widthCm, heightCm nein Maße dieses Kartons
packagingMaterialCode nein Verpackungsmaterial
packageContent nein Inhaltsbeschreibung
referenceNo nein Referenz des aufrufenden Systems für diesen Karton
lines nein Was in diesem Karton liegt: salesLineNo, itemNo, quantity

Die Reihenfolge im Array ist die Paketnummer: der erste Eintrag wird Paket 1.

packagesJson ist ein Sendefeld. Beim Lesen einer Anfrage kommt es leer zurück; das Ergebnis steht in shipmentPackages, mit der Sendungsnummer je Karton.

Gewicht und Maße gehören in die Pakete

Bei einer Mehrkolli-Anfrage leitet der Hub Gewicht und Maße der Sendung aus den Paketen ab. weightGrams, lengthCm, widthCm und heightCm der Anfrage selbst werden deshalb abgewiesen, statt stillschweigend überschrieben zu werden. Ein Retourenetikett nimmt keine Pakete entgegen.

Der Dienstleister muss Mehrkolli unterstützen; das steht in supportsMultiColli des Endpunkts shipmentProviders. Andernfalls endet die Anfrage mit status = Error und einem lesbaren Hinweis.

Pakete lesen

GET …/shipmentPackages?$filter=shipmentLabelEntryNo eq 801

Felder: id, entryNo, shipmentLabelEntryNo, packageNo, providerCode, salesOrderNo, salesShipmentNo, trackingNo, trackingUrl, shipmentNo, weightGrams, lengthCm, widthCm, heightCm, packagingMaterialCode, packageContent, referenceNo, isReturn, createdDateTime.

shipmentLabels zeigt die Sendung als Ganzes; die Sendungsnummer je Karton steht nur hier. Der Sendungsverlauf führt seine Ereignisse ebenfalls je Karton, über trackingNo.

Zolldaten

Bei Sendungen ins Drittland baut merchantCENTRAL die Zollinhaltserklärung aus dem Beleg. Führt das aufrufende System seine Zolldaten selbst, oder hält der Beleg keine, bringt es sie mit:

POST …/labelRequests

{
  "externalReference": "SHOP-2026-0815",
  "salesOrderNo": "VA-2026-01187",
  "providerCode": "DHL",
  "shipToCountryCode": "CH",
  "customsJson": "{\"exportType\":\"Commercial Goods\",\"exportDescription\":\"Bücher\",\"postalChargesAmount\":12.5,\"postalChargesCurrency\":\"EUR\",\"items\":[{\"itemNo\":\"1000\",\"description\":\"Buch\",\"countryOfOriginCode\":\"DE\",\"hsCode\":\"49019900\",\"quantity\":2,\"unitPrice\":19.99,\"currencyCode\":\"EUR\",\"netWeightGrams\":500,\"grossWeightGrams\":550}]}"
}
Feld im Kopf Bedeutung
exportType Other, Present, Commercial Sample, Document, Return of Goods, Commercial Goods
exportDescription Beschreibung der Ausfuhr
postalChargesAmount, postalChargesCurrency Portokosten für die Zollabfertigung
permitNo, attestationNo, mrn Genehmigung, Bescheinigung, Ausfuhrvorgangsnummer
shipperCustomsRef, consigneeCustomsRef Zollreferenzen von Versender und Empfänger
electronicExportNotification Elektronische Ausfuhranmeldung
Feld je Position Pflicht Bedeutung
description ja Warenbezeichnung
quantity ja Menge
itemNo nein Artikelnummer
countryOfOriginCode nein Ursprungsland
hsCode nein Zolltarifnummer
unitPrice, currencyCode nein Einzelpreis und Währung; der Positionsbetrag wird gerechnet
netWeightGrams, grossWeightGrams nein Netto- und Bruttogewicht

Ersetzen, nicht mischen

Übergebene Positionen ersetzen die aus dem Beleg abgeleiteten. Eine Mischung aus beiden wäre eine Erklärung, für die niemand geradesteht. Kopffelder, die Sie leer lassen, behalten dagegen den Wert aus dem Beleg: Ein leeres Feld heißt „unverändert", nicht „löschen".

Ein Retourenetikett nimmt keine Zollinhaltserklärung entgegen. Eine Position ohne Beschreibung oder ohne Menge wird abgewiesen, statt eine unvollständige Erklärung an den Zusteller zu geben.

Verhalten

  • Sync antwortet mit dem fertigen Ergebnis, typisch ein bis drei Sekunden.
  • Async antwortet sofort mit status = Pending; der Client liest die Anfrage später per GET …/labelRequests({id}) oder ?$filter=externalReference eq '…'.
  • Eingabefehler (fehlende Referenz, unbekannter Beleg, ungültiges JSON, doppelte Referenz) geben HTTP 400. Die Anfrage wird nicht gespeichert.
  • Schutzmechanismen (Modul nicht aktiv, Dienstleister nicht ermittelbar, Etikett vorhanden, Stundenlimit) speichern die Anfrage mit status = Error und einem lesbaren errorMessage.
  • Carrier-Fehler speichern die Anfrage mit status = Error; errorMessage enthält die Kernmeldung des Carriers, shipmentLabelEntryNo verweist auf das fehlgeschlagene Etikett.

Statuswerte

Status Bedeutung
Pending Wartet auf die Aufgabenwarteschlange
Processing Wird gerade verarbeitet
Completed Etikett erstellt, trackingNo gefüllt
Error Abgewiesen oder beim Carrier gescheitert, siehe errorMessage
Cancelled Vor der Verarbeitung storniert

Etiketten lesen

GET …/shipmentLabels?$filter=salesOrderNo eq 'VA-2026-01187'
GET …/shipmentLabels({id})

Felder: id, entryNo, providerCode, salesOrderNo, salesShipmentNo, status, shipmentNo, trackingNo, trackingUrl, labelFormat, weightGrams, productCode, referenceNo, deliveryStatus, lastTrackingUpdate, createdDateTime, cancelledDateTime, lastExportedDateTime, errorMessage.

Dokumente

GET …/shipmentLabelDocuments({id})

Liefert labelBase64, returnLabelBase64, customsDocBase64 und codLabelBase64. Das Format steht in labelFormat (PDF oder ZPL). Nur in Business Central gespeicherte Dokumente werden geliefert; ein nie gespeichertes Etikett kommt leer zurück.

Aktionen

POST …/shipmentLabels({id})/Microsoft.NAV.cancel
POST …/shipmentLabels({id})/Microsoft.NAV.refreshTracking

cancel storniert beim Carrier und antwortet mit true. refreshTracking fragt den Carrier ab und antwortet mit dem neuen Zustellstatus als Text. Beide Aktionen prüfen, dass das Modul aktiv und lizenziert ist.

Sendungsverfolgung nicht im Takt abfragen

Die Zusteller gewähren je Schlüssel nur eine begrenzte Zahl von Abrufen pro Tag, DHL zum Beispiel 250. Aus demselben Kontingent bedient sich auch die geplante Sendungsverfolgung des Connectors. refreshTracking ist deshalb gedrosselt: Max. Sendungsverfolgungs-Abrufe pro Stunde in der Einrichtung, Standard 60, 0 schaltet die Begrenzung ab. Ist das Zeitfenster ausgeschöpft, antwortet die Aktion mit einem Fehler, ohne den Carrier zu fragen.

Für laufende Statusänderungen ist das Business Event DeliveryStatusChanged der richtige Weg. Es meldet jede Änderung von sich aus und kostet keinen einzigen Abruf.


Dienstleister, Produkte und Vorabprüfung

GET …/shipmentProviders
GET …/shipmentProviders?$filter=enabled eq true

Felder: id, code, name, enabled, status, shippingAgentCode, defaultServiceCode, defaultLabelFormat, supportsReturnLabels, supportsCancellation, supportsTracking, supportsCustoms, supportsMultiColli, supportsManifesting, moduleCode, connectorVersion.

Damit beantwortet ein externes System selbst, welche Dienstleister es ansprechen darf und was jeder von ihnen kann, statt Codes fest zu verdrahten.

Produkte

POST …/shipmentProviders({id})/Microsoft.NAV.getProducts

Liefert [{"code":"V01PAK","name":"DHL Paket"}, …]. Ein Code daraus passt in das Feld productCode einer Anfrage. Damit muss ein externes System keine Produktcodes fest verdrahten.

Die Codes der Zusatzleistungen für servicesJson stehen weiterhin in der Dokumentation des jeweiligen Connectors, siehe die Tabelle unter Zusatzleistungen.

Sammel-Archiv

Sollen mehrere Aufträge auf einmal versendet werden, holt ein Aufruf alle Belege als eine ZIP-Datei:

POST …/shipmentProviders({id})/Microsoft.NAV.getLabelArchive
{
  "salesOrderNosJson": "[\"VA-2026-01187\",\"VA-2026-01188\"]",
  "onlyNotExported": true,
  "markAsExported": true
}
{
  "documentCount": 5,
  "skippedCount": 0,
  "markedCount": 4,
  "fileName": "ShipmentLabels_20260909_144311.zip",
  "archiveBase64": "UEsDBBQ…"
}

Im Archiv liegen je Sendung Paketschein, Retourenetikett, Nachnahmeetikett, Zolldokument und QR-Code, bei Mehrkolli ein Paketschein je Karton. Die Dateinamen tragen die Sendungsnummer.

Feld Bedeutung
documentCount Anzahl der Dokumente im Archiv
skippedCount Sendungen ohne hinterlegtes Dokument; kein Fehler, nur gezählt
markedCount Sendungen, die als abgeholt gekennzeichnet wurden
fileName Vorschlag für den Dateinamen mit Zeitstempel
archiveBase64 Die ZIP-Datei, Base64-kodiert; leer, wenn nichts zusammenkam

Berücksichtigt werden nur Sendungen mit Status Erstellt oder Manifestiert und nur die des angesprochenen Dienstleisters. Eine doppelt genannte Auftragsnummer zählt einmal.

Blockweise abholen

Schalter Wirkung
onlyNotExported Nimmt nur Sendungen, deren Belege noch nie herausgegeben wurden
markAsExported Kennzeichnet die Sendungen des fertigen Archivs als abgeholt

Beide zusammen ergeben den üblichen Ablauf: Jeder Lauf holt, was seit dem letzten Mal dazugekommen ist. Der zweite Aufruf mit derselben Auftragsliste liefert dann ein leeres Ergebnis.

Gekennzeichnet wird erst, wenn das Archiv steht. Ein Lauf, der nichts herausgibt, behauptet nicht, die Sendungen seien abgeholt.

Die Kennzeichnung ist ein Filter, keine Sperre: Ohne onlyNotExported kommen auch bereits abgeholte Belege wieder. Wann eine Sendung zuletzt herausgegeben wurde, steht als lastExportedDateTime am Endpunkt shipmentLabels.

Höchstens hundert Aufträge je Aufruf

Base64 vergrößert die Antwort um ein Drittel. Über hundert Aufträgen bricht der Aufruf mit einer lesbaren Meldung ab, statt die Liste stillschweigend zu kürzen. Fordern Sie größere Läufe in Blöcken an.

Vorabprüfung

POST …/shipmentProviders({id})/Microsoft.NAV.validateSalesOrder
{ "salesOrderNo": "VA-2026-01187" }

POST …/shipmentProviders({id})/Microsoft.NAV.validateSalesShipment
{ "salesShipmentNo": "VL-2026-00912" }

Antwort:

{ "valid": false, "errors": ["Ship-to postal code is missing.", "The parcel weight exceeds 31.5 kg."] }

Die Prüfung erstellt nichts und ruft den Carrier nicht. Sie eignet sich, um einen Beleg im Vorfeld zu prüfen, etwa beim Packen: Fehlt etwas, meldet es das aufrufende System sofort, statt später ein fehlgeschlagenes Etikett zu hinterlassen.


Tagesabschluss

Am Ende des Versandtages werden die erstellten Sendungen an den Zusteller übergeben. Ein Aufruf erledigt das und bringt das Begleitdokument gleich mit:

POST …/shipmentProviders({id})/Microsoft.NAV.closeOutDay
{}
{
  "manifestedCount": 29,
  "manifestedAt": "2026-09-09T15:14:04Z",
  "documentAvailable": true,
  "documentBase64": "JVBERi0…"
}
Feld Bedeutung
manifestedCount Übergebene Sendungen
manifestedAt Zeitpunkt des Abschlusses; leer, wenn nichts übergeben wurde
documentAvailable Ob ein Begleitdokument geliefert wurde
documentBase64 Das Dokument, Base64-kodiert

Übergeben werden alle Sendungen des Dienstleisters mit Status Erstellt; danach stehen sie auf Manifestiert.

Ein ruhiger Tag ist kein Fehler

Ist nichts offen, antwortet der Aufruf mit manifestedCount = 0. Ein System, das nach Plan abschließt, muss auch an einem Tag ohne Sendungen laufen können, ohne eine Fehlermeldung zu verarbeiten.

Kein Dokument ist auch kein Fehler

Manche Zusteller liefern kein Begleitdokument, und in Testumgebungen fehlt es häufig. Der Abschluss selbst hat dann trotzdem stattgefunden: documentAvailable ist false, die Sendungen sind übergeben.

Das Dokument eines früheren Tages holt

POST …/shipmentProviders({id})/Microsoft.NAV.getManifestDocument
{ "manifestDate": "2026-09-09" }

Der Dienstleister muss den Tagesabschluss unterstützen; das steht in supportsManifesting des Endpunkts shipmentProviders.


Sendungsverlauf

GET …/shipmentTrackingEvents?$filter=shipmentLabelEntryNo eq 801
GET …/shipmentTrackingEvents?$filter=trackingNo eq '00340434161094015902'

Wo shipmentLabels sagt, wo ein Paket steht, sagt dieser Endpunkt, wie es dorthin kam. Sortiert nach Zeitpunkt, das neueste Ereignis zuerst.

Felder: id, entryNo, shipmentLabelEntryNo, providerCode, trackingNo, eventDateTime, deliveryStatus, eventCode, description, location, countryCode, signedBy, recordedDateTime.

{
  "entryNo": 4711,
  "shipmentLabelEntryNo": 801,
  "providerCode": "DHL",
  "trackingNo": "00340434161094015902",
  "eventDateTime": "2026-09-09T07:12:00Z",
  "deliveryStatus": "Delivered",
  "eventCode": "DELIVERY",
  "description": "Die Sendung wurde zugestellt",
  "location": "Bonn",
  "countryCode": "DE",
  "signedBy": "M. Musterfrau"
}

deliveryStatus ist zustellerneutral und verwendet dieselbe Skala wie shipmentLabels. eventCode ist der Code des Zustellers, für Rückfragen bei ihm. Er wird in Großbuchstaben gespeichert; vergleichen Sie ihn ohne Rücksicht auf Groß- und Kleinschreibung.

Diesen Endpunkt abfragen, nicht refreshTracking

Der Verlauf entsteht, wenn die Sendungsverfolgung des Carrier-Moduls ohnehin läuft. Ihn zu lesen kostet keinen Aufruf beim Zusteller und unterliegt keiner Drosselung. refreshTracking fragt dagegen den Zusteller und ist begrenzt.

Nur, was der Zusteller meldet

Ein Verlauf ist so vollständig wie die Antwort des Zustellers. Ist die Sendungsverfolgung eines Dienstleisters nicht eingerichtet, bleibt die Liste leer; das ist kein Fehler. Ob ein Dienstleister Sendungsverfolgung unterstützt, steht in supportsTracking des Endpunkts shipmentProviders.

Der Verlauf wird ausschließlich von den Carrier-Modulen geschrieben. Über die API ist er schreibgeschützt.


Business Events

Für Power Automate und Dataverse stellt die App drei Business Events in der Kategorie merchantCENTRAL Versand bereit. Die Nutzlast enthält nur Kennungen, Nummern und Status; wer Adressen braucht, liest sie mit den Rechten seiner Verbindung über die API.

Event Nutzlast
ShipmentLabelCreated labelId, labelEntryNo, providerCode, salesOrderNo, salesShipmentNo, trackingNo
ShipmentLabelFailed labelId, labelEntryNo, providerCode, salesOrderNo, salesShipmentNo, errorMessage
DeliveryStatusChanged labelId, labelEntryNo, providerCode, trackingNo, oldStatus, newStatus

Die Events werden nur ausgelöst, solange das Modul aktiv und lizenziert ist.


Berechtigungen

Berechtigungssatz Für wen
Integration API - Client Die Entra-Anwendung des externen Systems. Anfragen anlegen und lesen, Etiketten und Belege lesen, API-Pages ausführen. Dazu D365 BASIC, D365 READ, merchantCENTRAL User und der User-Satz des jeweiligen Carriers, siehe Einrichtung.
Integration API - User Versandmitarbeiter: Anfragen überwachen, wiederholen, stornieren.
Integration API - Admin Einrichtung, Assistent, Aufgabenwarteschlange.