Erscheinungsbild
Fracht anlegen und ausschreiben
Vollständiger Ablauf: Token holen, Fracht anlegen, ausschreiben, abrufen, Angebote lesen. Alle Requests und Antworten hier sind gegen die API gemessen, nicht nachgebaut.
Was ist eine „Fracht"?
Eine Fracht (/freights/…) ist eine Transportanfrage: Strecke, Gewicht, Packstücke, Termin. Anbieter sehen sie und geben Angebote ab. Bekommt ein Angebot den Zuschlag, wird aus der Fracht ein Auftrag.
Der Ablauf
1. POST /users/login.json → auth_token
2. POST /freights/add.json?type=AHR → status: draft ← noch unsichtbar!
3. POST /freights/view/{id}.json → status: open ← jetzt ausgeschrieben
{ "action": "publish" }
4. GET /freights/view/{id}.json → Fracht + _bidsSchritt 3 ist der, den man vergisst. Eine neu angelegte Fracht ist ein Entwurf und für Anbieter nicht sichtbar.
Schritt 1: Token
Siehe Authentifizierung. Für die weiteren Beispiele:
bash
BASE="https://demo.aircargobook.com"
TOKEN="8f14e45fceea167a5a36dedd4bea2543"
UA="MyApplication / 1.0.0"Schritt 2: Fracht anlegen
type=AHR ist eine Ausschreibung — mehrere Anbieter können bieten. Das ist der Standardfall. class=rfs bedeutet Trucking; für Luftfracht siehe weiter unten.
Dieses Beispiel enthält alles, was die Weboberfläche beim Anlegen mitschickt — damit sieht deine Fracht im UI genauso vollständig aus wie eine manuell erfasste:
bash
curl -X POST "$BASE/freights/add.json?type=AHR" \
-H "Authorization: ApplePass $TOKEN" \
-H "Content-Type: application/json" \
-H "User-Agent: $UA" \
-d '{
"class": "rfs",
"type": "AHR",
"category": "ftl",
"from": "city:60547",
"from_country": "DE",
"from_addr": "Frankfurt am Main, Germany",
"from_latitude": 50.0379,
"from_longitude": 8.5622,
"to": "city:34119",
"to_country": "DE",
"to_addr": "Kassel, Germany",
"to_latitude": 51.3127,
"to_longitude": 9.4797,
"eta": "2026-09-22 14:00:00",
"dangerous": "no",
"secured": 1,
"pieces": [
{"pieces": 2, "depth": 120, "width": 80, "height": 100, "weight": 125, "stackable": 1},
{"pieces": 2, "depth": 120, "width": 80, "height": 60, "weight": 115, "stackable": 1}
],
"refs": {
"commodity": "Autoteile",
"customer": "Musterkunde GmbH / Bestellung 4711",
"incoterm": "dap",
"incoterm_place": "Kassel",
"pieces": 4,
"weight_actual": 480,
"weight_volume": 640,
"capacity": 3.84,
"external_ref": "MYAPP-2026-00123"
}
}'Antwort (gekürzt um die UI-Anzeigefelder):
json
{
"data": {
"id": 1965,
"class": "rfs",
"type": "AHR",
"status": "draft",
"category": "ftl",
"target": 3,
"from": "60547",
"from_country": "DE",
"from_addr": "Frankfurt am Main, Germany",
"from_latitude": "50.0379",
"from_longitude": "8.5622",
"to": "34119",
"to_country": "DE",
"to_addr": "Kassel, Germany",
"eta": "2026-09-22 14:00:00",
"dangerous_goods": "no",
"secured": "1",
"company": 101,
"user": 1001038,
"created": "2026-09-15 13:42:32",
"refs": {
"commodity": "Autoteile",
"customer": "Musterkunde GmbH / Bestellung 4711",
"incoterm": "dap",
"incoterm_place": "Kassel",
"pieces": 4,
"weight_actual": 480,
"weight_volume": 640,
"capacity": 3.84,
"external_ref": "MYAPP-2026-00123"
},
"f_from": "60547 (DE)",
"f_to": "34119 (DE)",
"f_eta": "2026-09-22 14:00",
"f_etd": "nicht festgelegt",
"caption": "60547 (DE) to 34119 (DE) - (Musterkunde GmbH / Bestellung 4711)",
"display_id": "Musterkunde GmbH / Bestellung 4711"
},
"status": "success",
"auth": { "email": "…", "token": "…", "language": "deu" }
}Wo die Fracht liegt, wechselt je Endpunkt
Beim Anlegen liegt sie flach in data — deine neue ID steht unter data.id, nicht data.route.id. Beim Abrufen (Schritt 4) ist es dagegen data.route. Die beiden Endpunkte sind darin nicht einheitlich; die vollständige Tabelle steht in Konventionen.
Die formatierten f_-Felder
Neben den Rohwerten liefert jede Fracht fertig formatierte Anzeigefelder. Die kannst du direkt in deine Oberfläche schreiben, statt selbst zu formatieren:
| Feld | Rohwert | formatiert |
|---|---|---|
f_from / f_to | 60547 + DE | 60547 (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 | — | 60547 (DE) to 34119 (DE) - (Bestellung 4711) |
display_id | — | Kundenreferenz aus refs.customer, sonst die ID |
Zeiten kommen in der Zeitzone der Anwendung und ohne Sekunden. Ist ein Wert nicht gesetzt, steht dort ein übersetzter Platzhaltertext in der Sprache deines Benutzers — nicht null.
Anzeigen vs. rechnen
Für die Darstellung die f_-Felder, für Logik und Vergleiche die Rohfelder. f_eta kann "nicht festgelegt" enthalten und lässt sich dann nicht parsen.
f_from und f_to unterliegen derselben Maskierung wie die Rohfelder: bei Frachten, deren Details du nicht sehen darfst, steht dort xxx (XX).
Weitere Anzeigefelder: delivery_status_in_words, info, editable, list_icon, media_types.
Das Minimum
Wenn du nur eine PLZ-zu-PLZ-Anfrage brauchst:
json
{
"class": "rfs",
"from": "city:60547",
"to": "city:34119",
"eta": "2026-09-22 14:00:00",
"refs": { "weight_actual": 480 },
"pieces": [
{"pieces": 4, "depth": 120, "width": 80, "height": 100, "weight": 120}
]
}Die fünf Stellen, an denen es klemmt
1. eta ohne Uhrzeit wird still verworfen
json
"eta": "2026-09-22" ← wird zu null, ohne Fehlermeldung
"eta": "2026-09-22 14:00:00" ← funktioniertDas gilt genauso für etd. Immer eine Uhrzeit mitgeben.
2. Postleitzahlen werden nicht aufgelöst
from und to akzeptieren drei Schreibweisen:
| Wert | Bedeutung |
|---|---|
city:34119 | Postleitzahlengebiet — für Trucking der Normalfall |
airport:FRA | Flughafen per IATA-Code |
FRA | dasselbe, ohne Prefix |
Der Prefix wird abgeschnitten; in der Antwort steht nur noch 34119 bzw. FRA.
Bei einem IATA-Code ergänzt der Server from_country, from_addr und die Koordinaten selbst. Bei einer Postleitzahl passiert das nicht — dann bleiben diese Felder leer, wenn du sie nicht mitsendest. Deshalb sind sie im Beispiel oben explizit gesetzt.
Die acht Felder: from_country, from_addr, from_latitude, from_longitude und dieselben vier für to_.
WARNING
Ist from_addr gesetzt, unterbleibt die automatische IATA-Auflösung komplett. Entweder alle vier Felder selbst setzen oder keins.
3. Zeitfenster sind ein String
Statt eines einzelnen Termins geht auch ein Fenster. Dann ist es ein Feld, kein Paar — Separator ist Leerzeichen-to-Leerzeichen:
json
"eta_range": "2026-09-22 08:00 to 2026-09-22 18:00"Daraus werden eta und eta_end. etd_range funktioniert identisch für etd/etd_end. Bei jedem anderen Trennzeichen bleiben beide Zielfelder leer.
4. Gewicht: zwei Wege
Entweder weight direkt, oder weight_actual und weight_volume gemeinsam — dann gilt der größere Wert. Nur eines der beiden zu senden bewirkt nichts.
Zusätzlich gehören die Anzeigewerte nach refs: refs.weight_actual, refs.weight_volume, refs.capacity (Volumen in m³) und refs.pieces (Gesamtzahl). Die füllt im UI der Volumenrechner — ohne sie wirkt die Fracht dort unvollständig.
5. pieces ersetzt, statt zu ergänzen
Die Packstückliste überschreibt beim Bearbeiten die bestehende. Immer die vollständige Liste senden.
Pro Zeile: pieces (Anzahl), depth, width, height (cm), weight (kg), stackable, weight_per_piece (1 = weight gilt je Stück) und optional refs.awb / refs.uld.
Es gibt keine Feldvalidierung
Ein Tippfehler im Feldnamen wird still ignoriert — status ist trotzdem "success". Vergleiche die zurückgegebene data mit dem, was du gesendet hast, besonders eta, etd und weight.
Das Referenzfeld refs
refs ist ein freier JSON-Container. Er wird unverändert gespeichert und beim Abruf zurückgegeben — der richtige Ort für deine eigenen Referenzen (external_ref).
Einige Schlüssel haben Bedeutung für die Plattform:
| Schlüssel | Wirkung |
|---|---|
customer | wird im UI als Titel der Fracht angezeigt |
commodity | Warenbeschreibung |
incoterm | exw, fca, fas, fob, cfr, cif, dat, dap, cpt, cip, ddp |
incoterm_place | benannter Ort dazu |
pieces, weight_actual, weight_volume, capacity | Anzeigewerte |
target_rate, target_currency | Zielpreis, unverbindlich |
entity + obj_id | Bezug auf eine konkrete Rate oder einen Flug |
Schritt 3: Ausschreiben
Solange die Fracht draft ist, sieht sie kein Anbieter.
bash
curl -X POST "$BASE/freights/view/1965.json" \
-H "Authorization: ApplePass $TOKEN" \
-H "Content-Type: application/json" \
-H "User-Agent: $UA" \
-d '{"action": "publish", "deadline": "+48 hours"}'Zwei Eigenheiten
Es gibt keinen /freights/publish/-Endpunkt. Ausgeschrieben wird über dieselbe URL wie beim Abrufen, per POST und mit action: "publish" im Body.
Die Antwort ist ein 302 — und der Request war trotzdem erfolgreich. Das ist die eine Stelle, an der ein Redirect kein Fehler ist. Prüfe den Erfolg mit einem GET im nächsten Schritt.
Ohne deadline setzt der Server jetzt + 24 Stunden, damit immer ein Bietfenster existiert. Akzeptiert werden YYYY-MM-DD HH:MM und relative Angaben wie +48 hours.
Nur an ausgewählte Anbieter:
json
{
"action": "publish",
"visibility": -2,
"whitelist": [42, 88, 130],
"deadline": "2026-09-20 17:00"
}whitelist greift nur zusammen mit visibility: -2.
Schritt 4: Fracht abrufen
bash
curl "$BASE/freights/view/1965.json" \
-H "Authorization: ApplePass $TOKEN" \
-H "User-Agent: $UA"Hier liegt die Fracht unter data.route — anders als beim Anlegen:
json
{
"data": {
"route": {
"id": 1965,
"status": "open",
"from": "60547",
"to": "34119",
"eta": "2026-09-22 14:00:00",
"deadline": "2026-09-17 15:30:00",
"price": null,
"winner": null,
"refs": { "customer": "Musterkunde GmbH / Bestellung 4711" },
"f_eta": "2026-09-22 14:00",
"_route_pieces": [
{"pieces": 2, "depth": 120, "width": 80, "height": 100, "weight": 125},
{"pieces": 2, "depth": 120, "width": 80, "height": 60, "weight": 115}
],
"_bids": []
}
},
"status": "success"
}Prüfe hier, ob eta und die Packstücke so angekommen sind, wie du sie gesendet hast. Das ist der Ersatz für die fehlende Feldvalidierung.
404 ist zweideutig
Eine Fracht, die deiner Firma nicht gehört und nicht an dich ausgeschrieben ist, antwortet mit 404 — genau wie eine nicht existierende. Aus dem Statuscode allein kannst du nicht schließen, ob es die Fracht gibt.
Angebote lesen
Es gibt kein /quotes/get/{id}.json
Angebote werden nicht über einen eigenen Endpunkt abgerufen, sondern kommen als data.route._bids in der Fracht mit. Die /quotes/…-Endpunkte dienen nur dem Abgeben, Zurückziehen und Bestätigen.
Sobald Anbieter geboten haben, füllt sich _bids:
json
"_bids": [
{
"id": 9911,
"route": 1965,
"company": 42,
"display_company_id": 42,
"amount": 780.00,
"currency_code": "EUR",
"status": "open",
"note": "Direktverkehr, Zustellung wie angefragt",
"deadline": "2026-09-19 12:00:00",
"created": "2026-09-16 09:14:02"
}
]Zwei Bedingungen, damit _bids überhaupt erscheint:
- Status. Die Fracht muss
open,pendingodertakensein. Beidraft,expired,revoked,closedoderarchivedfehlt das Feld ganz — nicht als leeres Array, sondern gar nicht. - Sichtbarkeit. Gehört die Fracht deiner Firma, siehst du alle Angebote. Gehört sie einer anderen, siehst du nur deine eigenen.
Ein fehlendes _bids heißt also nicht „keine Angebote". Prüfe zuerst status.
Luftfracht statt Trucking
class steuert, ob gefahren oder geflogen wird:
| Wert | Bedeutung | target |
|---|---|---|
rfs | Trucking (Road Feeder Service) — der Normalfall | 3 |
air | Luftfracht, Flughafen zu Flughafen | 2 |
target wird daraus abgeleitet und serverseitig gesetzt — nicht mitsenden.
json
{
"class": "air",
"type": "AHR",
"from": "airport:FRA",
"to": "airport:JFK",
"direction": "export",
"weight_actual": 480,
"weight_volume": 640,
"etd_range": "2026-09-20 08:00 to 2026-09-20 18:00",
"dangerous": "no",
"secured": 1
}Bei IATA-Codes kannst du die from_* / to_*-Felder weglassen — die ergänzt der Server.
Direktbuchung ohne Ausschreibung
Wenn Anbieter und Preis feststehen, spart type=DTO mit winner_direct die Bietrunde:
bash
curl -X POST "$BASE/freights/add.json?type=DTO" \
-H "Authorization: ApplePass $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"class": "rfs",
"from": "city:60547",
"to": "city:34119",
"eta": "2026-09-22 14:00:00",
"winner_direct": 42,
"price": 780,
"refs": {"external_ref": "MYAPP-2026-00123"}
}'winner_direct: 42 setzt in einem Schritt whitelist = [42], visibility = -2 und status = open. Kein separates Ausschreiben nötig.
Statusverlauf
status | Bedeutung |
|---|---|
draft | Entwurf, für Anbieter unsichtbar |
open | Ausgeschrieben, Angebote möglich |
pending | Ein Angebot wurde von einer Seite bestätigt, wartet auf die Gegenseite |
taken | Beauftragt — winner und price sind gesetzt |
expired | Deadline verstrichen, ohne Zuschlag |
revoked | Zurückgezogen |
closed / archived | Abgeschlossen |
Deine Frachten wiederfinden
Es gibt keinen freien Filter — gelistet wird über vordefinierte Listen-IDs:
bash
curl "$BASE/freights/enlist/10.json?page=1&limit=50" \
-H "Authorization: ApplePass $TOKEN"| ID | Liste |
|---|---|
20 | Meine Entwürfe |
10 | Meine offenen Ausschreibungen |
17 | Meine Ausschreibungen mit eingegangenen Angeboten |
12 | Meine beauftragten Transporte |
19 | Archiv |
Ergebnis unter data.routes, ohne Pagination-Metadaten — blättern über page, bis das Array kürzer als limit ist.
Besser: eigene Referenz mitführen
Speichere data.id aus Schritt 2 in deinem System und rufe die Fracht direkt über GET /freights/view/{id}.json ab. refs.external_ref hilft umgekehrt, eine Fracht deinem eigenen Datensatz zuzuordnen.
Checkliste
- Token einmal holen, im
Authorization: ApplePass …-Header senden. User-Agent: MyApplication / 1.0.0mitschicken.- Gegen Demo entwickeln.
.jsonan jede URL.eta/etdmit Uhrzeit.- Bei Postleitzahlen die
from_*/to_*-Felder selbst setzen. databeim Anlegen,data.routebeim Abrufen.- Antwort gegen den Request prüfen — es gibt keine Feldvalidierung.
- Bei
AHR: ausschreiben, sonst bleibt die Fracht ein unsichtbarer Entwurf. Der302dabei ist normal. - Angebote über
GET /freights/view/{id}.json→_bidslesen. - Für die Anzeige die
f_-Felder nutzen, für Logik die Rohfelder.
Weiter
- API-Referenz: Freights — alle Felder
- API-Referenz: Quotes — Angebote abgeben und bestätigen
- Konventionen — Envelope, Fehler, Datumsformate
