Skip to content

Konventionen

Was für alle Endpunkte gilt. Wenn du das hier einmal richtig implementierst, sind die einzelnen Endpunkte unspektakulär.

.json an jede URL

Jeder Endpunkt liefert JSON, sobald die URL auf .json endet. Ohne die Endung bekommst du die HTML-Oberfläche — mit HTTP 200.

✗  POST /freights/add          →  200, text/html
✓  POST /freights/add.json     →  200, application/json

Das ist die häufigste Fehlerursache beim ersten Integrieren, und sie äußert sich als JSON-Parse-Fehler an einer Stelle, die nichts damit zu tun hat.

Response-Envelope

Alle Antworten haben dieselbe Hülle:

json
{
  "data":   { },
  "status": "success",
  "auth":   { "email": "…", "token": "…", "language": "deu" }
}

status ist "success", wenn der Request durchgelaufen ist, sonst "error". Der auth-Block wiederholt nur, wer du bist — du brauchst ihn nicht auszuwerten.

Wo die Nutzdaten liegen, ist pro Endpunkt verschieden

Es gibt kein einheitliches Muster. Halte dich an die Tabelle:

EndpunktNutzdaten
POST /freights/add.jsondata — flach, die ID also unter data.id
POST /freights/edit/{id}.jsondata
GET /freights/view/{id}.jsondata.route
GET /freights/enlist/{id}.jsondata.routes (Array)
POST /quotes/add.jsondata.bid
POST /pricing/search/results.jsondata.rates, data.combinations

Derselbe Gegenstand liegt beim Anlegen also in data und beim Abrufen in data.route. Das ist unschön, aber so ist es.

Erfolg erkennen

js
if (res.status === 200 && body.status === 'success' && body.data?.id) {
  // angelegt
}

status allein reicht dafür aus — das zusätzliche Prüfen des Nutzdatenfelds kostet nichts und schützt dich davor, dass ein Feldname-Tippfehler unbemerkt bleibt (siehe Regel 2 unten).

Formatierte Anzeigefelder (f_-Prefix)

Frachten liefern neben den Rohwerten fertig formatierte Felder. Direkt verwendbar:

FeldRohwertformatiert
f_from / f_toFRA + DEFRA (DE)
f_eta / f_etd2026-09-22 14:00:002026-09-22 14:00
f_eta_end / f_etd_endnullnicht festgelegt
captionFRA (DE) to MUC (DE) - (Bestellung 4711)
display_idKundenreferenz, sonst die ID

Zeiten in der Zeitzone der Anwendung, ohne Sekunden. Fehlt ein Wert, steht dort ein übersetzter Platzhaltertext in der Sprache deines Benutzers statt null.

Für die Anzeige die f_-Felder, für Logik die Rohfelderf_eta kann "nicht festgelegt" enthalten und ist dann nicht parsebar.

Weitere: delivery_status_in_words, info, editable, list_icon, media_types.

Felder mit _-Prefix sind optional

Verschachtelte Objekte tragen einen Unterstrich: _bids, _route_pieces, _company. Ob sie vorhanden sind, hängt vom Endpunkt und von deiner Sichtbarkeit ab. Behandle jedes _-Feld als optional und prüfe auf Existenz, bevor du darauf zugreifst.

Felder ohne Unterstrich sind Kernfelder der Ressource und verlässlich da.

Fehlerbehandlung

Zwei Regeln, die du nicht umgehen kannst:

1. Redirects und HTML sind Fehler — mit einer Ausnahme

Ein fehlgeschlagener Request antwortet nicht immer mit 4xx-JSON. Fehlt eine Berechtigung, kommt oft ein Redirect auf eine HTML-Seite. Deshalb:

Schalte automatisches Redirect-Following ab. Sonst siehst du am Ende einen 200 mit HTML-Body und hältst ihn für einen Erfolg.

js
const res = await fetch(url, { redirect: 'manual', /* … */ })
if (!res.ok || res.status >= 300) throw new Error(`HTTP ${res.status}`)
if (!res.headers.get('content-type')?.includes('application/json')) {
  throw new Error('Keine JSON-Antwort — nicht autorisiert oder .json vergessen')
}
python
r = requests.post(url, json=payload, headers=headers, allow_redirects=False)
if r.status_code >= 300:
    raise RuntimeError(f"HTTP {r.status_code}")
if "application/json" not in r.headers.get("content-type", ""):
    raise RuntimeError("Keine JSON-Antwort")
php
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
AntwortBedeutung
200 + JSON, status: "success"Erfolg
200 + JSON, status: "error"Fachlicher Fehler
200 + HTML.json an der URL vergessen
3xxKeine Berechtigung — außer beim Ausschreiben, siehe unten
401Token fehlt, falsch, oder aus der anderen Umgebung
404Existiert nicht oder ist für dich nicht sichtbar

404 ist zweideutig: Frachten anderer Firmen sehen für dich aus wie nicht existierende.

Die Ausnahme: Eine Fracht ausschreiben (POST /freights/view/{id}.json mit action: "publish") antwortet planmäßig mit 302 und war trotzdem erfolgreich. Dasselbe gilt für POST /quotes/confirm/{bidId}.json. An diesen zwei Stellen den Zustand mit einem GET nachprüfen, statt den Statuscode zu bewerten.

2. Prüfe die Antwort gegen deinen Request

Beim Anlegen und Ändern von Frachten gibt es keine Feldvalidierung. Unbekannte oder falsch typisierte Felder werden still verworfen — status bleibt trotzdem "success", weil der Request als solcher ja durchgelaufen ist.

Der häufigste Fall: ein Datum ohne Uhrzeit.

json
"eta": "2026-09-22"            → wird zu null
"eta": "2026-09-22 14:00:00"   → funktioniert

Vergleiche die zurückgegebene Ressource mit dem, was du gesendet hast — besonders eta, etd und weight.

Request-Body-Formate

Query-Parameter, Formularfelder und JSON-Body werden serverseitig zusammengeführt. Jedes dokumentierte Feld funktioniert daher in allen drei Formen. Für neue Integrationen: application/json.

bash
# alle drei äquivalent
curl -X POST "$BASE/freights/add.json" -H "Content-Type: application/json" -d '{"weight": 250}'
curl -X POST "$BASE/freights/add.json" -d "weight=250"
curl -X POST "$BASE/freights/add.json?weight=250"

Datum und Zeit

Alle Zeitstempel in Antworten: YYYY-MM-DD HH:MM:SS, ohne Zeitzonen-Angabe.

json
"etd": "2026-09-20 08:00:00"

In Requests akzeptiert werden YYYY-MM-DD HH:MM und YYYY-MM-DD HH:MM:SS. Bei deadline zusätzlich relative Angaben wie +48 hours.

Die Uhrzeit ist Pflicht

Ein reines Datum wird bei eta und etd still verworfen — das Feld bleibt null, ohne Fehlermeldung.

Zeitfenster sind ein String mit dem Separator to (Leerzeichen, to, Leerzeichen):

json
"etd_range": "2026-09-20 08:00 to 2026-09-20 18:00"

Daraus werden etd und etd_end. eta_range analog. Bei jedem anderen Trennzeichen bleiben beide Zielfelder leer.

Bitte einen User-Agent senden

Sofern dein HTTP-Client das zulässt, schick einen aussagekräftigen User-Agent im Format Softwarename / Version:

User-Agent: MyApplication / 1.0.0

Das ist keine technische Voraussetzung — es funktioniert auch ohne. Aber wenn wir bei einer Störung oder Rückfrage in die Logs schauen, ist der Unterschied zwischen MyApplication / 1.0.0 und python-requests/2.31.0 genau der zwischen „wir wissen, wen wir anrufen" und „irgendein Client irgendeiner Firma". Die Versionsnummer hilft zusätzlich, wenn ein Problem nur mit einem bestimmten Release deiner Software auftritt.

Pagination

Listen-Endpunkte nehmen page, limit, sort und direction. Die Antwort enthält keine Pagination-Metadaten — weder Gesamtzahl noch Seitenanzahl.

Blättere, bis das Array kürzer als limit ist:

js
let page = 1, all = []
for (;;) {
  const { data } = await get(`/freights/enlist/10.json?page=${page}&limit=50`)
  const batch = data.routes ?? []
  all.push(...batch)
  if (batch.length < 50) break
  page++
}

Rate Limits

Es gibt derzeit keine dokumentierten Rate Limits. Verhalte dich trotzdem fair: Ergebnisse cachen, nicht pollen, wo ein einzelner Abruf genügt, und den Token nicht bei jedem Request neu holen.

Wenn deine Integration nennenswertes Volumen erwartet, sag uns vorher Bescheid: support@aircargobook.com.

IDs sind umgebungsspezifisch

Fracht 12345 in der Demo ist eine andere Fracht als 12345 in der Produktion. Das gilt auch für Firmen-, Raten- und Angebots-IDs. Übernimm keine IDs aus Tests.