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/jsonAuth: 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
countryin the body (it's ignored now), and (2) set the destination correctly for your mode (destination_airportfor air,destination_countryfor 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) | Meaning | Fix |
|---|---|---|
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:
{
"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_coderesolves 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_codedoes 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:
| Owner | extra_data.cainiao_cop_no + cainiao_waybill | Result |
|---|---|---|
| Cainiao / AliExpress | both present and non-empty | accepted |
| Cainiao / AliExpress | either missing | 422 cainiao_fields_missing |
| any other client | either present | 422 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 country | flightData.destination_airport (IATA code) | flightData.destination_country (ISO-2 code) |
flightData required? | Yes β with at least destination_airport | Yes β with at least destination_country (do not send null) |
Other flightData fields | flight_number, origin_airport, origin_country, scheduled_departure, scheduled_arrival (recommended) | not needed β leave them out |
| Flight record | A flight is created/linked (you get a flight_id) | No flight is created (flight_id is null) |
licensePlate (top-level) | leave null | optional β the delivery vehicle's plate (AR writes it to the customs file) |
Air example β
{
"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 β
{
"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:
| Field | Rule | Rejected example |
|---|---|---|
flight_number | Must 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_arrival | Must 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 astransportMode: "air"will fail with400 destination_unresolved, because there is no airport to resolve a country from. Ocean legs go throughtransportMode: "ground"withflightData.destination_country.
Ground gotcha:
flightDatais still required for ground β it carriesdestination_country. SendingflightData: null(or omittingdestination_country) returns400 destination_unresolved. The airport/flight fields are simply ignored on ground.
Request body β
{
"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 with400 manifest_validation_failedandinvalid_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 explicittrue/false) and, as before,internal_number. Five stopped being required on CL and PE β the fourshipper_*fields andinsurance_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 {}(nevernull) when empty.external_guide_number,consignee_document_typealways optional May be omitted or nullfor every country.shipper_name,shipper_address,shipper_city,shipper_country,insurance_usdCL / PE Optional on Chile and Peru; required on AR, which writes them into the customs file. See Contract aligned with the courier gate. consignee_typeCL / PE AR-only (drives the cupo lookup); CL/PE may omit it. internal_number,order_created_at,iva_overrideAR only CL and PE share one row contract β both must fill all three. Only Argentina is exempt. dispatcherDocument,cargoReference,licensePlate(top-level)CL / PE: always AR manifest-header fields ( dispatcherDocumentrequired;cargoReferenceβ auto-gen,licensePlateground-only).flightData(top-level)never Required for both modes β air carries destination_airport, ground carriesdestination_country. See Air vs Ground.
Top-level fields β
companyId,country, andpreAlertIdare server-derived from your token β do not send them. If you do, they are ignored.
| Field | Type | Notes |
|---|---|---|
mawbNumber | string | The MAWB. Must be unique across the system (duplicate β 409). |
client_code | string | null | Optional. 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. |
transportMode | enum air | ground | air derives the country from destination_airport + creates a flight; ground derives it from destination_country + skips flight creation (use licensePlate). See Air vs Ground. |
flightData | object | Required for both modes. Air β destination_airport (+ flight fields). Ground β destination_country (ISO-2). Not nullable. |
guideRows | array (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).
| Field | Type | Notes |
|---|---|---|
dispatcherDocument | string [0-9]{7,11} | null | Customs broker (despachante) document. Required for AR β the AIDA TXT generator throws without it. Must arrive at pre-alert. |
cargoReference | string (β€32) | null | Optional client override. If null, the server auto-generates <TRACKING_PREFIX><NNN> race-safe. |
licensePlate | string (β€32) | null | Ground 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_datatakes{}when empty.cif_usdis 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 β
| Field | Type | Notes |
|---|---|---|
hawb | string | Non-empty. House Air Waybill β unique per row. |
Consignee (all required) β
| Field | Type | Notes |
|---|---|---|
consignee_name | string | |
consignee_rut | string | CL: RUT mod-11 (12.345.678-5). AR: CUIT/CUIL 20XXXXXXXX9. |
consignee_type | enum C | P | AR: required. Drives cupo lookup β P (particular) triggers cupo, C (comercial) skips it. CL/PE: optional (ignored on CL; passthrough on PE). |
consignee_document_type | enum CUIT | CUIL | DNI | RUT | OTHER | Optional 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_address | string | |
consignee_city | string | |
consignee_state | string | AR: ISO 2-letter province code (e.g. C for CABA). |
consignee_zip_code | string | |
consignee_phone | string | |
consignee_email | string | |
consignee_country | string |
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.
| Field | Type |
|---|---|
shipper_name | string (AR) | null (CL/PE) |
shipper_address | string (AR) | null (CL/PE) |
shipper_city | string (AR) | null (CL/PE) |
shipper_country | string (AR) | null (CL/PE) |
Package (all required) β
| Field | Type | Notes |
|---|---|---|
packages_count | int β₯ 1 | |
weight_kg | number | All-or-nothing gate β must be > 0. Derived (Ξ£ product weights) when products[] is sent β pass null then. |
description_original | string | CL: input to Gemini translation. Derived (joined product descriptions) when products[] is sent. |
hs_code | string | Derived (distinct product codes joined with " - ") when products[] is sent β see Multi-product. |
Financial (all required) β
| Field | Type | Notes |
|---|---|---|
fob_usd | number | All-or-nothing gate β must be > 0. Derived (Ξ£ qty Γ unit_value) when products[] is sent β pass null then. |
product_value | number | Derived (= derived fob_usd) when products[] is sent. |
freight_usd | number | All-or-nothing gate β must be > 0 for CL, AR and PE (unified gate). |
insurance_usd | number | null | Required on AR (AIDA record 02), optional on CL/PE. On CL it still feeds the CIF, so omitting it lowers the duties. |
currency | string |
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:
| Field | Type | Notes |
|---|---|---|
description | string | Required, non-empty. |
quantity | int β₯ 1 | Defaults to 1 if omitted. |
unit_value | number β₯ 0 | Required. Value per unit. |
weight_kg | number β₯ 0 | Required. Weight of the line. |
hs_code | string | null | Optional per product. |
sku | string | null | Optional. |
brand | string | null | Optional. |
model | string | null | Optional. |
Derivation rules:
| Guide field | Derived from products as |
|---|---|
weight_kg | Ξ£ product.weight_kg |
fob_usd | Ξ£ (product.quantity Γ product.unit_value) |
product_value | same as derived fob_usd |
description_original | product descriptions joined with " - " |
hs_code | the 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):
{
"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 β
| Field | Type | Required | Notes |
|---|---|---|---|
platform_name | string | null | optional | Marketplace / 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_rut | string | null | optional β required key for consolidator clients | Chilean 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_name | string | null | optional β required key for consolidator clients | Name 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_number | string | null | optional | Origin courier's own folio; surfaced by last-mile search. |
order_created_at | string | required on CL and PE, optional on AR | ISO 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_rutandmerchant_namemust be present in everyguideRow(the value may benull) β a missing key returns400 validation_error. This validates structure, not per-row data:platform_rut: nullis valid and simply classifies the guide as platform not-resolved. The platform's source of truth isplatform_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-INvs no inscritaPD-NG / D-NG).- AR β identity capture only:
platform_company_id/merchant_nameare persisted, but the AR customs model (CUIT / NCM) does not branch on platform registration.- PE β
platform_rutis not yet wired into the PE pipeline (accepted, not resolved).
IVA override / CL-specific fields β
| Field | Type | Notes |
|---|---|---|
iva_override | bool | Required 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_number | string | Required 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).
| Field | Rule |
|---|---|
consignee_rut | RUC 11-digit (mod-11) or DNI 8-digit or passport/other (non-empty) |
consignee_state | required non-empty |
consignee_zip_code | required non-empty |
internal_number, order_created_at | CL-only β may be null |
iva_override | Optional on PE; may be omitted (default derives from company's is_iva_registered). |
Passthrough (required key, may be empty) β
| Field | Type | Notes |
|---|---|---|
extra_data | object | Free-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:
Token β
401 missing_api_key(no header) Β·401 invalid_api_key(token doesn't match).Destination β country β
400 unsupported_transport_modeΒ·400 destination_unresolvedΒ·403 country_not_allowed. (See One token, any country.)Owner (only when you send
client_code) β403 client_code_not_allowedΒ·403 client_not_resolved. (See Acting on behalf of a client.)JSON shape β
400 validation_failed(wrong types, missing keys, bad enums).Duplicate MAWB β
409 mawb_already_exists(echoes themawb; we never overwrite).Rows β
400 manifest_validation_failed. All-or-nothing: one bad row rejects the whole manifest, andinvalid_rows[]lists every problem so you can fix them in one pass. Multi-product derivation runs before this step β when a row hasproducts[], the server fillsweight_kg/fob_usd/description_original/hs_codefirst, then validates the derived values (so an empty derivedhs_codestill tripsfield_emptyhere). 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_usdall> 0, plusconsignee_stateandconsignee_zip_codenon-empty. Only the document check differs:Country Document ( consignee_rut)Extra π¨π± CL Chilean RUT (mod-11; -Ppassport bypass)internal_numberrequired;iva_overrideoptional (default fromis_iva_registered)π¦π· AR CUIT/CUIL (or DNI, non-empty) consignee_typerequired (drives cupo)π΅πͺ PE RUC (11 digits) / DNI (8) / passport β
Cainiao fields β
422 cainiao_fields_missing/422 cainiao_fields_not_allowed. (See Cainiao clients.)Account checks β
400 company_is_courier(a courier token with noclient_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:
| Country | What's specific |
|---|---|
| π¨π± Chile | RUT validation, IVA customs_status codes, ad-valorem + IVA duties, bigbox grouping (internal_number). |
| π΅πͺ Peru | RUC/DNI, SUNAT categories 1β4, anti-split rule, Category-4 retention, IGV duties. |
| π¦π· Argentina | CUIT/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.
mawbNumberis 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.
{
"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.
{
"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.
{
"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.
{
"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 β
| Status | Body shape | When |
|---|---|---|
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 β
| Aspect | Excel (/process-pre-alert) | JSON (/process-pre-alert-json) |
|---|---|---|
| Input | excelBase64 + flightData + totals | guideRows (canonical) |
| Server-side parsing | Yes (per-company parser dispatch) | No |
| Per-company format detection | Implicit (parser fails on wrong format) | N/A β company is token-derived, contract is fixed canonical JSON |
| Totals | Caller-supplied + recomputed | Recomputed server-side |
| Description translation (CL) | Yes (Gemini bulk) | Yes (Gemini bulk, same code path) |
| Validation gate | Yes | Yes |
| Courier guard | No (Excel path inherited behavior) | Yes β unless client_code names the owner |
Multi-tenant (client_code) | No | Yes, optional |
| Country support | AR + CL | CL + 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_nameare not required. - Consolidator client β ships parcels belonging to different end-clients / marketplaces under one token. Every
guideRowMUST carryplatform_rut+merchant_name(value may benull).
Normal client (CL) β
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{
"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.
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{
"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:
dispatcherDocumentcarries the despachante document (required β the AIDA TXT generator throws without it);cargoReferenceis optional (nullβ server auto-generates);licensePlateonly onground.- Each row sets
consignee_type("C"/"P") andconsignee_document_type("CUIT"/"CUIL"/"DNI"). CUIT/CUIL are mod-11 validated. - CL-only field
internal_numberis sent asnull(AR-exempt).iva_overrideis optional everywhere β sendingnullis valid; the default derives from the company'sis_iva_registeredflag.
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{
"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 β
| Dimension | Limit | Notes |
|---|---|---|
| HTTP body | 50 MB | Express json limit. With ~1 KB per row, this comfortably handles up to ~30 000 rows. |
guideRows[] length | No hard schema cap | Practical ceiling per request: see DB and processing notes below. |
| Single MAWB per request | 1 | One 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_originalis 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_rowslist, 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.
Recommended client pattern for β₯ 5 000 guides β
- Split the manifest into chunks of 1 000β1 500 rows by
hawb. - Send each chunk as a separate request with its own
mawbNumber(e.g.MAWB-PART-01,MAWB-PART-02). - 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.