CX Crossdocker API v1

Crossdocker API

Con esta API usted da de alta en Crossdocker los paquetes y los manifiestos que llegan a la bodega.

Crossdocker recibe la carga en la bodega, la cuadra con sus manifiestos y entrega cada paquete a su transportista de última milla. Cuando ese transportista tiene integración, Crossdocker le envía el paquete en el mismo momento y le devuelve a usted su respuesta.

Todas las peticiones y respuestas son JSON, con la codificación de caracteres UTF-8 (la que admite tildes, ñ y cualquier alfabeto). Toda petición lleva un token en Authorization: Bearer.

Use this API to register in Crossdocker the packages and manifests that arrive at the warehouse.

Crossdocker receives the cargo at the warehouse, reconciles it against its manifests and hands each package over to its last-mile carrier. When that carrier is integrated, Crossdocker forwards the package to it immediately and returns the carrier's response to you.

All requests and responses are JSON, with UTF-8 character encoding (which supports accents and any alphabet). Every request carries a token in Authorization: Bearer.

通过此 API,您可以在 Crossdocker 中登记到达仓库的包裹和舱单。

Crossdocker 在仓库接收货物,将其与舱单核对,并把每个包裹交给对应的末端配送承运商。如果该承运商已完成对接,Crossdocker 会立即把包裹转发给它,并把承运商的响应原样返回给您。

所有请求和响应均为 JSON,字符编码为 UTF-8(支持重音字母和任何文字,包括中文)。每个请求都需要在 Authorization: Bearer 中携带令牌。

URL base de la APIAPI base URLAPI 基础 URL

https://crossdocker.nextmile.tech/api

URL del tokenToken URL令牌 URL

https://crossdocker.nextmile.tech/auth/realms/crossdocker/protocol/openid-connect/token
RecorridoFlow流程
1. POST token            → access_token (8 h)
2. POST /v1/package      → un paquete cada vez
3. POST /v1/manifest     → el manifiesto, con los
                           paquetes ya creados
4. GET  /v1/package/list → estados (opcional)1. POST token            → access_token (8 h)
2. POST /v1/package      → one package at a time
3. POST /v1/manifest     → the manifest, listing
                           packages already created
4. GET  /v1/package/list → status (optional)1. POST 令牌             → access_token(8 小时)
2. POST /v1/package      → 每次一个包裹
3. POST /v1/manifest     → 舱单,列出已创建的包裹
4. GET  /v1/package/list → 状态(可选)

Si ya usa la API courier de MoovinIf you already use Moovin's courier API如果您已在使用 Moovin 快递 API

Esta API tiene el mismo contrato que la API courier de Moovin: los mismos cuerpos, las mismas respuestas y los mismos errores. La única diferencia es que los mensajes van siempre en inglés: los que Moovin da en español, Crossdocker los da traducidos. Solo cambian dos URL y el usuario:

This API has the same contract as Moovin's courier API: the same bodies, the same responses and the same errors. The only difference is that messages are always in English: those Moovin returns in Spanish, Crossdocker returns translated. Only two URLs and the user change:

本 API 与 Moovin 快递 API 的契约完全相同:请求体、响应和错误都一样。唯一的区别是信息一律使用英文:Moovin 以西班牙语返回的信息,Crossdocker 会翻译成英文返回。只需更改两个 URL 和用户:

MoovinCrossdocker
URL del tokenToken URL令牌 URLhttps://auth.moovin.me/auth/realms/morannon/protocol/openid-connect/tokenhttps://crossdocker.nextmile.tech/auth/realms/crossdocker/protocol/openid-connect/token
URL baseBase URL基础 URL…/moovin-courier/apihttps://crossdocker.nextmile.tech/api
client_idmorannon-apimorannon-api
Usuario y contraseñaUsername and password用户名和密码los de Moovinyour Moovin onesMoovin 提供的los que le da Crossdockerthe ones Crossdocker gives youCrossdocker 提供的
RutasPaths路径iguales:identical:相同: /v1/package, /v1/manifest, /v2/manifest, /v1/package/list
Los paquetes de Moovin siguen llegando a Moovin: Crossdocker se los envía en su nombre y le devuelve la respuesta de Moovin tal cual, con su idPackage.
Moovin packages still reach Moovin: Crossdocker sends them on your behalf and returns Moovin's response unchanged, including its idPackage.
Moovin 的包裹仍会送达 Moovin:Crossdocker 代表您发送,并原样返回 Moovin 的响应,包括其 idPackage。

EntornosEnvironments环境

Hay un solo servidor. Lo que distingue el entorno es la credencial: Crossdocker le da una para desarrollo y otra para producción.

  • Desarrollo: para integrar y probar. Los paquetes se envían a los entornos de prueba de los transportistas.
  • Producción: paquetes reales, que llegan a la bodega y a los transportistas.

Las dos credenciales son independientes: lo que se crea con una no se ve con la otra.

There is a single server. The credential determines the environment: Crossdocker gives you one for development and another for production.

  • Development: for integration and testing. Packages are sent to the carriers' test environments.
  • Production: real packages, which reach the warehouse and the carriers.

The two credentials are independent: what you create with one is not visible with the other.

只有一个服务器,环境由凭据决定:Crossdocker 会分别提供开发凭据和生产凭据。

  • 开发:用于对接和测试。包裹会发送到承运商的测试环境。
  • 生产:真实包裹,会进入仓库并交给承运商。

两套凭据相互独立:用一套凭据创建的数据,另一套看不到。

AutenticaciónAuthentication身份验证

POST https://crossdocker.nextmile.tech/auth/realms/crossdocker/protocol/openid-connect/token

Pida un token con su usuario y su contraseña (OAuth 2.0, grant_type=password). El cuerpo va como application/x-www-form-urlencoded.

El token dura 8 horas (expires_in, en segundos). Reutilícelo hasta que caduque: no pida uno por petición.

Sin token, o con uno caducado o de otro sistema, la API responde 401 con el cuerpo vacío.

Request a token with your username and password (OAuth 2.0, grant_type=password). The body is application/x-www-form-urlencoded.

The token lasts 8 hours (expires_in, in seconds). Reuse it until it expires: do not request one per call.

With no token, or an expired one, or one from another system, the API returns 401 with an empty body.

使用您的用户名和密码申请令牌(OAuth 2.0,grant_type=password)。请求体格式为 application/x-www-form-urlencoded。

令牌有效期为 8 小时(expires_in,单位为秒)。请在过期前重复使用,不要每次请求都申请新令牌。

如果没有令牌、令牌已过期或来自其他系统,API 返回 401,响应体为空。

CampoField字段ValorValue值
grant_type*password
client_id*morannon-api
username*su usuarioyour username您的用户名
password*su contraseñayour password您的密码
La contraseña es un secreto de su servidor: no la ponga en una aplicación móvil ni en un navegador. Si cree que se ha filtrado, pida otra a Crossdocker; la anterior deja de valer en el acto.
The password is a server-side secret: never put it in a mobile app or a browser. If you think it has leaked, ask Crossdocker for a new one; the old one stops working immediately.
密码属于服务器端机密:切勿放在移动应用或浏览器中。如果怀疑密码已泄露,请向 Crossdocker 申请新密码,旧密码会立即失效。
PeticiónRequest请求
curl -X POST \
  https://crossdocker.nextmile.tech/auth/realms/crossdocker/protocol/openid-connect/token \
  -d grant_type=password \
  -d client_id=morannon-api \
  -d username=<usuario> \
  --data-urlencode password=<contraseña>
RespuestaResponse响应 200
{
  "access_token": "eyJhbGciOiJSUzI1NiIs…",
  "expires_in": 28800,
  "token_type": "Bearer",
  …
}
UsoUsage使用
Authorization: Bearer eyJhbGciOiJSUzI1NiIs…

Crear paqueteCreate package创建包裹

POST https://crossdocker.nextmile.tech/api/v1/package

Da de alta un paquete. Se envía uno por petición y lo identifica order.id_order, que es su tracking.

  • Si last_mile_partner es un transportista con integración (por ejemplo MOOVIN), Crossdocker se lo envía en ese momento y le devuelve a usted la respuesta del transportista.
  • Si es otro transportista, el paquete se queda en Crossdocker y la respuesta tiene la misma forma, con un idPackage de Crossdocker.
  • Si falta last_mile_partner o no se reconoce, el paquete se acepta igual (200) y queda retenido en la bodega hasta que se aclare su transportista.

No distingue mayúsculas en last_mile_partner. Acepta los nombres en snake_case y en camelCase (id_order o idOrder), el peso como texto o como número, e ignora los campos que no conoce. El cuerpo completo se guarda y se reenvía al transportista sin cambios.

Registers one package per request, identified by order.id_order, its tracking number.

  • If last_mile_partner is an integrated carrier (for example MOOVIN), Crossdocker forwards the package to it immediately and returns the carrier's response to you.
  • For any other carrier, the package stays in Crossdocker and the response has the same shape, with a Crossdocker idPackage.
  • If last_mile_partner is missing or unknown, the package is still accepted (200) and held at the warehouse until its carrier is clarified.

last_mile_partner is case-insensitive. Field names are accepted in snake_case and camelCase (id_order or idOrder), the weight as a string or a number, and unknown fields are ignored. The full body is stored and forwarded to the carrier unchanged.

每次请求登记一个包裹,以 order.id_order(即运单号)作为标识。

  • 如果 last_mile_partner 是已对接的承运商(例如 MOOVIN),Crossdocker 会立即转发,并把承运商的响应返回给您。
  • 如果是其他承运商,包裹留在 Crossdocker,响应格式相同,idPackage 由 Crossdocker 分配。
  • 如果缺少 last_mile_partner 或无法识别,包裹仍会被接受(200),并暂扣在仓库,直到确认承运商。

last_mile_partner 不区分大小写。字段名可使用 snake_case 或 camelCase(id_order 或 idOrder),重量可为字符串或数字,未知字段会被忽略。完整请求体会被保存,并原样转发给承运商。

Campos principalesMain fields主要字段

CampoField字段DescripciónDescription说明
order.id_order*Tracking del paquete. Único: no se puede repetir.Package tracking number. Unique: it cannot be repeated.包裹运单号,唯一,不可重复。
order.point_delivery*Destinatario y dirección de entrega.Recipient and delivery address.收件人和派送地址。
detail*Lista de productos; no puede ir vacía.List of products; it cannot be empty.商品列表,不能为空。
detail[].ship_date_process*Fecha de proceso: ISO 8601 o /Date(ms±hhmm)/, con el desfase horario obligatorio. Costa Rica es -0600 (UTC−6): /Date(1792540800000-0600)/ = 20/10/2026 18:00 en Costa Rica.Processing date: ISO 8601 or /Date(ms±hhmm)/, where the UTC offset is mandatory. Costa Rica is -0600 (UTC−6): /Date(1792540800000-0600)/ = 20/10/2026 18:00 Costa Rica time.处理日期:ISO 8601 或 /Date(ms±hhmm)/,后者必须带时区偏移。哥斯达黎加为 -0600(UTC−6):/Date(1792540800000-0600)/ = 哥斯达黎加时间 2026/10/20 18:00。
last_mile_partneropc.Transportista de última milla, por ejemplo MOOVIN.Last-mile carrier, for example MOOVIN.末端配送承运商,例如 MOOVIN。
detail[].ship_physical_weightopc.Peso declarado en kg (texto o número).Declared weight in kg (string or number).申报重量,单位 kg(字符串或数字)。
order.service_typeopc.DELIVERY · PICKUP
PeticiónRequest请求
curl -X POST https://crossdocker.nextmile.tech/api/v1/package \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d @paquete.json
paquete.json
{
  "last_mile_partner": "MOOVIN",
  "order": {
    "service_type": "DELIVERY",
    "id_order": "CX1234567890",
    "email": "destinatario@example.com",
    "point_collect": {
      "name": "Remitente Prueba",
      "address": "Comercio de prueba 1",
      "city": "Miami", "state": "FL", "zip_code": "33101"
    },
    "point_delivery": {
      "name": "Destinatario Prueba",
      "phone": "00000000",
      "address": "Dirección de prueba 123, San José",
      "district": "Carmen", "city": "San Jose",
      "state": "San Jose", "zip_code": "10101",
      "latitude": 9.932, "longitude": -84.079
    }
  },
  "detail": [{
    "trck_nmr_fol": "CX1234567890",
    "ship_content": "Artículo de prueba",
    "ship_quantity_piece": 1,
    "ship_physical_weight": "0.250",
    "ship_price_value": "10.00",
    "ship_merchant_name": "Tienda Prueba",
    "country_product_origin": "US",
    "ship_date_process": "/Date(1792540800000-0600)/"
  }]
}
RespuestaResponse响应 200
{
  "estimation_status": {
    "CX1234567890": {"status": "SUCCESS", "message": "Infomation estimation"}
  },
  "order_data": [{
    "order": {
      "idPackage": 1010474,
      "orderQR": "idPackage<MN>1010474||fullName<MN>Destinatario Prueba||phone<MN>00000000||identification<MN>null",
      "status": "SUCCESS",
      "message": "Complete payment"
    },
    "courier_product": [{"id_package": 1010474}]
  }],
  "order_status": {
    "CX1234567890": {"status": "SUCCESS", "message": "Complete payment"}
  }
}
Tracking repetidoDuplicate tracking运单号重复 500
{"httpCode": 500, "message": "An error has occurred while processing the package"}
Sin productosNo products没有商品 406
{
  "estimation_status": {
    "CX1234567890": {"status": "ERRORLISTPRODUCT", "message": "List of products is empty"}
  },
  "order_data": [],
  "order_status": {}
}

Estados del paquetePackage status包裹状态

GET https://crossdocker.nextmile.tech/api/v1/package/list?id=<id_order>

Devuelve la lista de estados que el transportista ha registrado para el paquete, con la fecha en UTC (termina en Z), tal como la devuelve el transportista. Para la hora de Costa Rica, reste 6 horas.

Si el paquete no existe, o su transportista no tiene integración, la respuesta es 200 con el cuerpo vacío, no un 404.

Returns the list of statuses the carrier has recorded for the package, with dates in UTC (ending in Z), exactly as the carrier returns them. For Costa Rica time, subtract 6 hours.

If the package does not exist, or its carrier is not integrated, the response is 200 with an empty body, not a 404.

返回承运商为该包裹记录的状态列表,日期为 UTC(以 Z 结尾),与承运商返回的一致。哥斯达黎加时间为 UTC 减 6 小时。

如果包裹不存在,或其承运商未对接,响应为 200,响应体为空,而不是 404。

ParámetroParameter参数DescripciónDescription说明
id*El id_order con el que se creó el paquete.The id_order the package was created with.创建包裹时使用的 id_order。
PeticiónRequest请求
curl "https://crossdocker.nextmile.tech/api/v1/package/list?id=CX1234567890" \
  -H "Authorization: Bearer $TOKEN"
RespuestaResponse响应 200
{
  "list": [
    {"date": "2026-10-17T00:44:57Z", "statusPackage": "PREPARE"},
    {"date": "2026-10-17T01:02:13Z", "statusPackage": "OUTCOUNTRY"}
  ],
  "status": "SUCCESS"
}

Manifiesto v1Manifest v1舱单 v1

POST https://crossdocker.nextmile.tech/api/v1/manifest

Agrupa en un manifiesto paquetes ya creados con /v1/package. En packages va la lista de sus id_order.

  • Todo o nada: si algún paquete no existe, la respuesta es 400 con la lista de los que faltan, y no se crea el manifiesto.
  • Lo identifica manifest.mnfs_id: un mnfs_id repetido da 400.
  • Si un paquete ya estaba en otro manifiesto, pasa al nuevo.
  • A cada transportista con integración solo le llegan sus paquetes, con los totales recalculados.

Las fechas van en dd/MM/yyyy h:mm AM o en ISO 8601. Crossdocker toma una fecha sin zona horaria en hora de Costa Rica (UTC−6). Para evitar dudas, envíela en ISO 8601 con desfase, por ejemplo 2026-10-20T06:30:00-06:00.

Groups into a manifest packages already created with /v1/package. packages is the list of their id_order values.

  • All or nothing: if any package does not exist, the response is 400 listing the missing ones, and the manifest is not created.
  • It is identified by manifest.mnfs_id: a repeated mnfs_id returns 400.
  • A package that was already in another manifest moves to the new one.
  • Each integrated carrier only receives its own packages, with the totals recalculated.

Dates use dd/MM/yyyy h:mm AM or ISO 8601. Crossdocker reads a date without a time zone as Costa Rica time (UTC−6). To avoid ambiguity, send ISO 8601 with an offset, for example 2026-10-20T06:30:00-06:00.

将已通过 /v1/package 创建的包裹归入一个舱单。packages 为这些包裹的 id_order 列表。

  • 全部成功或全部失败:只要有一个包裹不存在,就返回 400 并列出缺失的包裹,舱单不会创建。
  • 以 manifest.mnfs_id 作为标识:重复的 mnfs_id 返回 400。
  • 如果某个包裹已在其他舱单中,它会转入新舱单。
  • 每个已对接的承运商只会收到属于自己的包裹,合计数会重新计算。

日期格式为 dd/MM/yyyy h:mm AM 或 ISO 8601。Crossdocker 将不带时区的日期按哥斯达黎加时间(UTC−6)处理。为避免歧义,请使用带时区偏移的 ISO 8601,例如 2026-10-20T06:30:00-06:00。

Campos principalesMain fields主要字段

CampoField字段DescripciónDescription说明
manifest.mnfs_id*Identificador del manifiesto. Único.Manifest identifier. Unique.舱单标识,唯一。
packages*Los id_order de los paquetes; no puede ir vacía.The packages' id_order values; it cannot be empty.包裹的 id_order 列表,不能为空。
manifest.mnfs_nmr_airwaybillopc.Guía aérea.Air waybill.空运单号。
manifest.mnfs_flight_numberopc.Vuelo.Flight.航班号。
manifest.mnfs_flight_dateopc.Fecha del vuelo.Flight date.航班日期。
manifest.mnfs_date_entryopc.Fecha de entrada.Entry date.入境日期。
manifest.mnfs_ttl_*opc.Totales declarados: envíos, piezas, peso y valor.Declared totals: shipments, pieces, weight and value.申报合计:票数、件数、重量和价值。
PeticiónRequest请求
curl -X POST https://crossdocker.nextmile.tech/api/v1/manifest \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d @manifiesto.json
manifiesto.json
{
  "manifest": {
    "mnfs_id": 970101,
    "mnfs_nmr_airwaybill": "123-45678901",
    "mnfs_ext_number": "",
    "mnfs_flight_number": "XX123",
    "mnfs_flight_date": "20/10/2026 6:30 AM",
    "mnfs_date_entry": "20/10/2026 9:15 AM",
    "ctry_iso_code": "CR",
    "city_iso_code_origin": "MIA",
    "city_iso_code_dest": "SJO",
    "origin_country": "US",
    "manifest_ddup_type": "DDU",
    "mnfs_ttl_shipment": 2,
    "mnfs_ttl_quantity_piece": 2,
    "mnfs_ttl_physical_weight": 0.5,
    "mnfs_ttl_price_value": 20.0
  },
  "packages": ["CX1234567890", "CX1234567891"]
}
RespuestaResponse响应 200
[{"id_manifest": 259, "packages": ["CX1234567890", "CX1234567891"]}]
Paquete inexistenteUnknown package包裹不存在 400
{
  "body": [{
    "enterpriseCode": "CX1234567891",
    "message": "The package with code: CX1234567891 was not found"
  }],
  "httpCode": 400,
  "message": "The following packages were not found"
}

Manifiesto v2Manifest v2舱单 v2

POST https://crossdocker.nextmile.tech/api/v2/manifest

Igual que la v1, pero cada paquete va como objeto, con su número de saca (int_bag_num). La cabecera manifest y las respuestas son las mismas.

Cada versión exige su formato: textos en la v1 y objetos en la v2. Si se mezclan, la respuesta es 500.

Same as v1, but each package is an object with its bag number (int_bag_num). The manifest header and the responses are the same.

Each version requires its own format: strings in v1 and objects in v2. Mixing them returns 500.

与 v1 相同,但每个包裹以对象形式提交,并带有袋号(int_bag_num)。manifest 头部和响应与 v1 相同。

每个版本必须使用自己的格式:v1 为字符串,v2 为对象。混用会返回 500。

CampoField字段DescripciónDescription说明
packages[].order_label*El id_order del paquete.The package's id_order.包裹的 id_order。
packages[].int_bag_numopc.Número de saca.Bag number.袋号。
CuerpoBody请求体
{
  "manifest": { "mnfs_id": 970102, … },
  "packages": [
    {"order_label": "CX1234567890", "int_bag_num": 1},
    {"order_label": "CX1234567891", "int_bag_num": 2}
  ]
}
RespuestaResponse响应 200
[{"id_manifest": 260, "packages": ["CX1234567890", "CX1234567891"]}]

ErroresErrors错误

Los códigos HTTP y la forma de cada respuesta son los de la API courier de Moovin. Los mensajes van siempre en inglés, en cualquier idioma de esta documentación, para que su sistema los pueda comparar como texto. La fecha del manifiesto repetido va en UTC (+00:00).

HTTP codes and the shape of every response are those of Moovin's courier API. Messages are always in English, whatever the language of this documentation, so your system can compare them as text. The date in the duplicate-manifest message is in UTC (+00:00).

HTTP 状态码和各响应的结构与 Moovin 快递 API 相同。错误信息一律使用英文(无论本文档以何种语言显示),以便您的系统按文本比对。舱单重复错误中的日期为 UTC(+00:00)。

HTTPCuándoWhen何时
401Sin token, caducado o no válido. Cuerpo vacío.No token, expired or invalid. Empty body.没有令牌、令牌过期或无效。响应体为空。
500Paquete: id_order repetido, JSON mal formado, falta point_delivery o ship_date_process, o la fecha /Date()/ va sin desfase.Package: repeated id_order, malformed JSON, missing point_delivery or ship_date_process, or a /Date()/ without offset.包裹:id_order 重复、JSON 格式错误、缺少 point_delivery 或 ship_date_process,或 /Date()/ 未带时区偏移。
406Paquete: detail vacío.Package: empty detail.包裹:detail 为空。
400Manifiesto: mnfs_id repetido o ausente, o paquetes inexistentes.Manifest: repeated or missing mnfs_id, or unknown packages.舱单:mnfs_id 重复或缺失,或包裹不存在。
500Manifiesto: packages vacío, o formato de otra versión.Manifest: empty packages, or the other version's format.舱单:packages 为空,或使用了另一版本的格式。
Manifiesto repetidoDuplicate manifest舱单重复 400
{
  "httpCode": 400,
  "message": "The manifest you are trying to load with 'mnfs_id' : 970101 and `mnfs_nmr_airwaybill` : 123-45678901 was already loaded on 2026-10-20T15:15:00.000+00:00"
}
Sin mnfs_idNo mnfs_id缺少 mnfs_id 400
{
  "body": [{"enterpriseCode": "mnfsId", "message": "mnfsId is required"}],
  "httpCode": 400,
  "message": "The manifest cannot be processed, please check the data"
}
Manifiesto sin paquetesManifest without packages舱单没有包裹 500
{"httpCode": 500, "message": "An error has occurred while processing the manifest"}

ReintentosRetries重试

  • Si el transportista devuelve un error, usted recibe ese error y en Crossdocker no queda nada a medias: puede reintentar con el mismo cuerpo.
  • Si no le llega la respuesta (corte de red, tiempo agotado), reintente con el mismo id_order. Antes de reenviar, Crossdocker comprueba si el transportista ya tiene el paquete, así que no se duplica.
  • Si recibe 401 con un token que aún no había caducado, pida otro token y repita la petición.
  • If the carrier returns an error, you receive that error and nothing is left half-done in Crossdocker: you can retry with the same body.
  • If the response never arrives (network cut, timeout), retry with the same id_order. Before forwarding again, Crossdocker checks whether the carrier already has the package, so it is not duplicated.
  • If you get a 401 with a token that had not yet expired, request a new token and repeat the call.
  • 如果承运商返回错误,您会收到该错误,Crossdocker 中不会留下未完成的数据:可以用相同的请求体重试。
  • 如果没有收到响应(网络中断、超时),请使用相同的 id_order 重试。重新转发前,Crossdocker 会先确认承运商是否已有该包裹,因此不会重复。
  • 如果令牌尚未过期却收到 401,请重新申请令牌并重复请求。