Erscheinungsbild
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/jsonDas 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:
| Endpunkt | Nutzdaten |
|---|---|
POST /freights/add.json | data — flach, die ID also unter data.id |
POST /freights/edit/{id}.json | data |
GET /freights/view/{id}.json | data.route |
GET /freights/enlist/{id}.json | data.routes (Array) |
POST /quotes/add.json | data.bid |
POST /pricing/search/results.json | data.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:
| Feld | Rohwert | formatiert |
|---|---|---|
f_from / f_to | FRA + DE | FRA (DE) |
f_eta / f_etd | 2026-09-22 14:00:00 | 2026-09-22 14:00 |
f_eta_end / f_etd_end | null | nicht 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);| Antwort | Bedeutung |
|---|---|
200 + JSON, status: "success" | Erfolg |
200 + JSON, status: "error" | Fachlicher Fehler |
200 + HTML | .json an der URL vergessen |
3xx | Keine Berechtigung — außer beim Ausschreiben, siehe unten |
401 | Token fehlt, falsch, oder aus der anderen Umgebung |
404 | Existiert 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" → funktioniertVergleiche 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.0Das 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.
