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
caption—FRA (DE) to MUC (DE) - (Bestellung 4711)
display_id—Kundenreferenz, 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 Rohfelder — f_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.