{"openapi":"3.1.0","info":{"title":"ShipScan API","version":"1.0.0","description":"Canonical customer API: consignment ingestion + label generation. Auth: `x-api-key: <api-key>` header (`Authorization: Bearer <api-key>` also accepted). Errors: `{ error: { code, message, details? }, request_id }`. Breaking changes never land on /api/v1 — additive fields only."},"servers":[{"url":"{baseUrl}","variables":{"baseUrl":{"default":"https://app.shipscan.io"}}}],"components":{"securitySchemes":{"apiKey":{"type":"apiKey","in":"header","name":"x-api-key","description":"ShipScan API key (Settings → Integrations → API keys). The key alone identifies the organisation. `Authorization: Bearer <key>` is also accepted for older integrations."}},"schemas":{},"parameters":{}},"paths":{"/api/v1/consignments":{"post":{"summary":"Ingest a consignment (find-or-create by reference)","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"reference_number":{"type":"string","minLength":1},"carrier":{"type":"string","minLength":1},"ship_from":{"type":"string","minLength":1},"ship_to":{"type":"object","properties":{"line1":{"type":"string","minLength":1},"line2":{"type":"string"},"city":{"type":"string","minLength":1},"county":{"type":"string"},"postcode":{"type":"string","minLength":1},"country_code":{"type":"string","minLength":2,"maxLength":2,"default":"GB"}},"required":["line1","city","postcode"]},"parcels":{"type":"array","items":{"type":"object","properties":{"tracking_number":{"type":"string","minLength":1},"parcel_id":{"type":"string"},"weight_kg":{"type":"number","exclusiveMinimum":0},"length_cm":{"type":"number","exclusiveMinimum":0},"width_cm":{"type":"number","exclusiveMinimum":0},"height_cm":{"type":"number","exclusiveMinimum":0},"items":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","minLength":1},"quantity":{"type":"integer","exclusiveMinimum":0,"default":1},"sku":{"type":"string"},"value_minor_units":{"type":"integer","minimum":0},"value_currency":{"type":"string","minLength":3,"maxLength":3},"weight_kg":{"type":"number","exclusiveMinimum":0},"hs_code":{"type":"string"},"origin_country":{"type":"string","minLength":2,"maxLength":2}},"required":["description"]}}},"required":["tracking_number"]},"minItems":1},"notes":{"type":"string"},"value_minor_units":{"type":"integer","minimum":0},"value_currency":{"type":"string","minLength":3,"maxLength":3}},"required":["reference_number","carrier","ship_from","ship_to","parcels"]}}}},"responses":{"200":{"description":"Consignment recorded (existing reference is reused)","content":{"application/json":{"schema":{"type":"object","properties":{"consignment_id":{"type":"string","format":"uuid"},"parcel_count":{"type":"integer"},"status":{"type":"string","enum":["pending_loading"]}},"required":["consignment_id","parcel_count","status"]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]},"request_id":{"type":"string"}},"required":["error","request_id"]}}}},"422":{"description":"Validation failed, or an operator-fixable gap (CUSTOMS_REQUIRED, ADDRESS_INVALID, …)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]},"request_id":{"type":"string"}},"required":["error","request_id"]}}}}}},"get":{"summary":"List consignments (newest first)","description":"Filters: `reference` (exact, case-insensitive), `status`, `limit` (default 25, max 100).","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1},"required":false,"name":"reference","in":"query"},{"schema":{"type":"string","enum":["pending_loading","partially_loaded","fully_loaded","dispatched","cancelled"]},"required":false,"name":"status","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":100,"default":25},"required":false,"name":"limit","in":"query"}],"responses":{"200":{"description":"Consignments with carrier binding, status and parcels","content":{"application/json":{"schema":{"type":"object","properties":{"consignments":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"reference_number":{"type":"string"},"order_reference":{"type":["string","null"]},"carrier":{"type":"string"},"carrier_account_id":{"type":["string","null"],"format":"uuid"},"warehouse":{"type":["string","null"]},"status":{"type":"string"},"tracking_status":{"type":["string","null"]},"label_failed_at":{"type":["string","null"]},"parcel_count":{"type":"integer"},"parcels":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"tracking_number":{"type":["string","null"]},"status":{"type":"string"}},"required":["id","tracking_number","status"]}},"created_at":{"type":"string"}},"required":["id","reference_number","order_reference","carrier","carrier_account_id","warehouse","status","tracking_status","label_failed_at","parcel_count","parcels","created_at"]}}},"required":["consignments"]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]},"request_id":{"type":"string"}},"required":["error","request_id"]}}}},"422":{"description":"Validation failed, or an operator-fixable gap (CUSTOMS_REQUIRED, ADDRESS_INVALID, …)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]},"request_id":{"type":"string"}},"required":["error","request_id"]}}}}}}},"/api/v1/labels":{"post":{"summary":"Generate a label — one-request or two-step mode","description":"One-request mode: inline `shipment` + `carrier` blocks create the consignment AND generate one label per parcel in a single call (`carrier.name` matches an account display name; unmatched → shipment recorded, `labels: []`, `reason: NO_ADAPTER`). Two-step mode: `parcel_id` or `consignment_reference` + `parcel_tracking_number`. Send an `Idempotency-Key` header — retries replay, never double-book. Allow ~60s (synchronous carrier round-trip).","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"shipment":{"type":"object","properties":{"reference":{"type":"string","minLength":1},"ship_from":{"type":"string","minLength":1},"ship_to":{"type":"object","properties":{"line1":{"type":"string","minLength":1},"line2":{"type":"string"},"city":{"type":"string","minLength":1},"county":{"type":"string"},"postcode":{"type":"string","minLength":1},"country_code":{"type":"string","minLength":2,"maxLength":2,"default":"GB"}},"required":["line1","city","postcode"]},"parcels":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string","minLength":1},"tracking_number":{"type":"string","minLength":1},"weight_kg":{"type":"number","exclusiveMinimum":0},"length_cm":{"type":"number","exclusiveMinimum":0},"width_cm":{"type":"number","exclusiveMinimum":0},"height_cm":{"type":"number","exclusiveMinimum":0},"items":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","minLength":1},"quantity":{"type":"integer","exclusiveMinimum":0,"default":1},"sku":{"type":"string"},"value_minor_units":{"type":"integer","minimum":0},"value_currency":{"type":"string","minLength":3,"maxLength":3},"weight_kg":{"type":"number","exclusiveMinimum":0},"hs_code":{"type":"string"},"origin_country":{"type":"string","minLength":2,"maxLength":2}},"required":["description"]}}}},"minItems":1},"notes":{"type":"string"},"value_minor_units":{"type":"integer","minimum":0},"value_currency":{"type":"string","minLength":3,"maxLength":3}},"required":["reference","ship_from","ship_to","parcels"]},"carrier":{"type":"object","properties":{"name":{"type":"string","minLength":1},"account_id":{"type":"string","format":"uuid"},"service":{"type":"string","minLength":1},"options":{"type":"object","additionalProperties":{}}}},"parcel_id":{"type":"string","format":"uuid"},"consignment_reference":{"type":"string","minLength":1},"parcel_tracking_number":{"type":"string","minLength":1},"carrier_account_id":{"type":"string","format":"uuid"},"service_code":{"type":"string","minLength":1},"format":{"type":"string","enum":["pdf","zpl","png"],"default":"pdf"}}}}}},"responses":{"200":{"description":"One-request mode: `{ shipment, labels[], reason? }`. Two-step mode: a single label object.","content":{"application/json":{"schema":{"anyOf":[{"type":"object","properties":{"shipment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"reference":{"type":"string"},"status":{"type":"string"}},"required":["id","reference","status"]},"document":{"type":["object","null"],"properties":{"url":{"type":"string","description":"Time-limited signed URL to ONE printable document containing every parcel label as a page"},"format":{"type":"string","enum":["pdf","zpl","png"]}},"required":["url","format"]},"parcels":{"type":"array","items":{"type":"object","properties":{"reference":{"type":"string","description":"The caller's parcel reference/barcode, exactly as submitted"},"carrier_tracking_number":{"type":["string","null"],"description":"Carrier-assigned tracking number"},"label_id":{"type":["string","null"],"format":"uuid","description":"Per-parcel label id, for POST /api/v1/labels/{id}/void"}},"required":["reference","carrier_tracking_number","label_id"]}},"reason":{"type":"string","enum":["NO_ADAPTER"]},"reason_message":{"type":"string","description":"Human-readable diagnosis for NO_ADAPTER"}},"required":["shipment","document","parcels"]},{"type":"object","properties":{"label_id":{"type":"string","format":"uuid"},"label_url":{"type":"string"},"tracking_number":{"type":"string"},"carrier_service_code":{"type":"string"},"format":{"type":"string","enum":["pdf","zpl","png"]},"rate_amount":{"type":["number","null"]},"rate_currency":{"type":"string"},"status":{"type":"string","enum":["created","failed"]},"error_message":{"type":["string","null"]}},"required":["label_id","label_url","tracking_number","carrier_service_code","format","rate_amount","rate_currency","status","error_message"]}]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]},"request_id":{"type":"string"}},"required":["error","request_id"]}}}},"409":{"description":"Idempotency conflict: BODY_HASH_MISMATCH (same key, different body) or IN_FLIGHT","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]},"request_id":{"type":"string"}},"required":["error","request_id"]}}}},"422":{"description":"Validation failed, or an operator-fixable gap (CUSTOMS_REQUIRED, ADDRESS_INVALID, …)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]},"request_id":{"type":"string"}},"required":["error","request_id"]}}}}}}},"/api/v1/consignments/{id}/cancel":{"post":{"summary":"Cancel a consignment (voids labels at their carriers)","description":"Voids every created label AT ITS CARRIER (best-effort per label; failures reported), then flips the consignment to `cancelled`. Dispatched consignments refuse with 409 — the goods are with the carrier; raise an exception instead. Idempotent on already-cancelled.","security":[{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"Cancelled","content":{"application/json":{"schema":{"type":"object","properties":{"consignment_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["cancelled"]},"labels_voided":{"type":"integer"},"label_void_failures":{"type":"array","items":{"type":"object","properties":{"label_id":{"type":"string"},"error":{"type":"string"}},"required":["label_id","error"]}}},"required":["consignment_id","status","labels_voided","label_void_failures"]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]},"request_id":{"type":"string"}},"required":["error","request_id"]}}}},"409":{"description":"Already dispatched","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]},"request_id":{"type":"string"}},"required":["error","request_id"]}}}},"422":{"description":"Validation failed, or an operator-fixable gap (CUSTOMS_REQUIRED, ADDRESS_INVALID, …)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]},"request_id":{"type":"string"}},"required":["error","request_id"]}}}}}}},"/api/v1/labels/bulk":{"post":{"summary":"Generate labels for every parcel on a consignment","security":[{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"consignment_id":{"type":"string","format":"uuid"},"carrier_account_id":{"type":"string","format":"uuid"},"service_code":{"type":"string","minLength":1},"format":{"type":"string","enum":["pdf","zpl","png"],"default":"pdf"}},"required":["consignment_id"]}}}},"responses":{"200":{"description":"One label per parcel","content":{"application/json":{"schema":{"type":"object","properties":{"labels":{"type":"array","items":{"type":"object","properties":{"label_id":{"type":"string","format":"uuid"},"label_url":{"type":"string"},"tracking_number":{"type":"string"},"carrier_service_code":{"type":"string"},"format":{"type":"string","enum":["pdf","zpl","png"]},"rate_amount":{"type":["number","null"]},"rate_currency":{"type":"string"},"status":{"type":"string","enum":["created","failed"]},"error_message":{"type":["string","null"]}},"required":["label_id","label_url","tracking_number","carrier_service_code","format","rate_amount","rate_currency","status","error_message"]}}},"required":["labels"]}}}},"401":{"description":"Missing or invalid API key","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]},"request_id":{"type":"string"}},"required":["error","request_id"]}}}},"422":{"description":"Validation failed, or an operator-fixable gap (CUSTOMS_REQUIRED, ADDRESS_INVALID, …)","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}},"required":["code","message"]},"request_id":{"type":"string"}},"required":["error","request_id"]}}}}}}}},"webhooks":{}}