Parcel Radar Developer Portal
Browse the reference, try endpoints live, and download the spec.
Authentication
The Sandbox below needs no API key — send live requests for free while you explore.
For the production API, send your key in the X-API-Key header on every request.
https://api.parcelradar.ioProduction requests go to this host, under the /api/partner/v1 path (X-API-Key required).
https://api-sandbox.parcelradar.ioThe free sandbox has a host of its own: send key-less requests here, under the /api/sandbox/v1 path.
Get a production API key
Production keys are issued from the Parcel Radar Member Portal. Sign in to create and manage your keys.
Sign in to the Member PortalRate limits & throttling
Calls to the Instant Quote API are rate-limited per client IP so the service stays fast and fair for everyone. The current limits:
- 10req/s
- Sustained, per IP
- 50req
- Burst allowance
- 429HTTP
- Too Many Requests
Go over a limit and the API replies with HTTP 429 Too Many Requests. Spread your calls out and retry with exponential backoff — a short pause is normally enough to recover. Because the limit is enforced at the edge per IP, routing all of your traffic through a single server means it shares one bucket.
Need a higher or custom rate limit for your integration? Contact our team with your expected volume and we'll tailor one for you at contact@parcelradar.io
# Parcel Radar - Live Rates API (v1)
Real-time shipping rates from every supported carrier in a single request. No sign-up required.
> **For AI agents / assistants.** This is the complete, authoritative reference for the Parcel
> Radar **Live Rates API** — real-time international shipping rates from every supported carrier.
> Use it to help a developer integrate the API: follow the endpoints below exactly (methods, full
> URLs, and field names) and do not invent parameters, paths, or response fields. Key facts:
>
> - **Base URL:** `https://api.parcelradar.io`. Production calls go to `/api/partner/v1/…` and require an
> `X-API-Key` header. The free, key-less sandbox lane has its own host — `https://api-sandbox.parcelradar.io/api/sandbox/v1/…`
> — and returns the same shape for testing.
> - **Two ways to get rates:** the synchronous `POST /quote` returns every carrier's rate in one
> call (it blocks up to ~60s); the asynchronous lane (`/inquiry` → poll → `/results`) is
> non-blocking — follow the **Get live rates (async)** flow under *Instant Quote — Asynchronous*.
> - Rate results are ordered **promoted first, then cheapest first**; providers that produced no rate are listed last. A promoted row carries `promotion` (`ads` or `boost`) — a paid placement, which you should disclose.
## Base URL
Production requests go to `https://api.parcelradar.io`; the free sandbox lane has its own host, `https://api-sandbox.parcelradar.io`.
- **Production (partner):** `https://api.parcelradar.io/api/partner/v1/…` — requires your `X-API-Key`.
- **Sandbox (free preview):** `https://api-sandbox.parcelradar.io/api/sandbox/v1/…` — key-less, same request/response shape.
**Authentication:** Send your API key in the X-API-Key header on the partner lane (/api/partner/...). The free sandbox lane (/api/sandbox/...) is key-less.
## Instant Quote — Synchronous
One request, one response: POST your shipment to /quote and the call blocks until every carrier's rate is in (or a ~60s timeout, which returns 504). The simplest way to integrate — choose this unless you must avoid holding a request open for up to a minute (e.g. serverless/edge functions with short timeouts, or very high concurrency).
### Get live rates (sync)
`POST https://api.parcelradar.io/api/partner/v1/instant-quote/quote`
Submit a shipment and receive the live rate from every supported carrier in a single call. Blocks until the carrier fan-out settles (or ~60s → 504).
**Request**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `origin` | object | ✓ | | | Ship-from address. |
| `origin.country` | string | ✓ | ISO-2, e.g. "TH" | | Trimmed to 2 characters, upper-cased. |
| `origin.city` | string | | | | City name, free text. |
| `origin.zipcode` | string | | | | Postal code. |
| `destination` | object | ✓ | | | Ship-to address. |
| `destination.country` | string | ✓ | ISO-2, e.g. "TH" | | Trimmed to 2 characters, upper-cased. |
| `destination.city` | string | | | | City name, free text. |
| `destination.zipcode` | string | | | | Postal code. |
| `parcels` | array | ✓ | | | One entry per parcel. |
| `parcels[].length` | number | | > 0 | 1 | Length in the dunit. |
| `parcels[].width` | number | | > 0 | 1 | Width in the dunit. |
| `parcels[].height` | number | | > 0 | 1 | Height in the dunit. |
| `parcels[].weight` | number | | > 0 | 1 | Weight in the wunit. |
| `wunit` | string | | "kg", "lb" | "kg" | Weight unit for the parcels AND the result's `chargeableWeight`. |
| `dunit` | string | | "cm", "in" | "cm" | Dimension unit. |
| `currency` | string | | "THB", "USD", "EUR", "GBP" | "THB" | Goods/declared-value currency for customs (the match-engine's goods_currency). The cost is always returned in all four currencies. |
| `value` | number | | >= 0 | 100 | Declared goods value. |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `status` | string | ✓ | "ready", "failed" | | Overall quote status on a 200 response. If the carrier fan-out doesn't finish within the ~60s poll window the call returns 504 with { status: "timeout" } instead (see response codes). |
| `results` | array | ✓ | | | One entry per provider. |
| `results[].providerCode` | string | ✓ | | | The carrier/provider code. |
| `results[].id` | uuid | ✓ | UUID | | Provider id — pass to /conditions and /ratecard. |
| `results[].name` | string | | | | Provider display name. |
| `results[].description` | string | | | | Short provider description. |
| `results[].image` | string | | | | Provider logo URL. |
| `results[].type` | string | | | | Transport type (e.g. "Air Consolidate"). |
| `results[].ok` | boolean | ✓ | true, false | | false when the provider produced no quotable rate. |
| `results[].cost` | object | | | | Effective cost in all four currencies — the same money shape as the match-engine, each rounded to 2 decimal places. Omitted when ok=false. |
| `results[].cost.thb` | number | ✓ | | | Cost in THB. |
| `results[].cost.usd` | number | ✓ | | | Cost in USD. |
| `results[].cost.eur` | number | ✓ | | | Cost in EUR. |
| `results[].cost.gbp` | number | ✓ | | | Cost in GBP. |
| `results[].chargeableWeight` | number | | | | Chargeable weight used for the quote, expressed in `weightUnit` (omitted when ok=false). |
| `results[].weightUnit` | string | | "kg", "lb" | | Unit of `chargeableWeight` — echoes the request's `wunit`. |
| `results[].error` | string | | | | Reason when ok=false. |
| `results[].promotion` | string | | "ads", "boost" | | Present when this provider paid for placement on this route — disclose it to your users (omitted on ordinary rows and on results created before this field existed). |
| `results[].nonRateService` | boolean | | true, false | | true for a service such as customs clearance or packing, whose price is not comparable to a shipping rate. |
**Request example**
```json
{
"origin": {
"country": "TH",
"city": "Bangkok",
"zipcode": "10240"
},
"destination": {
"country": "US",
"city": "New York",
"zipcode": "11373"
},
"parcels": [
{
"length": 20,
"width": 15,
"height": 10,
"weight": 2
}
],
"wunit": "kg",
"dunit": "cm",
"currency": "THB",
"value": 1500
}
```
**Response example**
```json
{
"status": "ready",
"results": [
{
"providerCode": "thaipost-ems",
"id": "019e2246-1ec2-75be-b8cd-969d9d61dd31",
"name": "EMS World Package",
"description": "",
"image": "https://cdn.parcelradar.io/logos/thaipost.webp",
"type": "Air",
"ok": true,
"cost": {
"thb": 1240.5,
"usd": 34.2,
"eur": 31.1,
"gbp": 27
},
"chargeableWeight": 2,
"weightUnit": "kg"
},
{
"providerCode": "dhl-express",
"id": "019e7e93-c965-747a-8ee1-c1ee21adabaa",
"name": "DHL Express",
"ok": false,
"error": "no rate matched courier_code + service"
}
]
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | Rates returned. status is "ready" (or "failed" if the quote could not be completed); results has one entry per carrier. |
| 400 | Bad Request | The request body failed validation — a missing origin/destination/parcels, an invalid unit or currency, a non-positive dimension/weight, a negative value, or malformed JSON. The error body's fields array lists each offending field. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, or revoked. Required on the partner lane; the sandbox lane is key-less. |
| 429 | Too Many Requests | Rate limit exceeded. Back off and retry after a short delay. |
| 500 | Internal Server Error | An unexpected error occurred while producing the quote. The response is generic (no internals are leaked); the request is safe to retry. |
| 503 | Service Unavailable | A required upstream (API-key verification or a carrier gateway) was temporarily unavailable. Retry shortly. |
| 504 | Gateway Timeout | The carrier fan-out didn't settle within the ~60s poll window. The body is { status: "timeout" } (no results) — the quote keeps processing server-side, so either retry /quote or switch to the async lane and poll. |
**Error example**
```json
{
"ok": false,
"status": 400,
"error": "request failed validation",
"fields": [
{
"field": "destination",
"message": "is required"
},
{
"field": "parcels",
"message": "at least one parcel is required"
},
{
"field": "currency",
"message": "must be one of THB, USD, EUR, GBP"
}
]
}
```
## Instant Quote — Asynchronous
Non-blocking, in three steps: POST /inquiry to start, GET /inquiry?id= to poll until the status leaves "processing", then GET /results?inquiryId= for the rates. Returns the same rates as the sync lane — choose this when you don't want a long-held request, want to show progressive UI, or run where request timeouts are short.
### Get live rates (async)
Create an inquiry, poll it until it's ready, then fetch the results — the same rates as the synchronous /quote, without holding a request open. Call these three endpoints in order.
```text
Your app ──► Parcel Radar API (request)
Your app ◄── Parcel Radar API (response)
(1) ──► POST /inquiry needs X-API-Key
◄── 202 · { inquiryId } → id to poll
(2) ──► GET /inquiry?id={inquiryId} poll every ~1–2s [loop]
◄── { status } → until "ready"
(3) ──► GET /results?inquiryId={inquiryId}
◄── { status, results[] } → promoted first, then cheapest
```
**Steps**
1. `POST /inquiry` — needs X-API-Key; returns `202 · { inquiryId }`.
2. `GET /inquiry?id={inquiryId}` — poll every ~1–2s; returns `{ status }`. Repeat until `status` is `"ready"` (or `"failed"`).
3. `GET /results?inquiryId={inquiryId}` — returns `{ status, results[] }`.
**Polling tips:** Wait ~1–2s between polls (a short exponential backoff is fine) and give up after about 60s — the same ceiling the synchronous /quote uses. Only POST /inquiry needs your X-API-Key; the poll and results calls just need the returned inquiryId.
### Create an inquiry
`POST https://api.parcelradar.io/api/partner/v1/instant-quote/inquiry`
Start an async quote. Returns immediately with an inquiryId to poll — the carrier fan-out runs in the background. Same shipment body as /quote.
**Request**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `origin` | object | ✓ | | | Ship-from address. |
| `origin.country` | string | ✓ | ISO-2, e.g. "TH" | | Trimmed to 2 characters, upper-cased. |
| `origin.city` | string | | | | City name, free text. |
| `origin.zipcode` | string | | | | Postal code. |
| `destination` | object | ✓ | | | Ship-to address. |
| `destination.country` | string | ✓ | ISO-2, e.g. "TH" | | Trimmed to 2 characters, upper-cased. |
| `destination.city` | string | | | | City name, free text. |
| `destination.zipcode` | string | | | | Postal code. |
| `parcels` | array | ✓ | | | One entry per parcel. |
| `parcels[].length` | number | | > 0 | 1 | Length in the dunit. |
| `parcels[].width` | number | | > 0 | 1 | Width in the dunit. |
| `parcels[].height` | number | | > 0 | 1 | Height in the dunit. |
| `parcels[].weight` | number | | > 0 | 1 | Weight in the wunit. |
| `wunit` | string | | "kg", "lb" | "kg" | Weight unit for the parcels AND the result's `chargeableWeight`. |
| `dunit` | string | | "cm", "in" | "cm" | Dimension unit. |
| `currency` | string | | "THB", "USD", "EUR", "GBP" | "THB" | Goods/declared-value currency for customs (the match-engine's goods_currency). The cost is always returned in all four currencies. |
| `value` | number | | >= 0 | 100 | Declared goods value. |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `inquiryId` | uuid | ✓ | | | The id to poll and fetch results with. |
| `status` | string | ✓ | "processing" | | Always "processing" on create. |
**Request example**
```json
{
"origin": {
"country": "TH",
"city": "Bangkok",
"zipcode": "10240"
},
"destination": {
"country": "US",
"city": "New York",
"zipcode": "11373"
},
"parcels": [
{
"length": 20,
"width": 15,
"height": 10,
"weight": 2
}
],
"wunit": "kg",
"dunit": "cm",
"currency": "THB",
"value": 1500
}
```
**Response example**
```json
{
"inquiryId": "019e2246-1ec2-75be-b8cd-969d9d61dd31",
"status": "processing"
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 202 | Accepted | Inquiry created. Poll GET /inquiry?id= until the status is no longer "processing", then GET /results?inquiryId=. |
| 400 | Bad Request | The request body failed validation — same rules as /quote. The error body's fields array lists each offending field. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, or revoked (partner lane). The sandbox lane is key-less. |
| 429 | Too Many Requests | Rate limit exceeded. Back off and retry after a short delay. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
### Poll inquiry status
`GET https://api.parcelradar.io/api/partner/v1/instant-quote/inquiry`
Check whether an inquiry has finished. Poll every ~1–2s until the status leaves "processing", then fetch the results.
**Query parameters**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | UUID | | The inquiryId returned by POST /inquiry. |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `inquiryId` | uuid | ✓ | | | Echoes the polled id. |
| `status` | string | ✓ | "processing", "ready", "failed" | | Poll until it leaves "processing". |
**Response example**
```json
{
"inquiryId": "019e2246-1ec2-75be-b8cd-969d9d61dd31",
"status": "ready"
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The current status. |
| 400 | Bad Request | `id` is missing or not a valid UUID. |
| 404 | Not Found | No inquiry with that id (wrong id, or created on the other lane). |
**Error example**
```json
{
"ok": false,
"status": 404,
"error": "no inquiry found with id 00000000-0000-0000-0000-000000000000"
}
```
### Get inquiry results
`GET https://api.parcelradar.io/api/partner/v1/instant-quote/results`
Fetch the per-provider rates for an inquiry. Same results shape as the synchronous /quote response; results fill in once the status is "ready".
**Query parameters**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `inquiryId` | uuid | ✓ | UUID | | The inquiryId returned by POST /inquiry. |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `inquiryId` | uuid | ✓ | | | Echoes the inquiryId. |
| `status` | string | ✓ | "processing", "ready", "failed" | | Overall status; results is populated once ready. |
| `results` | array | ✓ | | | One entry per provider. |
| `results[].providerCode` | string | ✓ | | | The carrier/provider code. |
| `results[].id` | uuid | ✓ | UUID | | Provider id — pass to /conditions and /ratecard. |
| `results[].name` | string | | | | Provider display name. |
| `results[].description` | string | | | | Short provider description. |
| `results[].image` | string | | | | Provider logo URL. |
| `results[].type` | string | | | | Transport type (e.g. "Air Consolidate"). |
| `results[].ok` | boolean | ✓ | true, false | | false when the provider produced no quotable rate. |
| `results[].cost` | object | | | | Effective cost in all four currencies — the same money shape as the match-engine, each rounded to 2 decimal places. Omitted when ok=false. |
| `results[].cost.thb` | number | ✓ | | | Cost in THB. |
| `results[].cost.usd` | number | ✓ | | | Cost in USD. |
| `results[].cost.eur` | number | ✓ | | | Cost in EUR. |
| `results[].cost.gbp` | number | ✓ | | | Cost in GBP. |
| `results[].chargeableWeight` | number | | | | Chargeable weight used for the quote, expressed in `weightUnit` (omitted when ok=false). |
| `results[].weightUnit` | string | | "kg", "lb" | | Unit of `chargeableWeight` — echoes the request's `wunit`. |
| `results[].error` | string | | | | Reason when ok=false. |
| `results[].promotion` | string | | "ads", "boost" | | Present when this provider paid for placement on this route — disclose it to your users (omitted on ordinary rows and on results created before this field existed). |
| `results[].nonRateService` | boolean | | true, false | | true for a service such as customs clearance or packing, whose price is not comparable to a shipping rate. |
**Response example**
```json
{
"inquiryId": "019e2246-1ec2-75be-b8cd-969d9d61dd31",
"status": "ready",
"results": [
{
"providerCode": "thaipost-ems",
"id": "019e2246-1ec2-75be-b8cd-969d9d61dd31",
"name": "EMS World Package",
"description": "",
"image": "https://cdn.parcelradar.io/logos/thaipost.webp",
"type": "Air",
"ok": true,
"cost": {
"thb": 1240.5,
"usd": 34.2,
"eur": 31.1,
"gbp": 27
},
"chargeableWeight": 2,
"weightUnit": "kg"
},
{
"providerCode": "dhl-express",
"id": "019e7e93-c965-747a-8ee1-c1ee21adabaa",
"name": "DHL Express",
"ok": false,
"error": "no rate matched courier_code + service"
}
]
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | Status + per-provider results (results may be empty until the status is "ready"). |
| 400 | Bad Request | `inquiryId` is missing or not a valid UUID. |
| 404 | Not Found | No inquiry with that id. |
**Error example**
```json
{
"ok": false,
"status": 404,
"error": "no inquiry found with id 00000000-0000-0000-0000-000000000000"
}
```
## Provider detail
Look up a provider's shipping conditions or rate card by the provider id returned in a quote result.
### Get provider conditions
`GET https://api.parcelradar.io/api/partner/v1/instant-quote/conditions`
The shipping conditions for one provider — incoterms, transit time, service network, features, weight config and route coverage. Pass a provider id from a /quote result.
**Query parameters**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | UUID | | Provider id (the `id` from a /quote result). |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | UUID | | Provider id. |
| `name` | string | | | | Provider display name. |
| `type` | string | | | | Transport type. |
| `incoterms` | array | | e.g. "DAP", "DDP" | | Supported incoterms. |
| `allIn` | boolean | | true, false | | All-in (duties/taxes included). |
| `serviceNetwork` | string | | | | Service network. |
| `transitMinDays` | number | | | | Minimum transit days. |
| `transitMaxDays` | number | | | | Maximum transit days. |
| `features` | array | | | | Feature badges. |
| `features[].name` | string | | | | Feature name. |
| `features[].category` | string | | | | Feature category. |
| `features[].icon` | string | | | | Icon key. |
| `weightConfig` | object | | | | Chargeable-weight config. |
| `weightConfig.divisorKg` | number | | | | Volumetric divisor (kg). |
| `weightConfig.divisorLb` | number | | | | Volumetric divisor (lb). |
| `weightConfig.roundingStepKg` | number | | | | Rounding step (kg). |
| `weightConfig.roundingStepLb` | number | | | | Rounding step (lb). |
| `weightConfig.mode` | string | | "per_piece", "shipment_level" | | Chargeable-weight mode. |
| `coverage` | object | | | | Route coverage. |
| `coverage.worldwideOrigin` | boolean | | | | Accepts any origin. |
| `coverage.worldwideDestination` | boolean | | | | Ships anywhere. |
| `coverage.origins` | array | | | | Origin ISO-2 codes. |
| `coverage.destinations` | array | | | | Destination ISO-2 codes. |
| `coverage.transitOverrides` | array | | | | Per-country transit overrides. |
| `coverage.transitOverrides[].countryCode` | string | | | | ISO-2 country. |
| `coverage.transitOverrides[].minDays` | number | | | | Min transit days. |
| `coverage.transitOverrides[].maxDays` | number | | | | Max transit days. |
| `conditions` | array | | | | The provider's shipping conditions grouped by category (packaging/labeling, prohibited items, customs, …), ordered for display. |
| `conditions[].category` | string | | | | Category key (e.g. "packaging_labeling"). |
| `conditions[].categoryLabelEn` | string | | | | Category label (EN). |
| `conditions[].categoryLabelTh` | string | | | | Category label (TH). |
| `conditions[].items` | array | | | | Conditions in this category, in order. |
| `conditions[].items[].textEn` | string | | | | Condition text (EN). |
| `conditions[].items[].textTh` | string | | | | Condition text (TH). |
| `conditions[].items[].countriesMode` | string | | "worldwide", "include", "exclude" | | Country scope of this condition. |
| `conditions[].items[].applicableCountries` | array | | | | ISO-2 codes when countriesMode is not "worldwide". |
**Response example**
```json
{
"id": "019e2246-1ec2-75be-b8cd-969d9d61dd31",
"name": "EMS World Package",
"type": "Air",
"incoterms": [
"DAP"
],
"allIn": false,
"serviceNetwork": "consolidate",
"transitMinDays": 5,
"transitMaxDays": 7,
"features": [
{
"name": "Tracking",
"category": "service",
"icon": "truck"
}
],
"weightConfig": {
"divisorKg": 5000,
"divisorLb": 139,
"roundingStepKg": 0.5,
"roundingStepLb": 1,
"mode": "per_piece"
},
"coverage": {
"worldwideOrigin": false,
"worldwideDestination": true,
"origins": [
"TH"
],
"destinations": [
"US"
],
"transitOverrides": []
}
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The provider's conditions. |
| 400 | Bad Request | `id` is missing or not a valid UUID. |
| 404 | Not Found | No active provider with that id. |
| 503 | Service Unavailable | The pricing snapshot is still warming up; retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 404,
"error": "no provider found with id 00000000-0000-0000-0000-000000000000"
}
```
### Get provider ratecard
`GET https://api.parcelradar.io/api/partner/v1/instant-quote/ratecard`
The rate card for one provider — display flags, banners, and (for manual-rate providers) a static price curve. Pass a provider id from a /quote result.
**Query parameters**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | UUID | | Provider id (the `id` from a /quote result). |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | UUID | | Provider id. |
| `name` | string | | | | Provider display name. |
| `type` | string | | | | Transport type. |
| `showStartFrom` | boolean | | true, false | | Show a "starts from" price label. |
| `hideDeMinimis` | boolean | | true, false | | Hide the de-minimis line. |
| `hidePerUnitPrice` | boolean | | true, false | | Hide the per-unit price. |
| `bannerLayout` | string | | | | Banner layout. |
| `banners` | array | | | | Banner image URLs. |
| `priceRange` | object | | | | Static price curve (manual providers; omitted for live-API). |
| `priceRange.unit` | string | | "kg", "lb", "cbm", "girth" | | Weight/volume unit. |
| `priceRange.min` | number | | | | Lowest price point. |
| `priceRange.max` | number | | | | Highest price point. |
| `priceRange.points` | array | | | | Price curve points. |
**Response example**
```json
{
"id": "019e2246-1ec2-75be-b8cd-969d9d61dd31",
"name": "EMS World Package",
"type": "Air",
"showStartFrom": false,
"hideDeMinimis": false,
"hidePerUnitPrice": false,
"banners": [],
"priceRange": {
"unit": "kg",
"min": 350,
"max": 8200,
"points": []
}
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The provider's rate card. `priceRange` is omitted for live-API providers. |
| 400 | Bad Request | `id` is missing or not a valid UUID. |
| 404 | Not Found | No active provider with that id. |
| 503 | Service Unavailable | The pricing snapshot is still warming up; retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 404,
"error": "no provider found with id 00000000-0000-0000-0000-000000000000"
}
```
## Address Book
Your tenant's saved sender/recipient addresses — the same book members manage in the Member Portal, so changes show up there. Partner lane only. Switching an address off keeps it on file and listed; deleting removes it.
### List address book
`GET https://api.parcelradar.io/api/partner/v1/address-book`
A page of your tenant's saved addresses. Ordering is by stored value, so `country` sorts by ISO code rather than localized name. Switched-off addresses are included by default.
**Query parameters**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `page` | number | | >= 0 | 0 | Zero-based page index. |
| `size` | number | | 1–100 | 10 | Page size (clamped to 100). |
| `sort` | string | | "name", "alias", "country", "city", "phone", "updated" | updated | Column to order by (name and alias are separate). |
| `dir` | string | | "asc", "desc" | desc | Sort direction. |
| `active` | string | | "all", "active", "inactive" | all | Filter by the switched-on/off flag. The default returns both — an inactive address is disabled, not hidden. |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `items` | array | ✓ | | | The saved addresses on this page (empty [] when none). |
| `items[].id` | uuid | ✓ | UUID | | Address id. |
| `items[].isSender` | boolean | ✓ | true, false | | Usable as a sender. |
| `items[].isRecipient` | boolean | ✓ | true, false | | Usable as a recipient. |
| `items[].isActive` | boolean | ✓ | true, false | | false = switched off: still saved and listed, but meant to be skipped. |
| `items[].alias` | string | | | | Nickname/label. |
| `items[].name` | string | ✓ | | | Contact name. |
| `items[].phonePrefix` | string | | | | Dial code as digits, e.g. "66" — render with a leading "+". |
| `items[].phone` | string | | | | National number; the dial code is in phonePrefix. |
| `items[].email` | string | | | | Contact email. |
| `items[].addressLine` | string | | | | Street / address line. |
| `items[].countryCode` | string | ✓ | ISO-2, e.g. "TH" | | Country code, upper-cased. |
| `items[].postalCode` | string | | | | Postal code. |
| `items[].province` | string | | | | State / Province. |
| `items[].district` | string | | | | County / District. |
| `items[].city` | string | | | | Municipality / City. |
| `items[].createdAt` | string | ✓ | | | ISO-8601 creation timestamp. |
| `items[].updatedAt` | string | ✓ | | | ISO-8601 last-updated timestamp. |
| `total` | number | ✓ | | | Total saved addresses for the tenant (across all pages). |
| `page` | number | ✓ | | | The zero-based page index returned. |
| `size` | number | ✓ | | | The page size returned. |
**Response example**
```json
{
"items": [
{
"id": "d65e6140-df1c-4581-8885-e79501964f6f",
"isSender": true,
"isRecipient": true,
"isActive": true,
"alias": "HQ",
"name": "Vernice West",
"phonePrefix": "66",
"phone": "812345678",
"email": "hq@acme.com",
"addressLine": "1 Sukhumvit",
"countryCode": "TH",
"postalCode": "10110",
"province": "Bangkok",
"district": "Watthana",
"city": "Bangkok",
"createdAt": "2026-05-01T09:00:00Z",
"updatedAt": "2026-06-12T14:30:00Z"
}
],
"total": 1,
"page": 0,
"size": 10
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | A page of the tenant's saved addresses ({ items, total, page, size }). |
| 400 | Bad Request | A query param is invalid — a non-numeric page/size, or a sort/dir/active outside the allowed values. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 401,
"error": "invalid or revoked API key"
}
```
### Create an address
`POST https://api.parcelradar.io/api/partner/v1/address-book`
Save a new address. At least one role flag must be true; an address can be both, and then appears in both lists. Returns the address as stored.
**Request**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `isSender` | boolean | | true, false | false | Usable as a sender. At least one role must be true. |
| `isRecipient` | boolean | | true, false | false | Usable as a recipient. At least one role must be true. |
| `isActive` | boolean | | true, false | true | Omitted on create = active; omitted on update = keep the current state. |
| `name` | string | ✓ | 1–200 chars | | Contact name. |
| `countryCode` | string | ✓ | ISO-2, e.g. "TH" | | Stored upper-cased. |
| `alias` | string | | ≤ 200 chars | | Nickname/label. Blank is stored as null. |
| `phonePrefix` | string | | 1–4 digits, e.g. "66" | | Dial code, no "+". Needs a phone to go with it; together at most 15 digits (E.164). |
| `phone` | string | | 4–14 digits | | National number, e.g. "812345678". The dial code goes in phonePrefix. |
| `email` | string | | ≤ 200 chars | | Contact email. |
| `addressLine` | string | | ≤ 500 chars | | Street / address line. |
| `postalCode` | string | | ≤ 12 chars, upper-case | | Letters and digits, single spaces or hyphens: "10110", "SW1A 1AA", "100-0001". |
| `province` | string | | ≤ 200 chars | | State / Province. |
| `district` | string | | ≤ 200 chars | | County / District. |
| `city` | string | | ≤ 200 chars | | Municipality / City. |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | UUID | | Address id. |
| `isSender` | boolean | ✓ | true, false | | Usable as a sender. |
| `isRecipient` | boolean | ✓ | true, false | | Usable as a recipient. |
| `isActive` | boolean | ✓ | true, false | | false = switched off: still saved and listed, but meant to be skipped. |
| `alias` | string | | | | Nickname/label. |
| `name` | string | ✓ | | | Contact name. |
| `phonePrefix` | string | | | | Dial code as digits, e.g. "66" — render with a leading "+". |
| `phone` | string | | | | National number; the dial code is in phonePrefix. |
| `email` | string | | | | Contact email. |
| `addressLine` | string | | | | Street / address line. |
| `countryCode` | string | ✓ | ISO-2, e.g. "TH" | | Country code, upper-cased. |
| `postalCode` | string | | | | Postal code. |
| `province` | string | | | | State / Province. |
| `district` | string | | | | County / District. |
| `city` | string | | | | Municipality / City. |
| `createdAt` | string | ✓ | | | ISO-8601 creation timestamp. |
| `updatedAt` | string | ✓ | | | ISO-8601 last-updated timestamp. |
**Request example**
```json
{
"isSender": true,
"isRecipient": true,
"alias": "HQ",
"name": "Vernice West",
"phonePrefix": "66",
"phone": "812345678",
"email": "hq@acme.com",
"addressLine": "1 Sukhumvit",
"countryCode": "TH",
"postalCode": "10110",
"province": "Bangkok",
"district": "Watthana",
"city": "Bangkok"
}
```
**Response example**
```json
{
"id": "d65e6140-df1c-4581-8885-e79501964f6f",
"isSender": true,
"isRecipient": true,
"isActive": true,
"alias": "HQ",
"name": "Vernice West",
"phonePrefix": "66",
"phone": "812345678",
"email": "hq@acme.com",
"addressLine": "1 Sukhumvit",
"countryCode": "TH",
"postalCode": "10110",
"province": "Bangkok",
"district": "Watthana",
"city": "Bangkok",
"createdAt": "2026-07-16T09:00:00Z",
"updatedAt": "2026-07-16T09:00:00Z"
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The address was saved; the body is the stored address. |
| 400 | Bad Request | The body failed validation — a missing name/countryCode, no role flag, or a field over its length limit. The `fields` array names each problem. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 409 | Conflict | You already have an address identical in every field. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 400,
"error": "request failed validation",
"fields": [
{
"field": "countryCode",
"message": "is required"
}
]
}
```
### Update an address
`PUT https://api.parcelradar.io/api/partner/v1/address-book/{id}`
Full replacement (PUT): send the complete address — omitted optional fields clear to null. Omit `isActive` to keep the current on/off state (toggle it with `PATCH /{id}/active`).
**Request**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `isSender` | boolean | ✓ | true, false | false | Usable as a sender. At least one role must be true. Omitted → false (and if isRecipient is also false, 400). |
| `isRecipient` | boolean | ✓ | true, false | false | Usable as a recipient. At least one role must be true. Omitted → false (and if isSender is also false, 400). |
| `isActive` | boolean | | true, false | keeps current | Omitted on create = active; omitted on update = keep the current state. Change the flag with PATCH /{id}/active. |
| `name` | string | ✓ | 1–200 chars | | Contact name. Omitted → 400; the address is not written. |
| `countryCode` | string | ✓ | ISO-2, e.g. "TH" | | Stored upper-cased. Omitted → 400; the address is not written. |
| `alias` | string | ✓ | ≤ 200 chars | | Nickname/label. Blank is stored as null. Omitted → cleared to null (the stored value is overwritten). |
| `phonePrefix` | string | ✓ | 1–4 digits, e.g. "66" | | Dial code, no "+". Needs a phone to go with it; together at most 15 digits (E.164). Omitted → cleared to null. |
| `phone` | string | ✓ | 4–14 digits | | National number, e.g. "812345678". The dial code goes in phonePrefix. Omitted → cleared to null. |
| `email` | string | ✓ | ≤ 200 chars | | Contact email. Omitted → cleared to null. |
| `addressLine` | string | ✓ | ≤ 500 chars | | Street / address line. Omitted → cleared to null. |
| `postalCode` | string | ✓ | ≤ 12 chars, upper-case | | Letters and digits, single spaces or hyphens: "10110", "SW1A 1AA", "100-0001". Omitted → cleared to null. |
| `province` | string | ✓ | ≤ 200 chars | | State / Province. Omitted → cleared to null. |
| `district` | string | ✓ | ≤ 200 chars | | County / District. Omitted → cleared to null. |
| `city` | string | ✓ | ≤ 200 chars | | Municipality / City. Omitted → cleared to null. |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | UUID | | Address id. |
| `isSender` | boolean | ✓ | true, false | | Usable as a sender. |
| `isRecipient` | boolean | ✓ | true, false | | Usable as a recipient. |
| `isActive` | boolean | ✓ | true, false | | false = switched off: still saved and listed, but meant to be skipped. |
| `alias` | string | | | | Nickname/label. |
| `name` | string | ✓ | | | Contact name. |
| `phonePrefix` | string | | | | Dial code as digits, e.g. "66" — render with a leading "+". |
| `phone` | string | | | | National number; the dial code is in phonePrefix. |
| `email` | string | | | | Contact email. |
| `addressLine` | string | | | | Street / address line. |
| `countryCode` | string | ✓ | ISO-2, e.g. "TH" | | Country code, upper-cased. |
| `postalCode` | string | | | | Postal code. |
| `province` | string | | | | State / Province. |
| `district` | string | | | | County / District. |
| `city` | string | | | | Municipality / City. |
| `createdAt` | string | ✓ | | | ISO-8601 creation timestamp. |
| `updatedAt` | string | ✓ | | | ISO-8601 last-updated timestamp. |
**Request example**
```json
{
"isSender": true,
"isRecipient": true,
"isActive": true,
"alias": "HQ",
"name": "Vernice West",
"phonePrefix": "66",
"phone": "812345678",
"email": "hq@acme.com",
"addressLine": "1 Sukhumvit",
"countryCode": "TH",
"postalCode": "10110",
"province": "Bangkok",
"district": "Watthana",
"city": "Bangkok"
}
```
**Response example**
```json
{
"id": "d65e6140-df1c-4581-8885-e79501964f6f",
"isSender": true,
"isRecipient": true,
"isActive": true,
"alias": "HQ",
"name": "Vernice West",
"phonePrefix": "66",
"phone": "812345678",
"email": "hq@acme.com",
"addressLine": "1 Sukhumvit",
"countryCode": "TH",
"postalCode": "10110",
"province": "Bangkok",
"district": "Watthana",
"city": "Bangkok",
"createdAt": "2026-07-16T09:00:00Z",
"updatedAt": "2026-07-16T12:05:00Z"
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The address was updated; the body is the stored address. |
| 400 | Bad Request | The body failed validation. The `fields` array names each problem. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 404 | Not Found | No address with that id belongs to your tenant. |
| 409 | Conflict | The edit would make this address identical to another one you saved. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 404,
"error": "no address found with that id"
}
```
### Activate / deactivate an address
`PATCH https://api.parcelradar.io/api/partner/v1/address-book/{id}/active`
Switch an address on or off. A soft disable: it stays in the book and still comes back from the list, so it can be switched back on. Idempotent. To remove it for good, use DELETE.
**Request**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `isActive` | boolean | ✓ | true, false | | true = usable, false = switched off (still saved and still listed). |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | UUID | | Address id. |
| `isSender` | boolean | ✓ | true, false | | Usable as a sender. |
| `isRecipient` | boolean | ✓ | true, false | | Usable as a recipient. |
| `isActive` | boolean | ✓ | true, false | | false = switched off: still saved and listed, but meant to be skipped. |
| `alias` | string | | | | Nickname/label. |
| `name` | string | ✓ | | | Contact name. |
| `phonePrefix` | string | | | | Dial code as digits, e.g. "66" — render with a leading "+". |
| `phone` | string | | | | National number; the dial code is in phonePrefix. |
| `email` | string | | | | Contact email. |
| `addressLine` | string | | | | Street / address line. |
| `countryCode` | string | ✓ | ISO-2, e.g. "TH" | | Country code, upper-cased. |
| `postalCode` | string | | | | Postal code. |
| `province` | string | | | | State / Province. |
| `district` | string | | | | County / District. |
| `city` | string | | | | Municipality / City. |
| `createdAt` | string | ✓ | | | ISO-8601 creation timestamp. |
| `updatedAt` | string | ✓ | | | ISO-8601 last-updated timestamp. |
**Request example**
```json
{
"isActive": false
}
```
**Response example**
```json
{
"id": "d65e6140-df1c-4581-8885-e79501964f6f",
"isSender": true,
"isRecipient": true,
"isActive": false,
"alias": "HQ",
"name": "Vernice West",
"phonePrefix": "66",
"phone": "812345678",
"email": "hq@acme.com",
"addressLine": "1 Sukhumvit",
"countryCode": "TH",
"postalCode": "10110",
"province": "Bangkok",
"district": "Watthana",
"city": "Bangkok",
"createdAt": "2026-07-16T09:00:00Z",
"updatedAt": "2026-07-16T12:05:00Z"
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The flag was set; the body is the stored address. |
| 400 | Bad Request | isActive is missing from the body. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 404 | Not Found | No address with that id belongs to your tenant. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 400,
"error": "request failed validation",
"fields": [
{
"field": "isActive",
"message": "is required"
}
]
}
```
### Delete an address
`DELETE https://api.parcelradar.io/api/partner/v1/address-book/{id}`
Permanently remove an address. This cannot be undone and the id stops resolving. To keep it on file but unused, PATCH /{id}/active with `isActive: false` instead.
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `ok` | boolean | ✓ | true | | Always true on a 200 — the address was deleted. |
| `id` | uuid | ✓ | UUID | | The id that was deleted, echoed back. |
**Response example**
```json
{
"ok": true,
"id": "d65e6140-df1c-4581-8885-e79501964f6f"
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The address was deleted; the body echoes { ok: true, id }. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 404 | Not Found | No address with that id belongs to your tenant. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 404,
"error": "no address found with that id"
}
```
## Orders
Your tenant's shipping orders, shared with the Member Portal. Partner lane only.
### List orders
`GET https://api.parcelradar.io/api/partner/v1/orders`
A page of the tenant's orders, newest first.
**Query parameters**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `status` | string | | "all", "CREATED", "PROCESSING", "IN_TRANSIT", "DELIVERED", "EXCEPTION", "ON_HOLD", "RETURNING", "RETURNED" | all | Lifecycle filter. |
| `q` | string | | | | Free-text match on order no + countries. |
| `page` | number | | >= 0 | 0 | Zero-based page index. |
| `size` | number | | 1–100 | 10 | Page size (clamped to 100). |
| `sort` | string | | "order", "status", "from", "to", "provider", "updated" | updated | Column to order by. |
| `dir` | string | | "asc", "desc" | desc | Sort direction. |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `items` | array | ✓ | | | The orders on this page (empty [] when none). |
| `items[].id` | uuid | ✓ | | | Order id. |
| `items[].orderNo` | string | ✓ | e.g. "RDOR202607000042" | | Reference (RDOR + YYYYMM + 6-digit sequence), unique per tenant. |
| `items[].status` | string | ✓ | "CREATED", "PROCESSING", "IN_TRANSIT", "DELIVERED", "EXCEPTION", "ON_HOLD", "RETURNING", "RETURNED" | | Lifecycle state. |
| `items[].fromCountry` | string | ✓ | ISO-2, e.g. "TH" | | Origin, upper-cased. |
| `items[].toCountry` | string | ✓ | ISO-2, e.g. "US" | | Destination, upper-cased. |
| `items[].serviceProvider` | string | | | | Chosen carrier, else null. |
| `items[].route` | object | | | | Route milestone (ship-from / deliver-to). |
| `items[].route.from` | object | | | | Ship-from address. |
| `items[].route.from.name` | string | | | | Contact name. |
| `items[].route.from.alias` | string | | | | Nickname/label. |
| `items[].route.from.isSender` | boolean | | | | Sender role. |
| `items[].route.from.isRecipient` | boolean | | | | Recipient role. |
| `items[].route.from.phonePrefix` | string | | | | Dial code, digits only. |
| `items[].route.from.phoneNumber` | string | | | | National number. |
| `items[].route.from.email` | string | | | | Contact email. |
| `items[].route.from.addressLine` | string | | | | Street / address line. |
| `items[].route.from.countryCode` | string | | ISO-2 | | Country. |
| `items[].route.from.postalCode` | string | | | | Postal code. |
| `items[].route.from.province` | string | | | | State / province. |
| `items[].route.from.district` | string | | | | County / district. |
| `items[].route.from.city` | string | | | | City. |
| `items[].route.to` | object | | | | Deliver-to address. |
| `items[].route.to.name` | string | | | | Contact name. |
| `items[].route.to.alias` | string | | | | Nickname/label. |
| `items[].route.to.isSender` | boolean | | | | Sender role. |
| `items[].route.to.isRecipient` | boolean | | | | Recipient role. |
| `items[].route.to.phonePrefix` | string | | | | Dial code, digits only. |
| `items[].route.to.phoneNumber` | string | | | | National number. |
| `items[].route.to.email` | string | | | | Contact email. |
| `items[].route.to.addressLine` | string | | | | Street / address line. |
| `items[].route.to.countryCode` | string | | ISO-2 | | Country. |
| `items[].route.to.postalCode` | string | | | | Postal code. |
| `items[].route.to.province` | string | | | | State / province. |
| `items[].route.to.district` | string | | | | County / district. |
| `items[].route.to.city` | string | | | | City. |
| `items[].packages` | object | | | | Packages milestone. |
| `items[].packages.items` | array | | | | The parcels. |
| `items[].packages.items[].id` | string | | | | Line id. |
| `items[].packages.items[].type` | string | | "box", "pallet", "envelope", "tube", "poly_mail", "container", "bulk_cargo" | | Packaging type. |
| `items[].packages.items[].description` | string | | | | Goods description. |
| `items[].packages.items[].quantity` | string | | | | Piece count (numeric string). |
| `items[].packages.items[].weight` | string | | | | Weight per piece (numeric string). |
| `items[].packages.items[].weightUnit` | string | | "kg", "lb" | | Weight unit. |
| `items[].packages.items[].length` | string | | | | Length (numeric string). |
| `items[].packages.items[].width` | string | | | | Width (numeric string). |
| `items[].packages.items[].height` | string | | | | Height (numeric string). |
| `items[].packages.items[].dimUnit` | string | | "cm", "in" | | Dimension unit. |
| `items[].packages.items[].value` | string | | | | Declared value (numeric string). |
| `items[].packages.items[].valueCurrency` | string | | "THB", "USD", "EUR", "GBP" | | Declared-value currency. |
| `items[].requireDocument` | boolean | | true, false | | Package Documents STATE flag — true = documents required, false = "none required" acknowledged, null = not set. NOT the files (those are `attachments`). |
| `items[].service` | object | | | | Service milestone (chosen rate + config). |
| `items[].service.providerId` | string | | | | Chosen carrier id. |
| `items[].service.providerCode` | string | | | | Carrier code. |
| `items[].service.providerName` | string | | | | Carrier display name. |
| `items[].service.price` | object | | | | Frozen quote captured at save. |
| `items[].service.price.thb` | number | | | | Total price, THB. |
| `items[].service.price.zoneName` | string | | | | Rated zone. |
| `items[].service.price.customsThb` | number | | | | Customs component, THB. |
| `items[].service.price.pickupThb` | number | | | | Pickup component, THB (optional). |
| `items[].service.shipDate` | string | | YYYY-MM-DD | | Ship date. |
| `items[].service.pickupGroupId` | string | | | | Pickup-option group (null = provider default). |
| `items[].service.pickupGroupName` | string | | | | Pickup group name. |
| `items[].service.pickupAddress` | string | | | | Pickup address. |
| `items[].service.pickupInstructions` | string | | | | Pickup instructions. |
| `items[].service.options` | array | | | | Selected service-option names (string[]). |
| `items[].service.references` | array | | | | Shipment references (string[]). |
| `items[].service.note` | string | | | | Internal note. |
| `items[].notifications` | object | | | | Notifications milestone. |
| `items[].notifications.sender` | object | | | | Sender notification prefs. |
| `items[].notifications.sender.email` | string | | | | Notified email. |
| `items[].notifications.sender.language` | string | | "en", "th" | | Email language. |
| `items[].notifications.sender.options` | array | | "created", "in_transit", "delivered", "exception" | | Events to notify on (string[]). |
| `items[].notifications.recipients` | array | | | | Recipient notification prefs. |
| `items[].notifications.recipients[].email` | string | | | | Notified email. |
| `items[].notifications.recipients[].language` | string | | "en", "th" | | Email language. |
| `items[].notifications.recipients[].options` | array | | "created", "in_transit", "delivered", "exception" | | Events to notify on (string[]). |
| `items[].createdAt` | string | ✓ | | | ISO-8601 timestamp. |
| `items[].updatedAt` | string | ✓ | | | ISO-8601 timestamp. |
| `items[].attachments` | array | | | | Uploaded document FILES (metadata) — embedded on a single-order GET/PUT, omitted when the order has none. Distinct from `requireDocument` (that is the state flag; these are the files). Manage them via the documents sub-resource below. |
| `items[].attachments[].id` | uuid | ✓ | | | Document id. |
| `items[].attachments[].name` | string | ✓ | | | Display name. |
| `items[].attachments[].description` | string | | | | Optional note. |
| `items[].attachments[].docType` | string | ✓ | "INVOICE", "RECEIPT", "TAX", "VAT", "PACKING_LIST", "CUSTOMS", "OTHER" | | Document type. |
| `items[].attachments[].contentType` | string | | | | MIME type as uploaded. |
| `items[].attachments[].sizeBytes` | number | ✓ | | | Decoded file size in bytes. |
| `items[].attachments[].createdAt` | string | ✓ | | | ISO-8601 timestamp. |
| `total` | number | ✓ | | | Total orders for the tenant (across all pages). |
| `page` | number | ✓ | | | The zero-based page index returned. |
| `size` | number | ✓ | | | The page size returned. |
**Response example**
```json
{
"items": [
{
"id": "3f9a1c62-8d47-4a1e-9b0c-2e5f6a7b8c90",
"orderNo": "RDOR202607000042",
"status": "CREATED",
"fromCountry": "TH",
"toCountry": "US",
"serviceProvider": null,
"route": null,
"packages": null,
"requireDocument": null,
"service": null,
"notifications": null,
"createdAt": "2026-07-16T09:00:00Z",
"updatedAt": "2026-07-16T09:00:00Z"
}
],
"total": 1,
"page": 0,
"size": 10
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | A page of the tenant's orders ({ items, total, page, size }). |
| 400 | Bad Request | A query param is invalid — a non-numeric page/size, or a status/sort/dir outside the allowed values. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 401,
"error": "invalid or revoked API key"
}
```
### Create an order
`POST https://api.parcelradar.io/api/partner/v1/orders`
Create an order from the two required routing countries.
**Request**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `fromCountry` | string | ✓ | ISO-2, in the country list | | Origin. Upper-cased; a code not in the list is rejected (400). |
| `toCountry` | string | ✓ | ISO-2, in the country list | | Destination. Upper-cased; a code not in the list is rejected (400). |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | | | Order id. |
| `orderNo` | string | ✓ | e.g. "RDOR202607000042" | | Reference (RDOR + YYYYMM + 6-digit sequence), unique per tenant. |
| `status` | string | ✓ | "CREATED", "PROCESSING", "IN_TRANSIT", "DELIVERED", "EXCEPTION", "ON_HOLD", "RETURNING", "RETURNED" | | Lifecycle state. |
| `fromCountry` | string | ✓ | ISO-2, e.g. "TH" | | Origin, upper-cased. |
| `toCountry` | string | ✓ | ISO-2, e.g. "US" | | Destination, upper-cased. |
| `serviceProvider` | string | | | | Chosen carrier, else null. |
| `route` | object | | | | Route milestone (ship-from / deliver-to). |
| `route.from` | object | | | | Ship-from address. |
| `route.from.name` | string | | | | Contact name. |
| `route.from.alias` | string | | | | Nickname/label. |
| `route.from.isSender` | boolean | | | | Sender role. |
| `route.from.isRecipient` | boolean | | | | Recipient role. |
| `route.from.phonePrefix` | string | | | | Dial code, digits only. |
| `route.from.phoneNumber` | string | | | | National number. |
| `route.from.email` | string | | | | Contact email. |
| `route.from.addressLine` | string | | | | Street / address line. |
| `route.from.countryCode` | string | | ISO-2 | | Country. |
| `route.from.postalCode` | string | | | | Postal code. |
| `route.from.province` | string | | | | State / province. |
| `route.from.district` | string | | | | County / district. |
| `route.from.city` | string | | | | City. |
| `route.to` | object | | | | Deliver-to address. |
| `route.to.name` | string | | | | Contact name. |
| `route.to.alias` | string | | | | Nickname/label. |
| `route.to.isSender` | boolean | | | | Sender role. |
| `route.to.isRecipient` | boolean | | | | Recipient role. |
| `route.to.phonePrefix` | string | | | | Dial code, digits only. |
| `route.to.phoneNumber` | string | | | | National number. |
| `route.to.email` | string | | | | Contact email. |
| `route.to.addressLine` | string | | | | Street / address line. |
| `route.to.countryCode` | string | | ISO-2 | | Country. |
| `route.to.postalCode` | string | | | | Postal code. |
| `route.to.province` | string | | | | State / province. |
| `route.to.district` | string | | | | County / district. |
| `route.to.city` | string | | | | City. |
| `packages` | object | | | | Packages milestone. |
| `packages.items` | array | | | | The parcels. |
| `packages.items[].id` | string | | | | Line id. |
| `packages.items[].type` | string | | "box", "pallet", "envelope", "tube", "poly_mail", "container", "bulk_cargo" | | Packaging type. |
| `packages.items[].description` | string | | | | Goods description. |
| `packages.items[].quantity` | string | | | | Piece count (numeric string). |
| `packages.items[].weight` | string | | | | Weight per piece (numeric string). |
| `packages.items[].weightUnit` | string | | "kg", "lb" | | Weight unit. |
| `packages.items[].length` | string | | | | Length (numeric string). |
| `packages.items[].width` | string | | | | Width (numeric string). |
| `packages.items[].height` | string | | | | Height (numeric string). |
| `packages.items[].dimUnit` | string | | "cm", "in" | | Dimension unit. |
| `packages.items[].value` | string | | | | Declared value (numeric string). |
| `packages.items[].valueCurrency` | string | | "THB", "USD", "EUR", "GBP" | | Declared-value currency. |
| `requireDocument` | boolean | | true, false | | Package Documents STATE flag — true = documents required, false = "none required" acknowledged, null = not set. NOT the files (those are `attachments`). |
| `service` | object | | | | Service milestone (chosen rate + config). |
| `service.providerId` | string | | | | Chosen carrier id. |
| `service.providerCode` | string | | | | Carrier code. |
| `service.providerName` | string | | | | Carrier display name. |
| `service.price` | object | | | | Frozen quote captured at save. |
| `service.price.thb` | number | | | | Total price, THB. |
| `service.price.zoneName` | string | | | | Rated zone. |
| `service.price.customsThb` | number | | | | Customs component, THB. |
| `service.price.pickupThb` | number | | | | Pickup component, THB (optional). |
| `service.shipDate` | string | | YYYY-MM-DD | | Ship date. |
| `service.pickupGroupId` | string | | | | Pickup-option group (null = provider default). |
| `service.pickupGroupName` | string | | | | Pickup group name. |
| `service.pickupAddress` | string | | | | Pickup address. |
| `service.pickupInstructions` | string | | | | Pickup instructions. |
| `service.options` | array | | | | Selected service-option names (string[]). |
| `service.references` | array | | | | Shipment references (string[]). |
| `service.note` | string | | | | Internal note. |
| `notifications` | object | | | | Notifications milestone. |
| `notifications.sender` | object | | | | Sender notification prefs. |
| `notifications.sender.email` | string | | | | Notified email. |
| `notifications.sender.language` | string | | "en", "th" | | Email language. |
| `notifications.sender.options` | array | | "created", "in_transit", "delivered", "exception" | | Events to notify on (string[]). |
| `notifications.recipients` | array | | | | Recipient notification prefs. |
| `notifications.recipients[].email` | string | | | | Notified email. |
| `notifications.recipients[].language` | string | | "en", "th" | | Email language. |
| `notifications.recipients[].options` | array | | "created", "in_transit", "delivered", "exception" | | Events to notify on (string[]). |
| `createdAt` | string | ✓ | | | ISO-8601 timestamp. |
| `updatedAt` | string | ✓ | | | ISO-8601 timestamp. |
| `attachments` | array | | | | Uploaded document FILES (metadata) — embedded on a single-order GET/PUT, omitted when the order has none. Distinct from `requireDocument` (that is the state flag; these are the files). Manage them via the documents sub-resource below. |
| `attachments[].id` | uuid | ✓ | | | Document id. |
| `attachments[].name` | string | ✓ | | | Display name. |
| `attachments[].description` | string | | | | Optional note. |
| `attachments[].docType` | string | ✓ | "INVOICE", "RECEIPT", "TAX", "VAT", "PACKING_LIST", "CUSTOMS", "OTHER" | | Document type. |
| `attachments[].contentType` | string | | | | MIME type as uploaded. |
| `attachments[].sizeBytes` | number | ✓ | | | Decoded file size in bytes. |
| `attachments[].createdAt` | string | ✓ | | | ISO-8601 timestamp. |
**Request example**
```json
{
"fromCountry": "TH",
"toCountry": "US"
}
```
**Response example**
```json
{
"id": "3f9a1c62-8d47-4a1e-9b0c-2e5f6a7b8c90",
"orderNo": "RDOR202607000042",
"status": "CREATED",
"fromCountry": "TH",
"toCountry": "US",
"serviceProvider": null,
"route": null,
"packages": null,
"requireDocument": null,
"service": null,
"notifications": null,
"createdAt": "2026-07-16T09:00:00Z",
"updatedAt": "2026-07-16T09:00:00Z"
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The order was created; the body is the stored order. |
| 400 | Bad Request | A country is missing or not a known ISO code. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 400,
"error": "request failed validation",
"fields": [
{
"field": "toCountry",
"message": "is not a valid country"
}
]
}
```
### Get an order
`GET https://api.parcelradar.io/api/partner/v1/orders/{id}`
Fetch one order by id.
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | | | Order id. |
| `orderNo` | string | ✓ | e.g. "RDOR202607000042" | | Reference (RDOR + YYYYMM + 6-digit sequence), unique per tenant. |
| `status` | string | ✓ | "CREATED", "PROCESSING", "IN_TRANSIT", "DELIVERED", "EXCEPTION", "ON_HOLD", "RETURNING", "RETURNED" | | Lifecycle state. |
| `fromCountry` | string | ✓ | ISO-2, e.g. "TH" | | Origin, upper-cased. |
| `toCountry` | string | ✓ | ISO-2, e.g. "US" | | Destination, upper-cased. |
| `serviceProvider` | string | | | | Chosen carrier, else null. |
| `route` | object | | | | Route milestone (ship-from / deliver-to). |
| `route.from` | object | | | | Ship-from address. |
| `route.from.name` | string | | | | Contact name. |
| `route.from.alias` | string | | | | Nickname/label. |
| `route.from.isSender` | boolean | | | | Sender role. |
| `route.from.isRecipient` | boolean | | | | Recipient role. |
| `route.from.phonePrefix` | string | | | | Dial code, digits only. |
| `route.from.phoneNumber` | string | | | | National number. |
| `route.from.email` | string | | | | Contact email. |
| `route.from.addressLine` | string | | | | Street / address line. |
| `route.from.countryCode` | string | | ISO-2 | | Country. |
| `route.from.postalCode` | string | | | | Postal code. |
| `route.from.province` | string | | | | State / province. |
| `route.from.district` | string | | | | County / district. |
| `route.from.city` | string | | | | City. |
| `route.to` | object | | | | Deliver-to address. |
| `route.to.name` | string | | | | Contact name. |
| `route.to.alias` | string | | | | Nickname/label. |
| `route.to.isSender` | boolean | | | | Sender role. |
| `route.to.isRecipient` | boolean | | | | Recipient role. |
| `route.to.phonePrefix` | string | | | | Dial code, digits only. |
| `route.to.phoneNumber` | string | | | | National number. |
| `route.to.email` | string | | | | Contact email. |
| `route.to.addressLine` | string | | | | Street / address line. |
| `route.to.countryCode` | string | | ISO-2 | | Country. |
| `route.to.postalCode` | string | | | | Postal code. |
| `route.to.province` | string | | | | State / province. |
| `route.to.district` | string | | | | County / district. |
| `route.to.city` | string | | | | City. |
| `packages` | object | | | | Packages milestone. |
| `packages.items` | array | | | | The parcels. |
| `packages.items[].id` | string | | | | Line id. |
| `packages.items[].type` | string | | "box", "pallet", "envelope", "tube", "poly_mail", "container", "bulk_cargo" | | Packaging type. |
| `packages.items[].description` | string | | | | Goods description. |
| `packages.items[].quantity` | string | | | | Piece count (numeric string). |
| `packages.items[].weight` | string | | | | Weight per piece (numeric string). |
| `packages.items[].weightUnit` | string | | "kg", "lb" | | Weight unit. |
| `packages.items[].length` | string | | | | Length (numeric string). |
| `packages.items[].width` | string | | | | Width (numeric string). |
| `packages.items[].height` | string | | | | Height (numeric string). |
| `packages.items[].dimUnit` | string | | "cm", "in" | | Dimension unit. |
| `packages.items[].value` | string | | | | Declared value (numeric string). |
| `packages.items[].valueCurrency` | string | | "THB", "USD", "EUR", "GBP" | | Declared-value currency. |
| `requireDocument` | boolean | | true, false | | Package Documents STATE flag — true = documents required, false = "none required" acknowledged, null = not set. NOT the files (those are `attachments`). |
| `service` | object | | | | Service milestone (chosen rate + config). |
| `service.providerId` | string | | | | Chosen carrier id. |
| `service.providerCode` | string | | | | Carrier code. |
| `service.providerName` | string | | | | Carrier display name. |
| `service.price` | object | | | | Frozen quote captured at save. |
| `service.price.thb` | number | | | | Total price, THB. |
| `service.price.zoneName` | string | | | | Rated zone. |
| `service.price.customsThb` | number | | | | Customs component, THB. |
| `service.price.pickupThb` | number | | | | Pickup component, THB (optional). |
| `service.shipDate` | string | | YYYY-MM-DD | | Ship date. |
| `service.pickupGroupId` | string | | | | Pickup-option group (null = provider default). |
| `service.pickupGroupName` | string | | | | Pickup group name. |
| `service.pickupAddress` | string | | | | Pickup address. |
| `service.pickupInstructions` | string | | | | Pickup instructions. |
| `service.options` | array | | | | Selected service-option names (string[]). |
| `service.references` | array | | | | Shipment references (string[]). |
| `service.note` | string | | | | Internal note. |
| `notifications` | object | | | | Notifications milestone. |
| `notifications.sender` | object | | | | Sender notification prefs. |
| `notifications.sender.email` | string | | | | Notified email. |
| `notifications.sender.language` | string | | "en", "th" | | Email language. |
| `notifications.sender.options` | array | | "created", "in_transit", "delivered", "exception" | | Events to notify on (string[]). |
| `notifications.recipients` | array | | | | Recipient notification prefs. |
| `notifications.recipients[].email` | string | | | | Notified email. |
| `notifications.recipients[].language` | string | | "en", "th" | | Email language. |
| `notifications.recipients[].options` | array | | "created", "in_transit", "delivered", "exception" | | Events to notify on (string[]). |
| `createdAt` | string | ✓ | | | ISO-8601 timestamp. |
| `updatedAt` | string | ✓ | | | ISO-8601 timestamp. |
| `attachments` | array | | | | Uploaded document FILES (metadata) — embedded on a single-order GET/PUT, omitted when the order has none. Distinct from `requireDocument` (that is the state flag; these are the files). Manage them via the documents sub-resource below. |
| `attachments[].id` | uuid | ✓ | | | Document id. |
| `attachments[].name` | string | ✓ | | | Display name. |
| `attachments[].description` | string | | | | Optional note. |
| `attachments[].docType` | string | ✓ | "INVOICE", "RECEIPT", "TAX", "VAT", "PACKING_LIST", "CUSTOMS", "OTHER" | | Document type. |
| `attachments[].contentType` | string | | | | MIME type as uploaded. |
| `attachments[].sizeBytes` | number | ✓ | | | Decoded file size in bytes. |
| `attachments[].createdAt` | string | ✓ | | | ISO-8601 timestamp. |
**Response example**
```json
{
"id": "af1326f0-eaf8-4dbd-801e-43d1daf99a23",
"orderNo": "RDOR202607000039",
"status": "PROCESSING",
"fromCountry": "TH",
"toCountry": "US",
"serviceProvider": "DHL Express",
"route": {
"from": {
"name": "Green Parker",
"alias": "Office",
"isSender": true,
"isRecipient": false,
"phonePrefix": "66",
"phoneNumber": "973331702",
"email": "green@acme.com",
"addressLine": "514 Miriam Knolls",
"countryCode": "TH",
"postalCode": "10110",
"province": "Bangkok",
"district": "Watthana",
"city": "Bangkok"
},
"to": {
"name": "Martha Howell",
"alias": "HQ",
"isSender": false,
"isRecipient": true,
"phonePrefix": "1",
"phoneNumber": "5692748790",
"email": "martha@acme.com",
"addressLine": "274 Wellington Road",
"countryCode": "US",
"postalCode": "90001",
"province": "California",
"district": "Los Angeles County",
"city": "Los Angeles"
}
},
"packages": {
"items": [
{
"id": "pkg-1",
"type": "box",
"description": "Wooden soap",
"quantity": "1",
"weight": "1",
"weightUnit": "kg",
"length": "42.10",
"width": "54.80",
"height": "33.50",
"dimUnit": "cm",
"value": "457",
"valueCurrency": "THB"
}
]
},
"requireDocument": true,
"service": {
"providerId": "019e984a-f8b4-726f-8f02-0b51e7016f46",
"providerCode": "RADIMEXP",
"providerName": "DHL Express",
"price": {
"thb": 100,
"zoneName": "Default",
"customsThb": 0,
"pickupThb": 0
},
"shipDate": "2026-07-19",
"pickupGroupId": null,
"pickupGroupName": null,
"pickupAddress": "",
"pickupInstructions": "Ring the bell at the front desk.",
"options": [],
"references": [
"PO-412553",
"REF-A9BSA"
],
"note": "Handle with care."
},
"notifications": {
"sender": {
"email": "green@acme.com",
"language": "en",
"options": [
"created",
"in_transit",
"delivered",
"exception"
]
},
"recipients": [
{
"email": "martha@acme.com",
"language": "en",
"options": [
"created",
"delivered"
]
}
]
},
"createdAt": "2026-07-17T09:45:27Z",
"updatedAt": "2026-07-19T18:11:24Z",
"attachments": [
{
"id": "7a1b9c2d-0e3f-4a5b-8c6d-9e0f1a2b3c4d",
"name": "commercial-invoice.pdf",
"description": "Signed invoice",
"docType": "INVOICE",
"contentType": "application/pdf",
"sizeBytes": 20480,
"createdAt": "2026-07-18T04:12:00Z"
}
]
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The order. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 404 | Not Found | No order with that id belongs to your tenant. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 404,
"error": "no order found with that id"
}
```
### Update an order
`PUT https://api.parcelradar.io/api/partner/v1/orders/{id}`
Full replacement (PUT): send the complete order — omitted fields are cleared. Status is unchanged (use suspend).
**Request**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `fromCountry` | string | ✓ | ISO-2, in the country list | | Origin. Must be in the country list; omitted or unknown → 400. |
| `toCountry` | string | ✓ | ISO-2, in the country list | | Destination. Must be in the country list; omitted or unknown → 400. |
| `serviceProvider` | string | ✓ | ≤ 200 chars | | Chosen carrier. Omitted → cleared to null. |
| `route` | object | ✓ | | | Route milestone object. Omitted → cleared. |
| `route.from` | object | | | | Ship-from address. |
| `route.from.name` | string | | | | Contact name. |
| `route.from.alias` | string | | | | Nickname/label. |
| `route.from.isSender` | boolean | | | | Sender role. |
| `route.from.isRecipient` | boolean | | | | Recipient role. |
| `route.from.phonePrefix` | string | | | | Dial code, digits only. |
| `route.from.phoneNumber` | string | | | | National number. |
| `route.from.email` | string | | | | Contact email. |
| `route.from.addressLine` | string | | | | Street / address line. |
| `route.from.countryCode` | string | | ISO-2 | | Country. |
| `route.from.postalCode` | string | | | | Postal code. |
| `route.from.province` | string | | | | State / province. |
| `route.from.district` | string | | | | County / district. |
| `route.from.city` | string | | | | City. |
| `route.to` | object | | | | Deliver-to address. |
| `route.to.name` | string | | | | Contact name. |
| `route.to.alias` | string | | | | Nickname/label. |
| `route.to.isSender` | boolean | | | | Sender role. |
| `route.to.isRecipient` | boolean | | | | Recipient role. |
| `route.to.phonePrefix` | string | | | | Dial code, digits only. |
| `route.to.phoneNumber` | string | | | | National number. |
| `route.to.email` | string | | | | Contact email. |
| `route.to.addressLine` | string | | | | Street / address line. |
| `route.to.countryCode` | string | | ISO-2 | | Country. |
| `route.to.postalCode` | string | | | | Postal code. |
| `route.to.province` | string | | | | State / province. |
| `route.to.district` | string | | | | County / district. |
| `route.to.city` | string | | | | City. |
| `packages` | object | ✓ | | | Packages milestone object. Omitted → cleared. |
| `packages.items` | array | | | | The parcels. |
| `packages.items[].id` | string | | | | Line id. |
| `packages.items[].type` | string | | "box", "pallet", "envelope", "tube", "poly_mail", "container", "bulk_cargo" | | Packaging type. |
| `packages.items[].description` | string | | | | Goods description. |
| `packages.items[].quantity` | string | | | | Piece count (numeric string). |
| `packages.items[].weight` | string | | | | Weight per piece (numeric string). |
| `packages.items[].weightUnit` | string | | "kg", "lb" | | Weight unit. |
| `packages.items[].length` | string | | | | Length (numeric string). |
| `packages.items[].width` | string | | | | Width (numeric string). |
| `packages.items[].height` | string | | | | Height (numeric string). |
| `packages.items[].dimUnit` | string | | "cm", "in" | | Dimension unit. |
| `packages.items[].value` | string | | | | Declared value (numeric string). |
| `packages.items[].valueCurrency` | string | | "THB", "USD", "EUR", "GBP" | | Declared-value currency. |
| `requireDocument` | boolean | | true, false | | Package Documents STATE flag (true = required, false = "none required", omitted = cleared). To add files use `attachments` below (or the documents sub-resource). |
| `service` | object | ✓ | | | Service milestone object. Omitted → cleared. |
| `service.providerId` | string | | | | Chosen carrier id. |
| `service.providerCode` | string | | | | Carrier code. |
| `service.providerName` | string | | | | Carrier display name. |
| `service.price` | object | | | | Frozen quote captured at save. |
| `service.price.thb` | number | | | | Total price, THB. |
| `service.price.zoneName` | string | | | | Rated zone. |
| `service.price.customsThb` | number | | | | Customs component, THB. |
| `service.price.pickupThb` | number | | | | Pickup component, THB (optional). |
| `service.shipDate` | string | | YYYY-MM-DD | | Ship date. |
| `service.pickupGroupId` | string | | | | Pickup-option group (null = provider default). |
| `service.pickupGroupName` | string | | | | Pickup group name. |
| `service.pickupAddress` | string | | | | Pickup address. |
| `service.pickupInstructions` | string | | | | Pickup instructions. |
| `service.options` | array | | | | Selected service-option names (string[]). |
| `service.references` | array | | | | Shipment references (string[]). |
| `service.note` | string | | | | Internal note. |
| `notifications` | object | ✓ | | | Notifications milestone object. Omitted → cleared. |
| `notifications.sender` | object | | | | Sender notification prefs. |
| `notifications.sender.email` | string | | | | Notified email. |
| `notifications.sender.language` | string | | "en", "th" | | Email language. |
| `notifications.sender.options` | array | | "created", "in_transit", "delivered", "exception" | | Events to notify on (string[]). |
| `notifications.recipients` | array | | | | Recipient notification prefs. |
| `notifications.recipients[].email` | string | | | | Notified email. |
| `notifications.recipients[].language` | string | | "en", "th" | | Email language. |
| `notifications.recipients[].options` | array | | "created", "in_transit", "delivered", "exception" | | Events to notify on (string[]). |
| `attachments` | array | | | | OPTIONAL document files (base64) to ADD in this call — appended, NOT a replacement (existing documents stay; remove them via the sub-resource DELETE). Each entry is an upload body. The response embeds the resulting list. |
| `attachments[].name` | string | ✓ | ≤ 200 chars | | Display name. |
| `attachments[].description` | string | | ≤ 500 chars | | Optional note. |
| `attachments[].docType` | string | ✓ | "INVOICE", "RECEIPT", "TAX", "VAT", "PACKING_LIST", "CUSTOMS", "OTHER" | | One of the document types. |
| `attachments[].contentBase64` | string | ✓ | | | File bytes, base64 (a `data:…;base64,` prefix is accepted). Decoded ≤ 10 MB. |
| `attachments[].contentType` | string | | ≤ 200 chars | | MIME type (e.g. application/pdf). |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | | | Order id. |
| `orderNo` | string | ✓ | e.g. "RDOR202607000042" | | Reference (RDOR + YYYYMM + 6-digit sequence), unique per tenant. |
| `status` | string | ✓ | "CREATED", "PROCESSING", "IN_TRANSIT", "DELIVERED", "EXCEPTION", "ON_HOLD", "RETURNING", "RETURNED" | | Lifecycle state. |
| `fromCountry` | string | ✓ | ISO-2, e.g. "TH" | | Origin, upper-cased. |
| `toCountry` | string | ✓ | ISO-2, e.g. "US" | | Destination, upper-cased. |
| `serviceProvider` | string | | | | Chosen carrier, else null. |
| `route` | object | | | | Route milestone (ship-from / deliver-to). |
| `route.from` | object | | | | Ship-from address. |
| `route.from.name` | string | | | | Contact name. |
| `route.from.alias` | string | | | | Nickname/label. |
| `route.from.isSender` | boolean | | | | Sender role. |
| `route.from.isRecipient` | boolean | | | | Recipient role. |
| `route.from.phonePrefix` | string | | | | Dial code, digits only. |
| `route.from.phoneNumber` | string | | | | National number. |
| `route.from.email` | string | | | | Contact email. |
| `route.from.addressLine` | string | | | | Street / address line. |
| `route.from.countryCode` | string | | ISO-2 | | Country. |
| `route.from.postalCode` | string | | | | Postal code. |
| `route.from.province` | string | | | | State / province. |
| `route.from.district` | string | | | | County / district. |
| `route.from.city` | string | | | | City. |
| `route.to` | object | | | | Deliver-to address. |
| `route.to.name` | string | | | | Contact name. |
| `route.to.alias` | string | | | | Nickname/label. |
| `route.to.isSender` | boolean | | | | Sender role. |
| `route.to.isRecipient` | boolean | | | | Recipient role. |
| `route.to.phonePrefix` | string | | | | Dial code, digits only. |
| `route.to.phoneNumber` | string | | | | National number. |
| `route.to.email` | string | | | | Contact email. |
| `route.to.addressLine` | string | | | | Street / address line. |
| `route.to.countryCode` | string | | ISO-2 | | Country. |
| `route.to.postalCode` | string | | | | Postal code. |
| `route.to.province` | string | | | | State / province. |
| `route.to.district` | string | | | | County / district. |
| `route.to.city` | string | | | | City. |
| `packages` | object | | | | Packages milestone. |
| `packages.items` | array | | | | The parcels. |
| `packages.items[].id` | string | | | | Line id. |
| `packages.items[].type` | string | | "box", "pallet", "envelope", "tube", "poly_mail", "container", "bulk_cargo" | | Packaging type. |
| `packages.items[].description` | string | | | | Goods description. |
| `packages.items[].quantity` | string | | | | Piece count (numeric string). |
| `packages.items[].weight` | string | | | | Weight per piece (numeric string). |
| `packages.items[].weightUnit` | string | | "kg", "lb" | | Weight unit. |
| `packages.items[].length` | string | | | | Length (numeric string). |
| `packages.items[].width` | string | | | | Width (numeric string). |
| `packages.items[].height` | string | | | | Height (numeric string). |
| `packages.items[].dimUnit` | string | | "cm", "in" | | Dimension unit. |
| `packages.items[].value` | string | | | | Declared value (numeric string). |
| `packages.items[].valueCurrency` | string | | "THB", "USD", "EUR", "GBP" | | Declared-value currency. |
| `requireDocument` | boolean | | true, false | | Package Documents STATE flag — true = documents required, false = "none required" acknowledged, null = not set. NOT the files (those are `attachments`). |
| `service` | object | | | | Service milestone (chosen rate + config). |
| `service.providerId` | string | | | | Chosen carrier id. |
| `service.providerCode` | string | | | | Carrier code. |
| `service.providerName` | string | | | | Carrier display name. |
| `service.price` | object | | | | Frozen quote captured at save. |
| `service.price.thb` | number | | | | Total price, THB. |
| `service.price.zoneName` | string | | | | Rated zone. |
| `service.price.customsThb` | number | | | | Customs component, THB. |
| `service.price.pickupThb` | number | | | | Pickup component, THB (optional). |
| `service.shipDate` | string | | YYYY-MM-DD | | Ship date. |
| `service.pickupGroupId` | string | | | | Pickup-option group (null = provider default). |
| `service.pickupGroupName` | string | | | | Pickup group name. |
| `service.pickupAddress` | string | | | | Pickup address. |
| `service.pickupInstructions` | string | | | | Pickup instructions. |
| `service.options` | array | | | | Selected service-option names (string[]). |
| `service.references` | array | | | | Shipment references (string[]). |
| `service.note` | string | | | | Internal note. |
| `notifications` | object | | | | Notifications milestone. |
| `notifications.sender` | object | | | | Sender notification prefs. |
| `notifications.sender.email` | string | | | | Notified email. |
| `notifications.sender.language` | string | | "en", "th" | | Email language. |
| `notifications.sender.options` | array | | "created", "in_transit", "delivered", "exception" | | Events to notify on (string[]). |
| `notifications.recipients` | array | | | | Recipient notification prefs. |
| `notifications.recipients[].email` | string | | | | Notified email. |
| `notifications.recipients[].language` | string | | "en", "th" | | Email language. |
| `notifications.recipients[].options` | array | | "created", "in_transit", "delivered", "exception" | | Events to notify on (string[]). |
| `createdAt` | string | ✓ | | | ISO-8601 timestamp. |
| `updatedAt` | string | ✓ | | | ISO-8601 timestamp. |
| `attachments` | array | | | | Uploaded document FILES (metadata) — embedded on a single-order GET/PUT, omitted when the order has none. Distinct from `requireDocument` (that is the state flag; these are the files). Manage them via the documents sub-resource below. |
| `attachments[].id` | uuid | ✓ | | | Document id. |
| `attachments[].name` | string | ✓ | | | Display name. |
| `attachments[].description` | string | | | | Optional note. |
| `attachments[].docType` | string | ✓ | "INVOICE", "RECEIPT", "TAX", "VAT", "PACKING_LIST", "CUSTOMS", "OTHER" | | Document type. |
| `attachments[].contentType` | string | | | | MIME type as uploaded. |
| `attachments[].sizeBytes` | number | ✓ | | | Decoded file size in bytes. |
| `attachments[].createdAt` | string | ✓ | | | ISO-8601 timestamp. |
**Request example**
```json
{
"fromCountry": "TH",
"toCountry": "US",
"serviceProvider": "DHL",
"route": null,
"packages": {
"items": []
},
"requireDocument": true,
"service": null,
"notifications": null,
"attachments": [
{
"name": "commercial-invoice.pdf",
"docType": "INVOICE",
"contentType": "application/pdf",
"contentBase64": "JVBERi0xLjcKJ..."
}
]
}
```
**Response example**
```json
{
"id": "3f9a1c62-8d47-4a1e-9b0c-2e5f6a7b8c90",
"orderNo": "RDOR202607000042",
"status": "CREATED",
"fromCountry": "TH",
"toCountry": "US",
"serviceProvider": "DHL",
"route": null,
"packages": {
"items": []
},
"requireDocument": true,
"service": null,
"notifications": null,
"createdAt": "2026-07-16T09:00:00Z",
"updatedAt": "2026-07-16T12:05:00Z",
"attachments": [
{
"id": "7a1b9c2d-0e3f-4a5b-8c6d-9e0f1a2b3c4d",
"name": "commercial-invoice.pdf",
"description": null,
"docType": "INVOICE",
"contentType": "application/pdf",
"sizeBytes": 20480,
"createdAt": "2026-07-16T12:05:00Z"
}
]
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The order was updated; the body is the stored order. |
| 400 | Bad Request | A country is missing or not a known ISO code, or serviceProvider is over its length limit. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 404 | Not Found | No order with that id belongs to your tenant. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 404,
"error": "no order found with that id"
}
```
### Suspend an order
`POST https://api.parcelradar.io/api/partner/v1/orders/{id}/suspend`
Put an order on hold (→ ON_HOLD). Only from CREATED or PROCESSING.
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | | | Order id. |
| `orderNo` | string | ✓ | e.g. "RDOR202607000042" | | Reference (RDOR + YYYYMM + 6-digit sequence), unique per tenant. |
| `status` | string | ✓ | "CREATED", "PROCESSING", "IN_TRANSIT", "DELIVERED", "EXCEPTION", "ON_HOLD", "RETURNING", "RETURNED" | | Lifecycle state. |
| `fromCountry` | string | ✓ | ISO-2, e.g. "TH" | | Origin, upper-cased. |
| `toCountry` | string | ✓ | ISO-2, e.g. "US" | | Destination, upper-cased. |
| `serviceProvider` | string | | | | Chosen carrier, else null. |
| `route` | object | | | | Route milestone (ship-from / deliver-to). |
| `route.from` | object | | | | Ship-from address. |
| `route.from.name` | string | | | | Contact name. |
| `route.from.alias` | string | | | | Nickname/label. |
| `route.from.isSender` | boolean | | | | Sender role. |
| `route.from.isRecipient` | boolean | | | | Recipient role. |
| `route.from.phonePrefix` | string | | | | Dial code, digits only. |
| `route.from.phoneNumber` | string | | | | National number. |
| `route.from.email` | string | | | | Contact email. |
| `route.from.addressLine` | string | | | | Street / address line. |
| `route.from.countryCode` | string | | ISO-2 | | Country. |
| `route.from.postalCode` | string | | | | Postal code. |
| `route.from.province` | string | | | | State / province. |
| `route.from.district` | string | | | | County / district. |
| `route.from.city` | string | | | | City. |
| `route.to` | object | | | | Deliver-to address. |
| `route.to.name` | string | | | | Contact name. |
| `route.to.alias` | string | | | | Nickname/label. |
| `route.to.isSender` | boolean | | | | Sender role. |
| `route.to.isRecipient` | boolean | | | | Recipient role. |
| `route.to.phonePrefix` | string | | | | Dial code, digits only. |
| `route.to.phoneNumber` | string | | | | National number. |
| `route.to.email` | string | | | | Contact email. |
| `route.to.addressLine` | string | | | | Street / address line. |
| `route.to.countryCode` | string | | ISO-2 | | Country. |
| `route.to.postalCode` | string | | | | Postal code. |
| `route.to.province` | string | | | | State / province. |
| `route.to.district` | string | | | | County / district. |
| `route.to.city` | string | | | | City. |
| `packages` | object | | | | Packages milestone. |
| `packages.items` | array | | | | The parcels. |
| `packages.items[].id` | string | | | | Line id. |
| `packages.items[].type` | string | | "box", "pallet", "envelope", "tube", "poly_mail", "container", "bulk_cargo" | | Packaging type. |
| `packages.items[].description` | string | | | | Goods description. |
| `packages.items[].quantity` | string | | | | Piece count (numeric string). |
| `packages.items[].weight` | string | | | | Weight per piece (numeric string). |
| `packages.items[].weightUnit` | string | | "kg", "lb" | | Weight unit. |
| `packages.items[].length` | string | | | | Length (numeric string). |
| `packages.items[].width` | string | | | | Width (numeric string). |
| `packages.items[].height` | string | | | | Height (numeric string). |
| `packages.items[].dimUnit` | string | | "cm", "in" | | Dimension unit. |
| `packages.items[].value` | string | | | | Declared value (numeric string). |
| `packages.items[].valueCurrency` | string | | "THB", "USD", "EUR", "GBP" | | Declared-value currency. |
| `requireDocument` | boolean | | true, false | | Package Documents STATE flag — true = documents required, false = "none required" acknowledged, null = not set. NOT the files (those are `attachments`). |
| `service` | object | | | | Service milestone (chosen rate + config). |
| `service.providerId` | string | | | | Chosen carrier id. |
| `service.providerCode` | string | | | | Carrier code. |
| `service.providerName` | string | | | | Carrier display name. |
| `service.price` | object | | | | Frozen quote captured at save. |
| `service.price.thb` | number | | | | Total price, THB. |
| `service.price.zoneName` | string | | | | Rated zone. |
| `service.price.customsThb` | number | | | | Customs component, THB. |
| `service.price.pickupThb` | number | | | | Pickup component, THB (optional). |
| `service.shipDate` | string | | YYYY-MM-DD | | Ship date. |
| `service.pickupGroupId` | string | | | | Pickup-option group (null = provider default). |
| `service.pickupGroupName` | string | | | | Pickup group name. |
| `service.pickupAddress` | string | | | | Pickup address. |
| `service.pickupInstructions` | string | | | | Pickup instructions. |
| `service.options` | array | | | | Selected service-option names (string[]). |
| `service.references` | array | | | | Shipment references (string[]). |
| `service.note` | string | | | | Internal note. |
| `notifications` | object | | | | Notifications milestone. |
| `notifications.sender` | object | | | | Sender notification prefs. |
| `notifications.sender.email` | string | | | | Notified email. |
| `notifications.sender.language` | string | | "en", "th" | | Email language. |
| `notifications.sender.options` | array | | "created", "in_transit", "delivered", "exception" | | Events to notify on (string[]). |
| `notifications.recipients` | array | | | | Recipient notification prefs. |
| `notifications.recipients[].email` | string | | | | Notified email. |
| `notifications.recipients[].language` | string | | "en", "th" | | Email language. |
| `notifications.recipients[].options` | array | | "created", "in_transit", "delivered", "exception" | | Events to notify on (string[]). |
| `createdAt` | string | ✓ | | | ISO-8601 timestamp. |
| `updatedAt` | string | ✓ | | | ISO-8601 timestamp. |
| `attachments` | array | | | | Uploaded document FILES (metadata) — embedded on a single-order GET/PUT, omitted when the order has none. Distinct from `requireDocument` (that is the state flag; these are the files). Manage them via the documents sub-resource below. |
| `attachments[].id` | uuid | ✓ | | | Document id. |
| `attachments[].name` | string | ✓ | | | Display name. |
| `attachments[].description` | string | | | | Optional note. |
| `attachments[].docType` | string | ✓ | "INVOICE", "RECEIPT", "TAX", "VAT", "PACKING_LIST", "CUSTOMS", "OTHER" | | Document type. |
| `attachments[].contentType` | string | | | | MIME type as uploaded. |
| `attachments[].sizeBytes` | number | ✓ | | | Decoded file size in bytes. |
| `attachments[].createdAt` | string | ✓ | | | ISO-8601 timestamp. |
**Response example**
```json
{
"id": "3f9a1c62-8d47-4a1e-9b0c-2e5f6a7b8c90",
"orderNo": "RDOR202607000042",
"status": "ON_HOLD",
"fromCountry": "TH",
"toCountry": "US",
"serviceProvider": null,
"route": null,
"packages": null,
"requireDocument": null,
"service": null,
"notifications": null,
"createdAt": "2026-07-16T09:00:00Z",
"updatedAt": "2026-07-16T13:40:00Z"
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The order is now ON_HOLD; the body is the stored order. |
| 400 | Bad Request | The order is past the early stage and can't be suspended. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 404 | Not Found | No order with that id belongs to your tenant. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 400,
"error": "order cannot be suspended from its current status"
}
```
## Order Documents
The files attached to an order's Package Documents (invoices, packing lists, …). Bytes travel base64 — no multipart. Also embedded as `attachments` on a single-order GET. Partner lane only.
### List an order's documents
`GET https://api.parcelradar.io/api/partner/v1/orders/{orderId}/documents`
The order's uploaded Package Documents (metadata only, newest first). These are the FILES — distinct from the `documents` milestone (the tab's JSON state). The same list is embedded as `attachments` on a single-order GET.
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | | | Document id. |
| `name` | string | ✓ | | | Display name. |
| `description` | string | | | | Optional note. |
| `docType` | string | ✓ | "INVOICE", "RECEIPT", "TAX", "VAT", "PACKING_LIST", "CUSTOMS", "OTHER" | | Document type. |
| `contentType` | string | | | | MIME type as uploaded. |
| `sizeBytes` | number | ✓ | | | Decoded file size in bytes. |
| `createdAt` | string | ✓ | | | ISO-8601 timestamp. |
**Response example**
```json
[
{
"id": "7a1b9c2d-0e3f-4a5b-8c6d-9e0f1a2b3c4d",
"name": "commercial-invoice.pdf",
"description": "Signed invoice",
"docType": "INVOICE",
"contentType": "application/pdf",
"sizeBytes": 20480,
"createdAt": "2026-07-18T04:12:00Z"
}
]
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | A JSON array of the order's documents (empty [] when none). |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 404 | Not Found | No order with that id belongs to your tenant. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 404,
"error": "no order found with that id"
}
```
### Upload an order document
`POST https://api.parcelradar.io/api/partner/v1/orders/{orderId}/documents`
Attach a file to the order. The bytes travel base64-encoded in `contentBase64` (a `data:…;base64,` prefix is accepted) — plain JSON, no multipart. Decoded size ≤ 10 MB.
**Request**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `name` | string | ✓ | ≤ 200 chars | | Display name. |
| `description` | string | | ≤ 500 chars | | Optional note. |
| `docType` | string | ✓ | "INVOICE", "RECEIPT", "TAX", "VAT", "PACKING_LIST", "CUSTOMS", "OTHER" | | One of the document types. |
| `contentBase64` | string | ✓ | | | File bytes, base64 (a `data:…;base64,` prefix is accepted). Decoded ≤ 10 MB. |
| `contentType` | string | | ≤ 200 chars | | MIME type (e.g. application/pdf). |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | | | Document id. |
| `name` | string | ✓ | | | Display name. |
| `description` | string | | | | Optional note. |
| `docType` | string | ✓ | "INVOICE", "RECEIPT", "TAX", "VAT", "PACKING_LIST", "CUSTOMS", "OTHER" | | Document type. |
| `contentType` | string | | | | MIME type as uploaded. |
| `sizeBytes` | number | ✓ | | | Decoded file size in bytes. |
| `createdAt` | string | ✓ | | | ISO-8601 timestamp. |
**Request example**
```json
{
"name": "commercial-invoice.pdf",
"description": "Signed invoice",
"docType": "INVOICE",
"contentType": "application/pdf",
"contentBase64": "JVBERi0xLjcKJ..."
}
```
**Response example**
```json
{
"id": "7a1b9c2d-0e3f-4a5b-8c6d-9e0f1a2b3c4d",
"name": "commercial-invoice.pdf",
"description": "Signed invoice",
"docType": "INVOICE",
"contentType": "application/pdf",
"sizeBytes": 20480,
"createdAt": "2026-07-18T04:12:00Z"
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The document was stored; the body is its metadata. |
| 400 | Bad Request | Missing name/bytes, an unknown docType, invalid base64, or a file over 10 MB. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 404 | Not Found | No order with that id belongs to your tenant. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 400,
"error": "request failed validation",
"fields": [
{
"field": "docType",
"message": "must be one of INVOICE, RECEIPT, TAX, VAT, PACKING_LIST, CUSTOMS, OTHER"
}
]
}
```
### Update a document
`PUT https://api.parcelradar.io/api/partner/v1/orders/{orderId}/documents/{docId}`
Replace a document's metadata (name / description / docType). Include `contentBase64` to replace the file bytes too; omit it to keep the stored file.
**Request**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `name` | string | ✓ | ≤ 200 chars | | Display name. |
| `description` | string | | ≤ 500 chars | | Optional note. |
| `docType` | string | ✓ | "INVOICE", "RECEIPT", "TAX", "VAT", "PACKING_LIST", "CUSTOMS", "OTHER" | | One of the document types. |
| `contentBase64` | string | | | | New file bytes (base64) to replace the stored ones; omit/blank to keep them. Decoded ≤ 10 MB. |
| `contentType` | string | | ≤ 200 chars | | MIME type of the replacement bytes. |
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `id` | uuid | ✓ | | | Document id. |
| `name` | string | ✓ | | | Display name. |
| `description` | string | | | | Optional note. |
| `docType` | string | ✓ | "INVOICE", "RECEIPT", "TAX", "VAT", "PACKING_LIST", "CUSTOMS", "OTHER" | | Document type. |
| `contentType` | string | | | | MIME type as uploaded. |
| `sizeBytes` | number | ✓ | | | Decoded file size in bytes. |
| `createdAt` | string | ✓ | | | ISO-8601 timestamp. |
**Request example**
```json
{
"name": "commercial-invoice-v2.pdf",
"description": "Re-signed invoice",
"docType": "INVOICE"
}
```
**Response example**
```json
{
"id": "7a1b9c2d-0e3f-4a5b-8c6d-9e0f1a2b3c4d",
"name": "commercial-invoice-v2.pdf",
"description": "Re-signed invoice",
"docType": "INVOICE",
"contentType": "application/pdf",
"sizeBytes": 20480,
"createdAt": "2026-07-18T04:12:00Z"
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The document was updated; the body is its metadata. |
| 400 | Bad Request | Missing name, an unknown docType, invalid base64, or a replacement file over 10 MB. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 404 | Not Found | No such document for this order/tenant. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 404,
"error": "no document found with that id"
}
```
### Download a document
`GET https://api.parcelradar.io/api/partner/v1/orders/{orderId}/documents/{docId}`
Fetch one document WITH its bytes (base64). Decode `contentBase64` to a Blob/file and save it as `name`.
**Response**
| Field | Type | Required | Values | Default | Note |
| ----- | ---- | :------: | ------ | ------- | ---- |
| `name` | string | ✓ | | | Display name (save the file as this). |
| `contentType` | string | | | | MIME type as uploaded. |
| `contentBase64` | string | ✓ | | | File bytes, base64 — decode to a Blob/file. |
**Response example**
```json
{
"name": "commercial-invoice.pdf",
"contentType": "application/pdf",
"contentBase64": "JVBERi0xLjcKJ..."
}
```
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 200 | OK | The document with its base64 bytes. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 404 | Not Found | No such document for this order/tenant. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 404,
"error": "no document found with that id"
}
```
### Delete a document
`DELETE https://api.parcelradar.io/api/partner/v1/orders/{orderId}/documents/{docId}`
Remove a document from the order.
**Response codes**
| Code | Status | Description |
| ---- | ------ | ----------- |
| 204 | No Content | The document was deleted; no body. |
| 401 | Unauthorized | The X-API-Key header is missing, invalid, revoked, or expired. |
| 404 | Not Found | No such document for this order/tenant. |
| 503 | Service Unavailable | API-key verification was temporarily unavailable. Retry shortly. |
**Error example**
```json
{
"ok": false,
"status": 404,
"error": "no document found with that id"
}
```
Sandbox
Pick an endpoint and send a live requestThe Sandbox is for exploring the API only — responses return cached sample data, so the rates and availability shown are not live and may be out of date. Don't rely on them for real quotes.
Register in the Member Portal to get an API key for live rates →/api/partner/v1/instant-quote/quote