{"openapi":"3.1.0","info":{"title":"Hansatic Packing API","version":"1.0.0","description":"Container and truck load optimization: submit cargo, receive an optimized, physically valid layout with LDM, linear feet and pallet positions. Async: POST /pack returns 202 with a job; poll GET /jobs/{id} or receive a signed webhook. Sandbox mode on every plan (25 successful calls/month); production on Enterprise (5,000 successful packs/month). Only successful packs count against quotas.","contact":{"email":"hello@hansatic.com"}},"servers":[{"url":"https://hansatic.com/api/v1"}],"security":[{"apiKey":[]}],"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","description":"API key from Settings → API (hk_live_…)."}},"schemas":{"PackItem":{"type":"object","required":["length","width","height","weight"],"properties":{"name":{"type":"string"},"length":{"type":"number","description":"In request units (mm default, inches when units=imperial)."},"width":{"type":"number"},"height":{"type":"number"},"weight":{"type":"number","description":"kg default, lb when units=imperial."},"quantity":{"type":"integer","minimum":1,"default":1},"stackable":{"type":"boolean","default":true},"max_stack":{"type":"number","description":"Max weight this item can bear on top (same units as weight)."},"fragile":{"type":"boolean","default":false},"tiltable":{"type":"boolean","default":true},"destination_stop":{"type":"integer","default":1,"description":"Delivery stop for multi-stop LIFO loading."}}},"PackRequest":{"type":"object","required":["items"],"properties":{"vehicle":{"type":"string","enum":["CURTAINSIDER_136","BOX_136","MEGA_136","MEGA_CURTAIN_136","FLATBED_136","REEFER_136","CURTAINSIDER_75","SWAPBODY_715","SWAPBODY_745","DRY_VAN_53","FLATBED_53","REEFER_53","CURTAINSIDER_125_CN","FLATBED_40_IN","BOX_138_BR","CURTAINSIDER_12_ZA","20DC","20HC","40DC","40HC","45HC","20OT","40OT","20RF","40RF","40RFHC","53DC"]},"custom_vehicle_id":{"type":"string","format":"uuid","description":"A vehicle from your workspace fleet (instead of vehicle)."},"objective":{"type":"string","enum":["ldm","pallets"],"default":"ldm"},"units":{"type":"string","enum":["metric","imperial"],"default":"metric","description":"How item numbers are read AND returned."},"items":{"type":"array","items":{"$ref":"#/components/schemas/PackItem"},"minItems":1},"sandbox":{"type":"boolean","description":"Test mode: full result, nothing persisted. Available on every plan."},"name":{"type":"string","description":"Plan name (production mode)."},"ldm_limit_m":{"type":"number","description":"Restrict the load to the first N loading meters (trucks)."},"budget_ms":{"type":"integer","description":"Optimization budget (production; default 30000, max 300000)."},"callback_url":{"type":"string","format":"uri","description":"https URL POSTed the result when done, signed via X-Hansatic-Signature (HMAC-SHA256 of the raw body with your webhook secret)."}}},"JobAccepted":{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","running","done","failed"]},"sandbox":{"type":"boolean"},"status_url":{"type":"string","format":"uri"},"deduplicated":{"type":"boolean","description":"True when an Idempotency-Key matched an existing job."}}},"ResultItem":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"placed":{"type":"boolean"},"x":{"type":["number","null"],"description":"Position from the door end, in request units. null when unplaced."},"y":{"type":["number","null"],"description":"Position from the left wall. null when unplaced."},"z":{"type":["number","null"],"description":"Height off the floor. null when unplaced."},"rotation_deg":{"type":"integer","enum":[0,90]},"tilt_deg":{"type":"integer","enum":[0,90]},"length":{"type":"number"},"width":{"type":"number"},"height":{"type":"number"},"weight":{"type":"number"}}},"PackResult":{"type":"object","properties":{"sandbox":{"type":"boolean"},"vehicle":{"type":"string"},"objective":{"type":"string"},"units":{"type":"string","enum":["metric","imperial"]},"placed_count":{"type":"integer"},"unplaced_count":{"type":"integer"},"metrics":{"type":"object","properties":{"ldm_m":{"type":"number","description":"Loading meters — the cargo block extent."},"span_mm":{"type":"integer"},"linear_ft":{"type":"number"},"pallet_positions_eur":{"type":["integer","null"],"description":"Billed EUR pallet positions; null when the layout is too large to measure."},"total_weight_kg":{"type":"number"},"overlaps":{"type":"integer","description":"Always 0 for a valid layout."}}},"items":{"type":"array","items":{"$ref":"#/components/schemas/ResultItem"}},"plan_url":{"type":["string","null"],"description":"The created plan (production jobs only)."}}},"Job":{"type":"object","properties":{"job_id":{"type":"string","format":"uuid"},"status":{"type":"string","enum":["queued","running","done","failed"]},"sandbox":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"},"finished_at":{"type":["string","null"],"format":"date-time"},"result":{"$ref":"#/components/schemas/PackResult"},"error":{"$ref":"#/components/schemas/ApiError"}}},"ApiError":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string","enum":["invalid_key","invalid_json","invalid_vehicle","invalid_items","suspicious_dimensions","invalid_units","invalid_objective","invalid_callback","too_many_items","upgrade_required","sandbox_limit_reached","quota_exceeded","too_many_jobs","not_found","internal"]},"message":{"type":"string"},"field":{"type":"string"},"upgrade_url":{"type":"string"}}}}}}},"paths":{"/pack":{"post":{"summary":"Submit a packing job","description":"Validates and queues the job; returns 202 immediately. Supports an Idempotency-Key header — a retried request returns the original job. Failed validation never consumes quota.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PackRequest"}}}},"responses":{"202":{"description":"Job accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAccepted"}}}},"400":{"description":"Validation error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"Invalid API key","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"Upgrade required / sandbox limit reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Concurrent job limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/jobs/{id}":{"get":{"summary":"Poll a job","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Job status (result included when done)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Job"}}}},"404":{"description":"No such job for this workspace","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}}}}