Skip to content

Process Pre-Alert (Canonical JSON) ​

The B2B client integration endpoint for ingesting pre-alerts as canonical JSON. Same downstream behavior as the Excel-based POST /api/manifests/process-pre-alert β€” a manifest created here is indistinguishable from one uploaded via the dashboard. The only difference is the input shape and the authentication.

Audience: B2B integration engineers (Pasarex, Cainiao, Aliexpress, Shein, Falabella, …). The endpoint uses a stable API key (no token expiry) and a strict, predictable JSON contract. Status can be polled via the read endpoints and pushed via client webhooks.

POST /api/manifests/process-pre-alert-json
Authorization: Bearer <api_password>
Content-Type: application/json

Auth: a static bearer token equal to your company's api_password (issued by LogirAI ops). No expiry β€” fit for server-to-server automation. The legacy operator JWT is no longer accepted on this URL.

Tenant isolation: your company is taken from the token. Anything you put in the body for companyId, country, or preAlertId is ignored β€” you can only act on your own data.

One token, any country ​

You have one api_password. It works for every country your account is enabled for (Chile, Peru, Argentina). You don't send a country field β€” the server figures out the country from where the shipment is going:

If you send a flight to……it's routed to
destination_airport: "SCL"πŸ‡¨πŸ‡± Chile
destination_airport: "LIM"πŸ‡΅πŸ‡ͺ Peru
destination_airport: "EZE" (or AEP, COR, CBA)πŸ‡¦πŸ‡· Argentina

So the same token can send one manifest to Santiago and the next to Lima β€” just change the destination. How you express the destination depends on the transport mode β€” see below.

Already integrated for CL or AR? Two things changed: (1) stop sending country in the body (it's ignored now), and (2) set the destination correctly for your mode (destination_airport for air, destination_country for ground) β€” that's what picks the country. Everything else is the same.

If something's off, you get a clear error before anything is created:

Error (HTTP)MeaningFix
country_not_allowed (403)The destination resolves to a country your account isn't enabled for.Ask LogirAI ops to enable that country for you.
destination_unresolved (400)Air: the airport isn't recognized. Ground: flightData.destination_country is missing.Send a supported airport / the destination country.
unsupported_transport_mode (400)transportMode isn't air or ground.Use air or ground.

Acting on behalf of a client ​

Most integrations are single-tenant: your token identifies the company that owns the cargo, and you send nothing else. A courier moving cargo for several client companies under one credential is the exception, and for that there is one optional top-level field:

jsonc
{
  "mawbNumber": "160-12345678",
  "client_code": "LION_SHEIN",   // ← optional; names the OWNER of this manifest
  "transportMode": "air",
  "flightData": { "destination_airport": "SCL" },
  "guideRows": [ /* ... */ ]
}
  • Omit it (the normal case) and your token's company owns the manifest. Nothing changes.
  • Send it and the manifest, its pre-alert and every guide belong to the company that client_code resolves to. Your token only authorizes the call.

The code must match an active client company exactly (case-sensitive). Ask LogirAI ops for the codes enabled for you.

Error (HTTP)Meaning
client_code_not_allowed (403)Your token is a client token, not a courier's. A client may not post on behalf of another client.
client_not_resolved (403)The code matches no active client company.

Sending client_code does not change the country rules: the destination still decides the country, and it is still gated against your account's allowed countries.


Cainiao clients ​

Cainiao / AliExpress carry two identifiers inside extra_data. The gate is bidirectional, and keyed on the owner company:

Ownerextra_data.cainiao_cop_no + cainiao_waybillResult
Cainiao / AliExpressboth present and non-emptyaccepted
Cainiao / AliExpresseither missing422 cainiao_fields_missing
any other clienteither present422 cainiao_fields_not_allowed

A stray cainiao_* key on a non-Cainiao manifest is treated as a mis-routed integration, not a harmless extra β€” hence the rejection rather than a silent passthrough. The 422 is deliberately outside the 400 family: the rows are well-formed, they are just the wrong shape for this client.


Air vs Ground ​

transportMode tells the server how the shipment travels, and which field decides the country. Pick one:

air (default)ground
What picks the countryflightData.destination_airport (IATA code)flightData.destination_country (ISO-2 code)
flightData required?Yes β€” with at least destination_airportYes β€” with at least destination_country (do not send null)
Other flightData fieldsflight_number, origin_airport, origin_country, scheduled_departure, scheduled_arrival (recommended)not needed β€” leave them out
Flight recordA flight is created/linked (you get a flight_id)No flight is created (flight_id is null)
licensePlate (top-level)leave nulloptional β€” the delivery vehicle's plate (AR writes it to the customs file)

Air example ​

jsonc
{
  "mawbNumber": "160-12345678",
  "transportMode": "air",
  "flightData": {
    "flight_number": "LA801",
    "origin_airport": "MIA",
    "destination_airport": "SCL",   // ← decides the country (SCL β†’ CL)
    "origin_country": "US",
    "scheduled_departure": "2026-06-01T22:00:00Z",
    "scheduled_arrival": "2026-06-02T08:00:00Z"
  },
  "licensePlate": null,
  "guideRows": [ /* ... */ ]
}

Ground example ​

jsonc
{
  "mawbNumber": "057-12345678",
  "transportMode": "ground",
  "flightData": {
    "destination_country": "AR"     // ← decides the country (ISO-2). REQUIRED for ground.
  },
  "licensePlate": "AB123CD",        // optional β€” the truck/van plate
  "guideRows": [ /* ... */ ]
}

flightData content rules (air) ​

Two checks run on the flight block itself. They are the most common cause of a rejected first integration, so check them before anything else:

FieldRuleRejected example
flight_numberMust look like a flight number, not a carrier name: no whitespace, at least one digit, 2–8 chars, [A-Za-z0-9-] only. LA533, 4M514, B6701, 533 all pass."Trans Caribbean" (space, no digit)
scheduled_arrivalMust be strictly after scheduled_departure when both are present and parseable.arrival 2026-06-01T08:00Z with departure 2026-06-02T22:00Z

Both surface as 400 validation_failed with the offending details[].path.

Ocean shipments: an ocean bill of lading (MAEU-…, MSCU-…) sent as transportMode: "air" will fail with 400 destination_unresolved, because there is no airport to resolve a country from. Ocean legs go through transportMode: "ground" with flightData.destination_country.

Ground gotcha: flightData is still required for ground β€” it carries destination_country. Sending flightData: null (or omitting destination_country) returns 400 destination_unresolved. The airport/flight fields are simply ignored on ground.


Request body ​

json
{
  "mawbNumber": "999-87654321",
  "transportMode": "air",
  "flightData": {
    "flight_number": "LA801",
    "origin_airport": "PVG",
    "destination_airport": "SCL",
    "origin_country": "CN",
    "scheduled_departure": "2026-04-18T02:00:00+08:00",
    "scheduled_arrival": "2026-04-18T14:00:00-03:00"
  },
  "dispatcherDocument": null,
  "cargoReference": null,
  "licensePlate": null,
  "guideRows": [ /* see below */ ]
}

Strict completeness contract (D13): every field listed in the canonical guide shape is required and must be non-empty (no null, no "") unless marked optional or a carve-out below. An empty required field rejects the whole manifest with 400 manifest_validation_failed and invalid_rows[].error_code = "field_empty".

Contract aligned with the courier gate (2026-08): the row contract now matches the one the Starken courier entry point has been enforcing in production. Three fields became required on CL β€” order_created_at, iva_override (an explicit true/false) and, as before, internal_number. Five stopped being required on CL and PE β€” the four shipper_* fields and insurance_usd; they are still persisted when you send them. Argentina still requires all five, because it is the one destination that writes them into a customs file: they are positional fields of AIDA record 02, where a missing value becomes a blank position, silently. Peru files nothing with SUNAT β€” the broker does β€” so demanding them there bought no guarantee. The Cainiao gate also applies here now.

Contract slimmed (2026-06): fields that no pipeline consumed were removed β€” customs_regime, bag_number, cb_number, courier_guide_number, courier_code, platform_tax_number, tax_payment_method, selected_warehouse, ddp_conso_upgrade, invoice_url. They are simply ignored if still sent.

Field(s)null / omit whenWhy
extra_dataβ€”Send {} (never null) when empty.
external_guide_number, consignee_document_typealways optionalMay be omitted or null for every country.
shipper_name, shipper_address, shipper_city, shipper_country, insurance_usdCL / PEOptional on Chile and Peru; required on AR, which writes them into the customs file. See Contract aligned with the courier gate.
consignee_typeCL / PEAR-only (drives the cupo lookup); CL/PE may omit it.
internal_number, order_created_at, iva_overrideAR onlyCL and PE share one row contract β€” both must fill all three. Only Argentina is exempt.
dispatcherDocument, cargoReference, licensePlate (top-level)CL / PE: alwaysAR manifest-header fields (dispatcherDocument required; cargoReference β†’ auto-gen, licensePlate ground-only).
flightData (top-level)neverRequired for both modes β€” air carries destination_airport, ground carries destination_country. See Air vs Ground.

Top-level fields ​

companyId, country, and preAlertId are server-derived from your token β€” do not send them. If you do, they are ignored.

FieldTypeNotes
mawbNumberstringThe MAWB. Must be unique across the system (duplicate β†’ 409).
client_codestring | nullOptional. Only for courier tokens acting on behalf of a client that owns the cargo. Omit it and your token's company is the owner. See Acting on behalf of a client.
transportModeenum air | groundair derives the country from destination_airport + creates a flight; ground derives it from destination_country + skips flight creation (use licensePlate). See Air vs Ground.
flightDataobjectRequired for both modes. Air β†’ destination_airport (+ flight fields). Ground β†’ destination_country (ISO-2). Not nullable.
guideRowsarray (min 1)Each row follows the canonical guide shape.

AR manifest-header fields (D14 β€” AR-only; CL sends null) ​

These are manifest-level customs-header fields written at pre-alert time β€” not per-guide, and not derived from the outbound dispatch ("batch de salida"). CL never uses them (the CL vehicle/conductor live on the later dispatch / hoja de ruta).

FieldTypeNotes
dispatcherDocumentstring [0-9]{7,11} | nullCustoms broker (despachante) document. Required for AR β€” the AIDA TXT generator throws without it. Must arrive at pre-alert.
cargoReferencestring (≀32) | nullOptional client override. If null, the server auto-generates <TRACKING_PREFIX><NNN> race-safe.
licensePlatestring (≀32) | nullGround transport only β€” written to manifests.license_plate and used as record-01 position-6 in AIDA TXT. Air β†’ null.

Metadata (sourceFilePath, sourceFileType, excelFileName, invoicePdfFileName) is server-managed β€” do not send it.


Canonical guide shape ​

Each row in guideRows[] is the same shape produced by the Excel parsers β€” already mapped to canonical field names. The server does not parse anything; it persists the rows as-is (after country-specific validation and enrichment).

Strict contract: every field below is required and non-empty in every row. The only nulls allowed are the carve-outs in the completeness contract above (the country/mode-conditional fields); extra_data takes {} when empty. cif_usd is not part of the body β€” the server computes it. Numeric gating columns (weight_kg, fob_usd, freight_usd) must be > 0 β€” see Validation rules.

Identifier ​

FieldTypeNotes
hawbstringNon-empty. House Air Waybill β€” unique per row.

Consignee (all required) ​

FieldTypeNotes
consignee_namestring
consignee_rutstringCL: RUT mod-11 (12.345.678-5). AR: CUIT/CUIL 20XXXXXXXX9.
consignee_typeenum C | PAR: required. Drives cupo lookup β€” P (particular) triggers cupo, C (comercial) skips it. CL/PE: optional (ignored on CL; passthrough on PE).
consignee_document_typeenum CUIT | CUIL | DNI | RUT | OTHEROptional everywhere. AR derives the doc kind from consignee_type. PE: send OTHER/PASSPORT for foreign docs to bypass the RUC/DNI mod-11 check (else it's inferred by digit count). CL ignores it.
consignee_addressstring
consignee_citystring
consignee_statestringAR: ISO 2-letter province code (e.g. C for CABA).
consignee_zip_codestring
consignee_phonestring
consignee_emailstring
consignee_countrystring

Shipper (required on AR, optional on CL/PE) ​

Required when the destination is Argentina β€” AR writes the whole block into AIDA record 02. Optional on Chile and Peru since the 2026-08 alignment, but send them anyway: the shipper is what Chile declares to Atrex and what the Peruvian broker files with SUNAT.

FieldType
shipper_namestring (AR) | null (CL/PE)
shipper_addressstring (AR) | null (CL/PE)
shipper_citystring (AR) | null (CL/PE)
shipper_countrystring (AR) | null (CL/PE)

Package (all required) ​

FieldTypeNotes
packages_countint β‰₯ 1
weight_kgnumberAll-or-nothing gate β€” must be > 0. Derived (Ξ£ product weights) when products[] is sent β€” pass null then.
description_originalstringCL: input to Gemini translation. Derived (joined product descriptions) when products[] is sent.
hs_codestringDerived (distinct product codes joined with " - ") when products[] is sent β€” see Multi-product.

Financial (all required) ​

FieldTypeNotes
fob_usdnumberAll-or-nothing gate β€” must be > 0. Derived (Ξ£ qty Γ— unit_value) when products[] is sent β€” pass null then.
product_valuenumberDerived (= derived fob_usd) when products[] is sent.
freight_usdnumberAll-or-nothing gate β€” must be > 0 for CL, AR and PE (unified gate).
insurance_usdnumber | nullRequired on AR (AIDA record 02), optional on CL/PE. On CL it still feeds the CIF, so omitting it lowers the duties.
currencystring

Multi-product (products[] β€” optional) ​

A single HAWB can carry several product lines. Add an optional products[] array to any guide row. When present, the products become the source of truth for the guide's totals: the server derives weight_kg, fob_usd, product_value, description_original and hs_code from the array, overwriting whatever you sent at guide level. Send those guide-level fields as null when you rely on products (the derivation fills them before the completeness gate runs).

Omit products entirely to keep the classic one-product-per-HAWB behavior. The HAWB is still a single guide β€” products never create extra guides. products[] is only accepted on this JSON endpoint (not the Excel/operator path).

Each product:

FieldTypeNotes
descriptionstringRequired, non-empty.
quantityint β‰₯ 1Defaults to 1 if omitted.
unit_valuenumber β‰₯ 0Required. Value per unit.
weight_kgnumber β‰₯ 0Required. Weight of the line.
hs_codestring | nullOptional per product.
skustring | nullOptional.
brandstring | nullOptional.
modelstring | nullOptional.

Derivation rules:

Guide fieldDerived from products as
weight_kgΞ£ product.weight_kg
fob_usdΞ£ (product.quantity Γ— product.unit_value)
product_valuesame as derived fob_usd
description_originalproduct descriptions joined with " - "
hs_codethe distinct product hs_codes joined with " - " (order preserved, duplicates removed); falls back to the guide-level hs_code if no product carries one

Example β€” a guide row sending products with null totals (server derives weight_kg: 0.8, fob_usd: 50, description_original: "phone case - charger", hs_code: "3926.90.90 - 8504.40.90"):

A complete multi-product guide row (every required field present; the five derivable fields sent as null):

jsonc
{
  "hawb": "PX-2026-00042",
  "consignee_name": "MarΓ­a GonzΓ‘lez",
  "consignee_rut": "12.345.678-5",
  "consignee_address": "Av. Apoquindo 4500",
  "consignee_city": "Las Condes",
  "consignee_state": "RM",
  "consignee_zip_code": "7550000",
  "consignee_phone": "+56912345678",
  "consignee_email": "maria@example.cl",
  "consignee_country": "CL",
  "shipper_name": "Acme Inc",
  "shipper_address": "1 Market St",
  "shipper_city": "Shenzhen",
  "shipper_country": "CN",

  "packages_count": 1,
  "weight_kg": null,            // ← derived: 0.8  (Ξ£ product weights)
  "description_original": null, // ← derived: "phone case - charger"
  "hs_code": null,              // ← derived: "3926.90.90 - 8504.40.90"

  "fob_usd": null,              // ← derived: 50   (Ξ£ qty Γ— unit_value)
  "product_value": null,        // ← derived: 50
  "freight_usd": 5.0,
  "insurance_usd": 1.0,
  "currency": "USD",

  "platform_name": "Shein",            // optional β€” marketplace/source tag, persisted to the guide
  "platform_rut": "76.212.492-0",      // consolidator clients: required key (value may be null)
  "merchant_name": "Tienda XYZ",       // consolidator clients: required key (value may be null)
  "external_guide_number": "EXT-0042", // optional
  "order_created_at": "2026-05-28T10:00:00Z", // optional

  "internal_number": "INT-0042",
  "iva_override": false,
  "extra_data": {},

  "products": [
    { "description": "phone case", "quantity": 2, "unit_value": 10, "weight_kg": 0.5, "hs_code": "3926.90.90", "sku": "PC-01" },
    { "description": "charger",    "quantity": 1, "unit_value": 30, "weight_kg": 0.3, "hs_code": "8504.40.90", "sku": "CH-02" }
  ]
}

This row goes inside guideRows[] of the request body; the top-level envelope (mawbNumber, flightData, …) is unchanged.

Customs exports stay per-guide. Atrex (CL) / AIDA (AR) still emit one line per HAWB using the derived single description + hs_code. With many products the concatenated description is truncated to 40 chars in Atrex. The full products[] array is stored on the guide and shown in the HAWB detail view, but is not declared line-by-line to customs.

Platform / Identifiers / Order ​

FieldTypeRequiredNotes
platform_namestring | nulloptionalMarketplace / source tag for the guide (e.g. "Shein"). Persisted to guides.platform_name. Send it per row: when a single importer consolidates orders from several sub-clients / marketplaces, this is what lets each guide be attributed to its origin later β€” your token identifies the importer, not which sub-client the guide came from. For consolidator clients the value is the resolved platform name (see platform_rut below); for everyone else it's the literal tag you send.
platform_rutstring | nulloptional β€” required key for consolidator clientsChilean RUT of the platform / marketplace that owns the merchandise (Falabella, AliExpress, Shein, Temu, …). Accepts dots/dash or plain digits; normalized server-side and matched against registered companies β†’ resolves guides.platform_company_id (+ the platform_name snapshot). null / invalid / no-match β‡’ guide is treated as platform not resolved (CL: no inscrita) and is not rejected for it.
merchant_namestring | nulloptional β€” required key for consolidator clientsName of the seller / store within the platform (free text, e.g. "Tienda XYZ"). Persisted to guides.merchant_name. Display/trace only β€” does not decide customs classification. Distinct from shipper_name (physical sender) and platform_name (the marketplace).
external_guide_numberstring | nulloptionalOrigin courier's own folio; surfaced by last-mile search.
order_created_atstringrequired on CL and PE, optional on ARISO 8601. Purchase date β€” when the end customer placed the order (distinct from created_at, the guide's ingest timestamp). Persisted to guides.order_created_at on the CL path; PE accepts it without persisting it yet, but is still required so CL and PE share one payload shape.

Consolidator clients. A consolidator is a client that ships parcels belonging to different end-clients / marketplaces under a single token β€” one importer carrying orders from many stores (Falabella, AliExpress, Shein, …) in the same manifest. A normal client ships only its own cargo. For consolidators the keys platform_rut and merchant_name must be present in every guideRow (the value may be null) β€” a missing key returns 400 validation_error. This validates structure, not per-row data: platform_rut: null is valid and simply classifies the guide as platform not-resolved. The platform's source of truth is platform_rut (the RUT), never a free-text name.

Per-country effect of platform_rut:

  • CL β€” the resolved platform's IVA-registration drives the customs code (inscrita L-IS / D-IS / D-IN / PD-IN vs no inscrita PD-NG / D-NG).
  • AR β€” identity capture only: platform_company_id / merchant_name are persisted, but the AR customs model (CUIT / NCM) does not branch on platform registration.
  • PE β€” platform_rut is not yet wired into the PE pipeline (accepted, not resolved).

IVA override / CL-specific fields ​

FieldTypeNotes
iva_overrideboolRequired on CL and PE β€” send an explicit true or false; null or omitted is rejected with field_empty. It states whether IVA was prepaid for this guide and drives the CL duty-calc branch, so guessing it from the company default was ambiguous. PE does not act on it today but requires it to keep one shared payload shape. Optional on AR, where the default derives from the sender company's is_iva_registered flag.
internal_numberstringRequired on CL and PE. CL uses it for bigbox tracking (passthrough to the bigbox detection RPC); PE does not consume it, but requires it so the row shape matches CL. AR: send null.

PE fields (when the destination resolves to Peru) ​

PE has its own customs model (SUNAT). The doc number rides in consignee_rut (same slot as CL/AR).

FieldRule
consignee_rutRUC 11-digit (mod-11) or DNI 8-digit or passport/other (non-empty)
consignee_staterequired non-empty
consignee_zip_coderequired non-empty
internal_number, order_created_atCL-only β†’ may be null
iva_overrideOptional on PE; may be omitted (default derives from company's is_iva_registered).

Passthrough (required key, may be empty) ​

FieldTypeNotes
extra_dataobjectFree-form passthrough. Send {} when empty β€” never null.

Validation rules ​

The server checks things in this order. The first failure stops everything β€” if a request is rejected, nothing is created (no manifest, no guides, no flight). So a 400/403 means you can safely fix and retry.

The checks, in order:

  1. Token β†’ 401 missing_api_key (no header) Β· 401 invalid_api_key (token doesn't match).

  2. Destination β†’ country β†’ 400 unsupported_transport_mode Β· 400 destination_unresolved Β· 403 country_not_allowed. (See One token, any country.)

  3. Owner (only when you send client_code) β†’ 403 client_code_not_allowed Β· 403 client_not_resolved. (See Acting on behalf of a client.)

  4. JSON shape β†’ 400 validation_failed (wrong types, missing keys, bad enums).

  5. Duplicate MAWB β†’ 409 mawb_already_exists (echoes the mawb; we never overwrite).

  6. Rows β†’ 400 manifest_validation_failed. All-or-nothing: one bad row rejects the whole manifest, and invalid_rows[] lists every problem so you can fix them in one pass. Multi-product derivation runs before this step β€” when a row has products[], the server fills weight_kg/fob_usd/description_original/hs_code first, then validates the derived values (so an empty derived hs_code still trips field_empty here). Two kinds of row problem:

    • Empty required field β†’ error_code: "field_empty". Every guide field must be filled except the carve-outs in the completeness table above.

    • Bad value for your destination country:

      Unified gate β€” every country requires weight_kg, fob_usd, freight_usd all > 0, plus consignee_state and consignee_zip_code non-empty. Only the document check differs:

      CountryDocument (consignee_rut)Extra
      πŸ‡¨πŸ‡± CLChilean RUT (mod-11; -P passport bypass)internal_number required; iva_override optional (default from is_iva_registered)
      πŸ‡¦πŸ‡· ARCUIT/CUIL (or DNI, non-empty)consignee_type required (drives cupo)
      πŸ‡΅πŸ‡ͺ PERUC (11 digits) / DNI (8) / passportβ€”
  7. Cainiao fields β†’ 422 cainiao_fields_missing / 422 cainiao_fields_not_allowed. (See Cainiao clients.)

  8. Account checks β†’ 400 company_is_courier (a courier token with no client_code β€” the manifest has no client owner) Β· 400 tracking_prefix_missing (the owner has no tracking prefix β€” contact ops).


Country-specific behavior ​

It's the same call for every country β€” only the customs handling differs by destination. Each country page covers its document rules, customs model, duties, and the extra response fields you get back:

CountryWhat's specific
πŸ‡¨πŸ‡± ChileRUT validation, IVA customs_status codes, ad-valorem + IVA duties, bigbox grouping (internal_number).
πŸ‡΅πŸ‡ͺ PeruRUC/DNI, SUNAT categories 1–4, anti-split rule, Category-4 retention, IGV duties.
πŸ‡¦πŸ‡· ArgentinaCUIT/CUIL, cupo check, dispatcherDocument / cargoReference / licensePlate, orphan auto-attach.

Integrating for one country? Read this page (the shared contract) plus your country's page above. That's everything you need.


Response (success) ​

201 Created. The shared shape is the same everywhere:

The MAWB is the identifier. mawbNumber is the key you send, the idempotency key, and the path parameter of the read endpoints β€” everything you need to find a manifest again. LogirAI's internal row id is not part of this contract; don't store it or branch on it.

json
{
  "success": true,
  "guides_created": 2,
  "guides": [{ "hawb": "AWB-001", "tracking_code": "XX-A1B2C3" }],
  "flight_id": "uuid",          // null on ground
  "transport_mode": "air",
  "errors": [],
  "warnings": []
}

Each country adds a few extra fields (e.g. cargo_reference for AR, rut_invalid_count for CL, pe_category_counts / retained_count for PE). See your country page β€” Chile Β· Peru Β· Argentina.

Read endpoints ​

Poll status with the same api_password bearer. All reads are scoped to your company β€” a MAWB or tracking code owned by another company returns 404 not_found. Auth failures use the same 401/401/403 tree as the write endpoint.

GET /api/manifests/client/mawbs ​

Your 50 most recent MAWBs with per-status guide counts.

json
{
  "mawbs": [
    {
      "mawb": "999-87654321",
      "status": "registered",
      "eta": "2026-04-18T14:00:00-03:00",
      "total_guides": 120,
      "total_weight_kg": 340.5,
      "total_pieces": 145,
      "total_fob_usd": 8200.0,
      "guides_by_status": { "registered": 118, "in_customs": 2 },
      "created_at": "2026-04-17T10:00:00Z"
    }
  ]
}

GET /api/manifests/client/mawbs/:mawb ​

A single MAWB's detail + per-guide status. 404 if not owned.

json
{
  "mawb": "999-87654321",
  "status": "registered",
  "eta": "2026-04-18T14:00:00-03:00",
  "total_guides": 2,
  "total_weight_kg": 7.5,
  "total_pieces": 3,
  "total_fob_usd": 169.9,
  "guides_by_status": { "registered": 2 },
  "guides": [
    { "hawb": "CL-AWB-001", "tracking_code": "CN-A1B2C3", "status": "registered", "status_code": 0 }
  ]
}

GET /api/manifests/client/guides/:tracking_code ​

A single guide's detail + ascending event timeline. 404 if not owned.

json
{
  "hawb": "CL-AWB-001",
  "tracking_code": "CN-A1B2C3",
  "mawb": "999-87654321",
  "status": "registered",
  "status_code": 0,
  "consignee_name": "Juan PΓ©rez",
  "weight_kg": 2.4,
  "packages_count": 1,
  "description_original": "Wireless bluetooth earphones",
  "fob_usd": 49.9,
  "events": [
    { "status": "registered", "status_code": 0, "timestamp": "2026-04-17T10:00:00Z", "source": "system" }
  ]
}

Error responses ​

StatusBody shapeWhen
401{ success: false, error: "missing_api_key" }No Authorization: Bearer header.
401{ success: false, error: "invalid_api_key" }Token matches no company's api_password.
403{ success: false, error: "unsupported_country", country }Your company has no country this endpoint serves (checked before schema).
403{ success: false, error: "client_code_not_allowed", reason }You sent client_code from a client token. Only a courier may act for a client.
403{ success: false, error: "client_not_resolved", client_code }client_code matches no active client company.
400{ success: false, error: "validation_failed", details: { fieldErrors } }Zod schema failed.
400{ success: false, error: "manifest_validation_failed", invalid_rows: [...], warnings }Completeness (error_code: "field_empty") or country gate (code: "value_*"/"invalid_format").
422{ success: false, error: "cainiao_fields_missing" | "cainiao_fields_not_allowed", details: { invalid_rows } }See Cainiao clients.
400{ success: false, error, reason: "company_is_courier", company_name }A courier token with no client_code β€” nothing owns the manifest.
400{ success: false, error: "tracking_prefix_missing", company_id, reason }Company has no tracking prefix (set it in admin).
409{ success: false, error: "mawb_already_exists", mawb }MAWB already used β€” no replace/upsert (D10).
404{ success: false, error: "not_found" }Read target not owned by your company / missing.
5xx{ success: false, error: "internal_server_error" }Internal error (DB, RPC, etc.).

invalid_rows[] per failed row: completeness β†’ { row_index, hawb, field, error_code: "field_empty", severity: "error", message }; country gate β†’ { row_index, hawb, field, code, message }. row_index mirrors the source line (header offset +2).


Differences vs the Excel endpoint ​

AspectExcel (/process-pre-alert)JSON (/process-pre-alert-json)
InputexcelBase64 + flightData + totalsguideRows (canonical)
Server-side parsingYes (per-company parser dispatch)No
Per-company format detectionImplicit (parser fails on wrong format)N/A β€” company is token-derived, contract is fixed canonical JSON
TotalsCaller-supplied + recomputedRecomputed server-side
Description translation (CL)Yes (Gemini bulk)Yes (Gemini bulk, same code path)
Validation gateYesYes
Courier guardNo (Excel path inherited behavior)Yes β€” unless client_code names the owner
Multi-tenant (client_code)NoYes, optional
Country supportAR + CLCL + AR + PE

PE already routes through this endpoint (destination LIM).


Full request example (CL) ​

A D13-compliant payload β€” every guide field is non-empty except extra_data: {} (passthrough). cif_usd is omitted (server-computed). companyId/country/preAlertId and the metadata fields are omitted (server-derived). AR-only header fields are null.

There are two flavors depending on the client type (see Consolidator clients):

  • Normal client β€” ships its own cargo. platform_rut / merchant_name are not required.
  • Consolidator client β€” ships parcels belonging to different end-clients / marketplaces under one token. Every guideRow MUST carry platform_rut + merchant_name (value may be null).

Normal client (CL) ​

bash
curl -X POST https://api.logirai.com/api/manifests/process-pre-alert-json \
  -H "Authorization: Bearer $API_PASSWORD" \
  -H "Content-Type: application/json" \
  -d @body.json
json
{
  "mawbNumber": "999-87654321",
  "transportMode": "air",
  "flightData": {
    "flight_number": "LA801",
    "origin_airport": "PVG",
    "destination_airport": "SCL",
    "origin_country": "CN",
    "scheduled_departure": "2026-04-18T02:00:00+08:00",
    "scheduled_arrival": "2026-04-18T14:00:00-03:00"
  },
  "dispatcherDocument": null,
  "cargoReference": null,
  "licensePlate": null,
  "guideRows": [
    {
      "hawb": "CL-AWB-001",
      "consignee_name": "Juan PΓ©rez",
      "consignee_rut": "12.345.678-5",
      "consignee_address": "Av. Apoquindo 4500, Of. 1201",
      "consignee_city": "Las Condes",
      "consignee_state": "RM",
      "consignee_zip_code": "7550000",
      "consignee_phone": "+56912345678",
      "consignee_email": "juan.perez@example.cl",
      "consignee_country": "CL",
      "shipper_name": "Shanghai Trading Co.",
      "shipper_address": "123 Nanjing Road",
      "shipper_city": "Shanghai",
      "shipper_country": "CN",
      "packages_count": 1,
      "weight_kg": 2.4,
      "description_original": "Wireless bluetooth earphones",
      "hs_code": "8518.30.0000",
      "fob_usd": 49.9,
      "product_value": 49.9,
      "freight_usd": 8.5,
      "insurance_usd": 1.0,
      "currency": "USD",
      "external_guide_number": "EXT-CL-001",
      "order_created_at": "2026-04-15T03:21:00Z",
      "internal_number": "BBX-001",
      "iva_override": false,
      "extra_data": {}
    }
  ]
}

Consolidator client (CL) ​

Same shape as above, but the client is a consolidator, so every guideRow includes platform_rut and merchant_name (value may be null). Below, the first guide resolves to a registered platform (Falabella β†’ inscrita); the second sends platform_rut: null (β†’ no inscrita). Both keys are present, so both rows are valid.

bash
curl -X POST https://api.logirai.com/api/manifests/process-pre-alert-json \
  -H "Authorization: Bearer $API_PASSWORD" \
  -H "Content-Type: application/json" \
  -d @body-consolidator.json
json
{
  "mawbNumber": "999-CONSOL-01",
  "transportMode": "air",
  "flightData": {
    "flight_number": "LA801",
    "origin_airport": "PVG",
    "destination_airport": "SCL",
    "origin_country": "CN",
    "scheduled_departure": "2026-04-18T02:00:00+08:00",
    "scheduled_arrival": "2026-04-18T14:00:00-03:00"
  },
  "dispatcherDocument": null,
  "cargoReference": null,
  "licensePlate": null,
  "guideRows": [
    {
      "hawb": "CL-CONSOL-001",
      "consignee_name": "Juan PΓ©rez",
      "consignee_rut": "12.345.678-5",
      "consignee_address": "Av. Apoquindo 4500, Of. 1201",
      "consignee_city": "Las Condes",
      "consignee_state": "RM",
      "consignee_zip_code": "7550000",
      "consignee_phone": "+56912345678",
      "consignee_email": "juan.perez@example.cl",
      "consignee_country": "CL",
      "shipper_name": "Shanghai Trading Co.",
      "shipper_address": "123 Nanjing Road",
      "shipper_city": "Shanghai",
      "shipper_country": "CN",
      "packages_count": 1,
      "weight_kg": 2.4,
      "description_original": "Wireless bluetooth earphones",
      "hs_code": "8518.30.0000",
      "fob_usd": 49.9,
      "product_value": 49.9,
      "freight_usd": 8.5,
      "insurance_usd": 1.0,
      "currency": "USD",
      "order_created_at": "2026-04-15T03:21:00Z",
      "internal_number": "BBX-001",
      "iva_override": false,
      "platform_rut": "76.212.492-0",
      "merchant_name": "Tienda XYZ",
      "extra_data": {}
    },
    {
      "hawb": "CL-CONSOL-002",
      "consignee_name": "MarΓ­a Soto",
      "consignee_rut": "9.876.543-3",
      "consignee_address": "Los Leones 567",
      "consignee_city": "Providencia",
      "consignee_state": "RM",
      "consignee_zip_code": "7510000",
      "consignee_phone": "+56987654321",
      "consignee_email": "maria.soto@example.cl",
      "consignee_country": "CL",
      "shipper_name": "Shenzhen Gadgets",
      "shipper_address": "88 Futian Ave",
      "shipper_city": "Shenzhen",
      "shipper_country": "CN",
      "packages_count": 1,
      "weight_kg": 0.6,
      "description_original": "Phone case",
      "hs_code": "3926.90.9090",
      "fob_usd": 12.5,
      "product_value": 12.5,
      "freight_usd": 3.0,
      "insurance_usd": 0.1,
      "currency": "USD",
      "order_created_at": "2026-04-16T10:00:00Z",
      "internal_number": "BBX-002",
      "iva_override": false,
      "platform_rut": null,
      "merchant_name": null,
      "extra_data": {}
    }
  ]
}

For an AR payload the same row shape applies, except:

  • dispatcherDocument carries the despachante document (required β€” the AIDA TXT generator throws without it); cargoReference is optional (null β†’ server auto-generates); licensePlate only on ground.
  • Each row sets consignee_type ("C"/"P") and consignee_document_type ("CUIT"/"CUIL"/"DNI"). CUIT/CUIL are mod-11 validated.
  • CL-only field internal_number is sent as null (AR-exempt). iva_override is optional everywhere β€” sending null is valid; the default derives from the company's is_iva_registered flag.
bash
curl -X POST https://api.logirai.com/api/manifests/process-pre-alert-json \
  -H "Authorization: Bearer $API_PASSWORD" \
  -H "Content-Type: application/json" \
  -d @body-ar.json
json
{
  "mawbNumber": "111-22334455",
  "transportMode": "air",
  "flightData": {
    "flight_number": "LA8074",
    "origin_airport": "PVG",
    "destination_airport": "EZE",
    "origin_country": "CN",
    "scheduled_departure": "2026-04-18T02:00:00+08:00",
    "scheduled_arrival": "2026-04-18T18:30:00-03:00"
  },
  "dispatcherDocument": "30715432",
  "cargoReference": null,
  "licensePlate": null,
  "guideRows": [
    {
      "hawb": "AR-AWB-001",
      "consignee_name": "MarΓ­a GonzΓ‘lez",
      "consignee_rut": "20312345677",
      "consignee_type": "P",
      "consignee_document_type": "CUIL",
      "consignee_address": "Av. Corrientes 1234, Piso 5",
      "consignee_city": "Buenos Aires",
      "consignee_state": "C",
      "consignee_zip_code": "C1043AAZ",
      "consignee_phone": "+541145678900",
      "consignee_email": "maria.gonzalez@example.com.ar",
      "consignee_country": "AR",
      "shipper_name": "Shanghai Trading Co.",
      "shipper_address": "123 Nanjing Road",
      "shipper_city": "Shanghai",
      "shipper_country": "CN",
      "packages_count": 1,
      "weight_kg": 1.8,
      "description_original": "Wireless bluetooth earphones",
      "hs_code": "8518.30.0000",
      "fob_usd": 39.9,
      "product_value": 39.9,
      "freight_usd": 7.0,
      "insurance_usd": 1.0,
      "currency": "USD",
      "external_guide_number": "EXT-AR-001",
      "order_created_at": "2026-04-15T03:21:00Z",
      "internal_number": null,
      "iva_override": null,
      "extra_data": {}
    }
  ]
}

Scale considerations ​

This endpoint is designed for batch ingestion and routinely receives manifests with thousands of guides per request. Plan around the following limits.

Practical request size ​

DimensionLimitNotes
HTTP body50 MBExpress json limit. With ~1 KB per row, this comfortably handles up to ~30 000 rows.
guideRows[] lengthNo hard schema capPractical ceiling per request: see DB and processing notes below.
Single MAWB per request1One manifest per call. To split a large shipment, send multiple requests with distinct mawbNumber values.

Server-side processing notes ​

  • Postgres bulk insert β€” guides are inserted with a single INSERT … VALUES (…) statement. Postgres has a hard cap of 65 535 bind parameters per query; with ~40 columns per row the maximum safe batch is β‰ˆ1 600 rows. Requests significantly larger than this currently risk failing on insert. Until the server-side chunking is in place, prefer batching on the client side at ≀ 1 500 rows per request.
  • CL Gemini translation β€” description_original is sent in a single bulk call. Translation is non-fatal (failure falls back to the raw description), but very large batches may time out and degrade per row.
  • AR cupo lookup β€” consignee_type = 'P' rows are validated against the cupo service in a single batch call. The external API may rate-limit on very large payloads.
  • All-or-nothing gate β€” rejects the whole prealert if any row fails. The error body returns the full invalid_rows list, which can be large for big batches; clients should be prepared to parse arrays of thousands of entries.
  • HTTP timeout β€” large requests can take tens of seconds. Set client timeouts to at least 120 s for batches above ~5 000 rows.
  1. Split the manifest into chunks of 1 000–1 500 rows by hawb.
  2. Send each chunk as a separate request with its own mawbNumber (e.g. MAWB-PART-01, MAWB-PART-02).
  3. Process responses sequentially or with a small concurrency cap (≀ 3 in flight); the server-side cupo and Gemini calls are not idempotent and large parallel bursts may rate-limit.

Native server-side chunking of bulk inserts is on the roadmap. Once it ships, single-request manifests up to 30 000 rows will be supported transparently.

LogirAI β€” Cross-Border Logistics API for Retailers and Marketplaces