Skip to content

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 + _bids

Schritt 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:

FeldRohwertformatiert
f_from / f_to60547 + DE60547 (DE)
f_eta / f_etd2026-09-22 14:00:002026-09-22 14:00
f_eta_end / f_etd_endnullnicht festgelegt
caption60547 (DE) to 34119 (DE) - (Bestellung 4711)
display_idKundenreferenz 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"   ← funktioniert

Das gilt genauso für etd. Immer eine Uhrzeit mitgeben.

2. Postleitzahlen werden nicht aufgelöst

from und to akzeptieren drei Schreibweisen:

WertBedeutung
city:34119Postleitzahlengebiet — für Trucking der Normalfall
airport:FRAFlughafen per IATA-Code
FRAdasselbe, 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üsselWirkung
customerwird im UI als Titel der Fracht angezeigt
commodityWarenbeschreibung
incotermexw, fca, fas, fob, cfr, cif, dat, dap, cpt, cip, ddp
incoterm_placebenannter Ort dazu
pieces, weight_actual, weight_volume, capacityAnzeigewerte
target_rate, target_currencyZielpreis, unverbindlich
entity + obj_idBezug 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:

  1. Status. Die Fracht muss open, pending oder taken sein. Bei draft, expired, revoked, closed oder archived fehlt das Feld ganz — nicht als leeres Array, sondern gar nicht.
  2. 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:

WertBedeutungtarget
rfsTrucking (Road Feeder Service) — der Normalfall3
airLuftfracht, Flughafen zu Flughafen2

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

statusBedeutung
draftEntwurf, für Anbieter unsichtbar
openAusgeschrieben, Angebote möglich
pendingEin Angebot wurde von einer Seite bestätigt, wartet auf die Gegenseite
takenBeauftragt — winner und price sind gesetzt
expiredDeadline verstrichen, ohne Zuschlag
revokedZurückgezogen
closed / archivedAbgeschlossen

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"
IDListe
20Meine Entwürfe
10Meine offenen Ausschreibungen
17Meine Ausschreibungen mit eingegangenen Angeboten
12Meine beauftragten Transporte
19Archiv

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

  1. Token einmal holen, im Authorization: ApplePass …-Header senden.
  2. User-Agent: MyApplication / 1.0.0 mitschicken.
  3. Gegen Demo entwickeln.
  4. .json an jede URL.
  5. eta / etd mit Uhrzeit.
  6. Bei Postleitzahlen die from_* / to_*-Felder selbst setzen.
  7. data beim Anlegen, data.route beim Abrufen.
  8. Antwort gegen den Request prüfen — es gibt keine Feldvalidierung.
  9. Bei AHR: ausschreiben, sonst bleibt die Fracht ein unsichtbarer Entwurf. Der 302 dabei ist normal.
  10. Angebote über GET /freights/view/{id}.json_bids lesen.
  11. Für die Anzeige die f_-Felder nutzen, für Logik die Rohfelder.

Weiter