API reference
Shipment
The https://v2.cartage.ai/p/v2/shipment endpoint exposes a GET method to read one shipment by reference number and a POST method to create one, and authenticates every call with the X-API-Key header.
The shipment is returned along with documents it links to, including: addressBooks (the facilities behind each stop) and quotes (the carrier rates gathered for it).
Authentication
Every request carries your organization's API key in the X-API-Key header and names your organization with orgId (a query parameter on GET, a body field on POST).
GET /p/v2/shipment
Retrieve a shipment by reference number.
Request
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
orgId |
string | Yes | Your organization ID |
referenceNumber |
string | Yes | Shipment reference (e.g., SHP-001) |
Example
curl -X GET "https://v2.cartage.ai/p/v2/shipment?orgId=<yourOrgId>&referenceNumber=SHP-123" \
-H "X-API-Key: <yourApiKey>"
Response
The shipment document. Some field are optional; only populated fields are returned. addressBooks and quotes are returned as empty arrays when nothing is linked.
{
"id": "shp_abc123",
"orgId": "org_xyz",
"createdAt": "2026-09-10T14:30:00.000Z",
"updatedAt": "2026-09-12T09:15:00.000Z",
"referenceNumber": "SHP-123",
"status": "scheduled",
"serviceType": "ltl",
"equipment": "dryVan",
"stops": [
{
"stopId": "stp_01",
"sequenceNumber": 0,
"type": "pickup",
"addressBookId": "adb_chicago",
"name": "Warehouse A",
"address1": "123 Main St",
"city": "Chicago",
"state": "IL",
"zipCode": "60601",
"country": "US",
"agreedArrivalStartAt": "2026-09-15T13:00:00.000Z"
},
{
"stopId": "stp_02",
"sequenceNumber": 1,
"type": "dropoff",
"addressBookId": "adb_detroit",
"name": "Distribution Center B",
"address1": "456 Oak Ave",
"city": "Detroit",
"state": "MI",
"zipCode": "48201",
"country": "US",
"agreedArrivalStartAt": "2026-09-17T14:00:00.000Z"
}
],
"commodities": [
{
"id": "cmd_01",
"name": "Electronics",
"quantity": 4,
"totalWeight": { "value": 5000, "unit": "lbs" },
"freightClass": "85"
}
],
"carrier": { "name": "FedEx Freight", "scac": "FXFE" },
"proNumber": "123456789",
"bolNumber": "BOL-88213",
"distance": 282,
"trackingStatus": "in_transit",
"cost": { "amount": 1175, "currency": "usd" },
"quoteIds": ["qte_fedex_01"],
"addressBookIds": ["adb_chicago", "adb_detroit"],
"activityLog": [
{ "action": "Created via API", "timestamp": "2026-09-10T14:30:00.000Z", "source": "api" }
],
"isMultiStop": false,
"origin": { "stopId": "stp_01", "sequenceNumber": 0, "name": "Warehouse A", "city": "Chicago", "state": "IL" },
"destination": { "stopId": "stp_02", "sequenceNumber": 1, "name": "Distribution Center B", "city": "Detroit", "state": "MI" },
"pickupDate": "2026-09-15T13:00:00.000Z",
"deliveryDate": "2026-09-17T14:00:00.000Z",
"totalWeight": { "value": 5000, "unit": "lbs" },
"totalDistance": 282,
"carrierName": "FedEx Freight",
"addressBooks": [
{
"id": "adb_chicago",
"name": "Warehouse A",
"address1": "123 Main St",
"city": "Chicago",
"state": "IL",
"zipCode": "60601",
"country": "US",
"locationType": "warehouse",
"isAppointmentRequired": false
},
{
"id": "adb_detroit",
"name": "Distribution Center B",
"address1": "456 Oak Ave",
"city": "Detroit",
"state": "MI",
"zipCode": "48201",
"country": "US",
"locationType": "distribution",
"isAppointmentRequired": true
}
],
"quotes": [
{
"id": "qte_fedex_01",
"referenceNumber": "Q-0451",
"status": "accepted",
"shipmentIds": ["shp_abc123"],
"carrierName": "FedEx Freight",
"carrierScac": "FXFE",
"serviceType": "ltl",
"transitDays": 2,
"baseCharge": { "amount": 1000, "currency": "usd" },
"fuelSurcharge": { "amount": 175, "currency": "usd" },
"totalRate": { "amount": 1175, "currency": "usd" }
}
]
}
POST /p/v2/shipment
Create one shipment. The shipment is validated, written through the same path Wilson uses (reference number minted, stops matched to your address book, distance computed, activity log stamped, search updated), and returned in the same shape GET uses.
Request
Body
| Field | Type | Required | Description |
|---|---|---|---|
orgId |
string | Yes | Your organization ID |
shipment |
object | Yes | The shipment to create — any writable field from the Shipment Schema below. status and serviceType are always required. |
activityLog |
object | No | One entry for the shipment's activity feed: { action, details? }. Defaults to { "action": "Created via API" }. |
What each status requires
status decides how much the shipment must carry. Start at pending when you only know the basics, and move up as facts arrive. A shipment sent at a status it isn't ready for is refused with a 422 naming every gap. The full list is under Required Fields below.
Unknown fields are refused. A top-level field on shipment that is not in the Shipment Schema is a 400 naming it. Nothing is silently dropped or stored.
Fields the API sets for you (a 400 when sent at the top level; the nested ones, such as stops[].stopId, are ignored): id, createdAt, updatedAt, markdownContent, activityLog (use the top-level activityLog entry instead), stops[].stopId, bookingIds, bookedAt, importedCarrier, schedulingVerdict, lifecycleStatus, operationalState, invoiceStatus, invoices[].paidAt, invoices[].paidAmount, the telemetry fields, and every computed field. Inline quotes are not accepted on a create — create the shipment first, then create each quote with POST /p/v2/quote naming the shipment in shipmentIds.
Linking facilities and products. Link a stop to a facility in your address book by sending that entry's id as stops[].addressBookId. Link a commodity line to a product by sending the product's id as commodities[].productId. Both take the document id, not its reference number — read it from GET /p/v2/addressBook / GET /p/v2/product, or from the response of the POST that created the record.
stops[].addressBookIdis whataddressBooks[]in the response is built from, and it is required on every stop fromprocuringon. When the stop leavesisAppointmentRequiredorspecialInstructionsblank, they are filled from the linked entry.commodities[].productIdis never required. It is stored on the commodity line and returned there; there is noproducts[]view.- Both are stored as sent. An id that matches no document in your organization is not refused and raises no advisory (
UNKNOWN_LINKED_IDS_DROPPEDcovers only the top-level id arrays), so send ids you have read back from the API.
Example
curl -X POST "https://v2.cartage.ai/p/v2/shipment" \
-H "X-API-Key: <yourApiKey>" \
-H "Content-Type: application/json" \
-d '{
"orgId": "<yourOrgId>",
"shipment": {
"referenceNumber": "PO-88213",
"status": "pending",
"serviceType": "ltl",
"customerEmail": "ops@customer.com",
"stops": [
{
"sequenceNumber": 0,
"type": "pickup",
"addressBookId": "adb_chicago",
"name": "Warehouse A",
"address1": "123 Main St",
"city": "Chicago",
"state": "IL",
"zipCode": "60601",
"country": "US",
"contactEmail": "dock@warehousea.com",
"phoneNumber": "555-123-4567",
"agreedArrivalStartAt": "2026-09-15T13:00:00.000Z"
},
{
"sequenceNumber": 1,
"type": "dropoff",
"addressBookId": "adb_detroit",
"name": "Distribution Center B",
"address1": "456 Oak Ave",
"city": "Detroit",
"state": "MI",
"zipCode": "48201",
"country": "US",
"isAppointmentRequired": true
}
],
"commodities": [
{
"id": "line-1",
"productId": "prd_electronics",
"name": "Electronics",
"quantity": 4,
"totalWeight": { "value": 5000, "unit": "lbs" },
"freightClass": "85"
}
],
"accessorials": ["liftgate"],
"purchaseOrderNumber": ["AT0000284061"]
},
"activityLog": { "action": "Created from order #88213" }
}'
Leave referenceNumber out to have one minted (SHP-…). Send your own to keep your order number as the shipment's identity; it must be unique within your organization.
Response
{
"status": "CREATED",
"shipment": { "...": "the shipment, in the same shape GET returns" },
"advisories": [
{
"field": "quoteIds",
"code": "SHIPMENT_QUOTES_LINKED",
"message": "No quote is linked to the shipment."
}
]
}
advisories appears only when the write succeeded but left something worth knowing. Each entry is { field, code, message } — the same shape as issues on a 400 or 422. Nothing in advisories blocked the create. The codes you can see here:
| Code | Meaning |
|---|---|
SHIPMENT_QUOTES_LINKED |
The shipment is at scheduled or later with no quote linked. Fine for a contract rate or a customer-arranged carrier; otherwise link the quote later. |
UNKNOWN_LINKED_IDS_DROPPED |
An id you sent in a link field (quoteIds, salesOrderIds, addressBookIds, …) matched no document in your organization and was dropped. The rest of the create applied. |
LINKED_DOCUMENTS_NOT_HYDRATED |
The shipment was created, but addressBooks and quotes could not be read afterwards and come back empty. Do not retry the create — the shipment exists, and a retry without a referenceNumber makes a duplicate. Read it back with GET. |
Refusals
Nothing is written on any of these.
| Status | When | Body |
|---|---|---|
| 400 | The body does not match the schema — a missing serviceType, an unknown status, stops sent as a string, inline quotes, a field that is not in the schema. |
{ "error": "Invalid input parameters", "issues": [ { "field": "shipment.serviceType", "message": "…", "code": "invalid_value" } ], "details": { … } } |
| 409 | A shipment with that referenceNumber already exists in your organization. |
{ "error": "A shipment with referenceNumber PO-88213 already exists …", "code": "SHIPMENT_REFERENCE_NUMBER_EXISTS", "referenceNumber": "PO-88213", "existingShipmentId": "shp_abc123" } — read it with GET, or send a different reference. |
| 422 | The shipment is missing what its status requires (see Required Fields). |
{ "error": "Cannot create shipment: 2 required fields missing for status scheduled. Nothing was created.", "code": "REQUIRED_FIELDS_MISSING", "issues": [ { "field": "carrier.name", "code": "SHIPMENT_BOOKED_CARRIER", "message": "No carrier is booked on the shipment." }, { "field": "stops[0].agreedArrivalStartAt", "code": "SHIPMENT_AGREED_PICKUP", "message": "The origin stop has no agreed pickup window." } ] } — fill exactly the listed fields and resend, or send a lower status. Codes are listed below. |
422 issue codes
Every issues[] entry carries a stable code. A rule that accepts alternatives lists them all under fields next to field.
| Code | From status | What to send |
|---|---|---|
SHIPMENT_ORIGIN_ADDRESS_BOOK |
procuring |
stops[0].addressBookId — the id of the origin facility's address book entry. |
SHIPMENT_DESTINATION_ADDRESS_BOOK |
procuring |
The final stop's addressBookId. |
SHIPMENT_INTERMEDIATE_STOPS_ADDRESS_BOOK |
procuring |
An addressBookId on every stop between origin and destination. |
SHIPMENT_EQUIPMENT |
procuring |
equipment (trailer type). Not asked for ltl or willCall. |
SHIPMENT_DISTANCE |
procuring |
A positive distance in miles, or geocodable stop addresses (city and state at minimum) so it can be computed. |
SHIPMENT_SERVICE_TYPE |
scheduled |
serviceType. (Always required on this endpoint, so this code should not appear.) |
SHIPMENT_BOOKED_CARRIER |
scheduled |
carrier.name (and carrier.scac when known), or an accepted quote linked through quoteIds. |
SHIPMENT_AGREED_PICKUP |
scheduled |
stops[0].agreedArrivalStartAt — the agreed pickup time. |
SHIPMENT_AGREED_DELIVERY |
inTransit |
agreedArrivalStartAt on the final stop. |
SHIPMENT_ACTUAL_DELIVERY |
delivered |
arrivalConfirmation.confirmedAt on the final stop — when it was actually delivered. |
SHIPMENT_WILL_CALL_SAME_FACILITY |
procuring |
willCall only: every stop's addressBookId must be the same facility. |
SHIPMENT_WILL_CALL_NO_DISTANCE |
procuring |
willCall only: omit distance or send 0. |
SHIPMENT_FIELD_NOT_IN_SERVICE_TYPE |
any | You sent a field the service type doesn't have (a vessel on a truckload, a chassis on an ocean move). Remove it, or change serviceType. |
SHIPMENT_EVENT_TYPE_NOT_IN_SERVICE_TYPE |
any | An events[] entry's type isn't one the service type can log. Remove it, or change serviceType. |
Required Fields
Everything else on a shipment is optional. Send what you know; the write is refused only when one of these is missing.
Always required on POST
| Field | Type | Description |
|---|---|---|
status |
enum | pending | procuring | scheduled | inTransit | delivered | cancelled | onHold | quoteLost | invoiceReceived | invoiceFlagged | invoicePaid. Decides what else is required (see below). |
serviceType |
enum | ftl | ltl | drayage | rail | ocean | air | lastMile | parcel | willCall |
Required inside nested objects, when you send them
| Object | Required |
|---|---|
stops[] |
sequenceNumber (0 = origin, last = final destination) |
commodities[] |
id, name |
Money (cost, revenue, value, fees) |
amount (currency defaults to usd) |
Weight (totalWeight, unitWeight) |
value, unit |
| Dimensions | A unit for every value sent (lengthUnit, widthUnit, heightUnit) |
Confirmation (arrivalConfirmation, departureConfirmation) |
confirmedAt |
containers[] |
containerNumber |
events[] |
type |
erpData |
platform, recordId |
activityLog (top-level body field) |
action |
Required by status
| Status | Required on the shipment |
|---|---|
pending |
Nothing beyond status and serviceType. |
procuring |
stops[].addressBookId on every stop (send it yourself — a stop matched to your address book by name and address alone is listed under addressBookIds but does not satisfy this requirement); equipment unless serviceType is ltl or willCall; a positive distance (computed from the stop addresses when omitted, so give each stop at least city and state). |
scheduled |
Everything above, plus carrier.name (or an accepted quote linked through quoteIds) and stops[0].agreedArrivalStartAt. |
inTransit |
Everything above, plus agreedArrivalStartAt on the final stop. |
delivered, invoiceReceived, invoiceFlagged, invoicePaid |
Everything above, plus arrivalConfirmation.confirmedAt on the final stop. |
cancelled, onHold, quoteLost |
Nothing beyond status and serviceType. |
A willCall shipment is the customer collecting at the shipper's facility: all stops share one address book entry, distance must be 0 or omitted, and no equipment is asked for.
Linked Documents
Both arrays are read-only views built from the shipment's ids. They are never stored on the shipment and are not accepted on POST.
Address Books
addressBooks[] — the facility record behind each stop's addressBookId, in route order (origin first), followed by any linked entry no stop references. Each entry is a full Address (see Shared Objects) with the document base fields, notes, and the facility details below.
| Field | Type | Description |
|---|---|---|
name |
string | Facility / location name |
locationType |
enum | warehouse | distribution | retail | residential | commercial | customer | port | railyard | crossDock | manufacturing | airport | other |
isAppointmentRequired |
boolean | The facility's standing appointment rule |
specialInstructions |
string | Standing handling instructions (check-in process, dock notes, after-hours rules) |
dockType |
enum | rearLoad | sideLoad | groundLevel | elevated | other |
numberOfDocks |
number | Dock count |
hasLiftGate / hasForklift |
boolean | Equipment on site |
maxTrailerLength |
number | Longest trailer the site takes |
Quotes
quotes[] — the quote documents behind quoteIds, in link order. A quote is a carrier's rate for this shipment; the accepted one is the booking.
| Field | Type | Description |
|---|---|---|
referenceNumber |
string | Quote identifier (Q-…) |
status |
enum | pending | accepted | rejected | expired |
shipmentIds |
string[] | The shipments this quote is for |
carrierName / carrierScac |
string | The carrier offering the rate |
vendorName / vendorId |
string | The vendor record the quote came through, when there is one |
platform |
string | Freight platform that produced the quote |
serviceType / serviceName |
string | Service type and the carrier's service name (Priority, Standard) |
transitDays |
number | Carrier-quoted transit time in days |
baseCharge |
Money | Line-haul charge |
fuelSurcharge |
Money | Fuel surcharge |
accessorialCharges |
object[] | { name, amount, currency } per accessorial |
totalRate |
Money | Computed total — base, fuel and accessorials |
distance / distanceUnit |
number / enum | Quoted distance, mi or km |
stops / commodities |
object[] | The route and freight the rate was quoted against |
transshipmentPorts |
string[] | Ocean / air: intermediate ports when the routing isn't direct |
index / quotedAt |
number / string | Position in, and timestamp of, the rate batch that produced it |
notes |
string | Validity, conditions, vendor remarks |
Shared Objects
Address (stops, billTo, address book entries): name, address1, address2, city, state, zipCode, country, placeId, coordinates ({ latitude, longitude }), timezone (IANA, e.g. America/Chicago), businessHoursStartAt / businessHoursEndAt (HH:mm), firstName, lastName, contactName, contactEmail, phoneNumber, phoneNumberExt, unLocode (ports, airports, rail terminals), rampCode (rail ramps)
Contact (shipper, consignee): firstName, lastName, fullName, email, phone, phoneExt, title, company
Money (value, cost, revenue, quote charges, container fees): { amount: number, currency: "usd" | "cad" } — currency defaults to usd
Weight (totalWeight, commodity weights): { value: number, unit: "lbs" | "kg" | "oz" | "tons" }
Dimensions (commodity lines): { length, width, height, lengthUnit, widthUnit, heightUnit } — units are in | cm | ft | m | mm, and each value needs its unit
Confirmation (arrivalConfirmation, departureConfirmation, pickupConfirmation, deliveryConfirmation): confirmedAt (required — the date it actually happened), confirmedBy, method (email | phone | app | edi | webhook | api | other), notes
Error Responses
| Status | Description |
|---|---|
| 400 | Invalid request — missing orgId or referenceNumber on GET; a body that doesn't match the schema on POST (the response lists each bad field under issues) |
| 401 | Missing or invalid API key |
| 404 | Organization or shipment not found |
| 409 | POST only — a shipment with that referenceNumber already exists (code: "SHIPMENT_REFERENCE_NUMBER_EXISTS") |
| 422 | POST only — the shipment is missing what its status requires (code: "REQUIRED_FIELDS_MISSING", with issues[]) |
| 500 | Server error |
Error Format
{
"error": "Error message",
"code": "STABLE_CODE",
"issues": [
{ "field": "path.to.field", "code": "STABLE_CODE", "message": "What is wrong with it." }
]
}
error is always present. code and issues appear on 400, 409 and 422 as shown under each endpoint above. Branch on code, not on error text.
Notes
- All fields are optional unless marked required — only populated fields appear in the response.
markdownContentis omitted; everything it renders is in the structured fields.- Computed fields (
origin,shipper,pickupDate,totalDistance,carrierName, …) are read-only projections and are recomputed on every write. addressBooksandquotesare hydrated fromaddressBookIdsandquoteIdson every read. An id that points at a deleted document is skipped, not returned asnull.- A
POSTthat fails with400,409or422writes nothing. Fix the payload and resend the same request. - Timestamps are ISO 8601. Money amounts are in major units (
1175is $1,175.00).