openapi: 3.0.3 info: title: LoadCargo.in API version: "1.0.0" description: | Create container and truck loading plans from your ERP. Send a cargo list and the equipment you want to load into; get back a calculated loading plan — as JSON, as a PDF/XLSX document you can attach to a delivery note, and as a share link into the interactive 3D viewer that you can paste onto an order. **Authentication.** Every request needs an API key: Authorization: Bearer lci_live_... Create keys in the web app under *your account → API keys*. A key acts as your account and uses your existing subscription — there is no separate API plan. The key is shown once, at creation; we store only a hash of it. **Units.** Lengths are centimetres, weights kilograms, volumes cubic metres (unless the plan was created with `unit: imperial`). contact: name: LoadCargo.in url: https://loadcargo.in servers: - url: https://loadcargo.in/api/v1 security: - ApiKeyAuth: [] tags: - name: Plans description: Create, read, export and delete loading plans. - name: Equipment description: The container and truck catalogue you map your own master data onto. paths: /plans: post: tags: [Plans] summary: Create and calculate a loading plan description: | Creates a plan from your cargo list, runs the loading algorithm, and returns the result. The plan is stored as a real project in your account, which is why the response can hand you a `share_url` into the live 3D viewer. At least one container is always required — including in the automatic equipment modes. Auto mode does not invent equipment out of nothing: it *starts* from the container you assign and may then add or swap containers as the cargo demands. requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/PlanRequest' examples: twoStops: summary: Two-stop shipment into a 40' container value: name: SO-2026-00123 customer: ACME GmbH reference: ERP-ORDER-77 containers: - equipment_code: Maersk 40 standard quantity: 1 cargo: - shipment: STOP-1 box_id: PN-4471 quantity: 40 length: 120 width: 80 height: 95 weight: 340 stackable: true stack_weight: 800 turnable: 1 - shipment: STOP-2 box_id: PN-9002 quantity: 30 length: 100 width: 120 height: 110 weight: 500 stackable: false responses: '201': description: Plan created and calculated. content: application/json: schema: $ref: '#/components/schemas/Plan' '401': $ref: '#/components/responses/Unauthorized' '402': $ref: '#/components/responses/SubscriptionRequired' '422': $ref: '#/components/responses/ValidationError' /plans/{id}: parameters: - $ref: '#/components/parameters/PlanId' get: tags: [Plans] summary: Read a plan, including box placements description: | Returns the plan plus `boxes` — the placement geometry, so you can render or store the layout yourself. Coordinates are the box's origin corner in centimetres, with the container floor at `z = 0`. `length`/`width`/`height` are the dimensions **as placed** (already rotated); `turnid` records which rotation was applied. responses: '200': description: The plan. content: application/json: schema: allOf: - $ref: '#/components/schemas/Plan' - type: object properties: boxes: type: array items: $ref: '#/components/schemas/PlacedBox' '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '409': description: The plan exists but has no computed result. content: application/json: schema: $ref: '#/components/schemas/Error' delete: tags: [Plans] summary: Delete a plan description: Removes the plan and all its cargo. Useful for cleaning up test runs. responses: '204': description: Deleted. '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' /plans/{id}/export: parameters: - $ref: '#/components/parameters/PlanId' - name: format in: query schema: type: string enum: [pdf, xlsx, rtf] default: pdf - name: lang in: query description: Report language. schema: type: string enum: [en, cs] default: en - name: reduce in: query description: Print the reduced (grouped) cargo list instead of every box. schema: type: boolean default: false get: tags: [Plans] summary: Download the loading plan as a document description: | Streams the same loading-plan report the web app produces — attach it to a delivery note or packing list. Image formats (PNG, images-in-PDF) are not offered here: they embed 3D snapshots rendered by the browser, so over an API they would come out blank. Use `share_url` as the visual channel instead. responses: '200': description: The report. content: application/pdf: schema: type: string format: binary application/vnd.openxmlformats-officedocument.spreadsheetml.sheet: schema: type: string format: binary application/rtf: schema: type: string format: binary '401': $ref: '#/components/responses/Unauthorized' '404': $ref: '#/components/responses/NotFound' '409': description: The plan has no computed result to export. content: application/json: schema: $ref: '#/components/schemas/Error' '422': $ref: '#/components/responses/ValidationError' /equipment: get: tags: [Equipment] summary: List available containers and trucks description: | The catalogue you map your own material master onto. Use `code` as the `equipment_code` when creating a plan. parameters: - name: type in: query description: Filter by equipment type. schema: type: string enum: [ctr, truck, pallet, flatrack, flatbed, ddtrailer, uld] responses: '200': description: The catalogue. content: application/json: schema: type: object properties: equipment: type: array items: $ref: '#/components/schemas/Equipment' '401': $ref: '#/components/responses/Unauthorized' components: securitySchemes: ApiKeyAuth: type: http scheme: bearer description: 'Your API key, sent as: `Authorization: Bearer lci_live_...`' parameters: PlanId: name: id in: path required: true description: The `plan_id` returned when the plan was created. schema: type: integer responses: Unauthorized: description: Missing, unknown or revoked API key. content: application/json: schema: $ref: '#/components/schemas/Error' SubscriptionRequired: description: The account has no active subscription or trial. content: application/json: schema: $ref: '#/components/schemas/Error' NotFound: description: No such plan in this account. content: application/json: schema: $ref: '#/components/schemas/Error' ValidationError: description: The request was rejected. `details` names the offending fields. content: application/json: schema: $ref: '#/components/schemas/Error' schemas: Error: type: object properties: error: type: string example: Validation failed code: type: string enum: [INVALID_API_KEY, SUBSCRIPTION_REQUIRED, VALIDATION_ERROR, NOT_FOUND, NOT_CALCULATED] details: type: array description: Present on validation errors — one entry per rejected field. items: type: string example: - 'cargo[0].weight: required, must be a positive number' PlanRequest: type: object required: [containers, cargo] properties: name: type: string description: Shown in the dashboard. Use your ERP document number. example: SO-2026-00123 customer: type: string forwarder: type: string reference: type: string description: Your own key. Stored and echoed back on every read, so you can join our plan to your record without storing our `plan_id`. example: ERP-ORDER-77 unit: type: string enum: [metric, imperial] default: metric params: $ref: '#/components/schemas/PlanParams' containers: type: array minItems: 1 items: $ref: '#/components/schemas/ContainerRequest' cargo: type: array minItems: 1 items: $ref: '#/components/schemas/CargoLine' PlanParams: type: object properties: equip_mode: type: string enum: [equipManual, equipAutoCtr, equipAutoTruck] default: equipManual description: | `equipManual` loads into exactly the equipment you assign. The auto modes let the algorithm add or swap containers/trucks as needed — starting from the one you assign. load_direction: type: integer default: 2 description: Which packing variant to run (the "loading approach" in the app). box_overlap: type: integer default: 0 description: > Above 0, a box may straddle the edges of the boxes underneath it at the same height. Off by default. volume_cap: type: integer default: 100 description: Do not fill a container beyond this percentage of its volume. max_levels: type: integer default: 0 description: Maximum number of stacked layers. 0 = unlimited. ContainerRequest: type: object properties: equipment_code: type: string description: A `code` from `GET /equipment`. Either this or `equipment_id`. example: Maersk 40 standard equipment_id: type: integer description: Alternative to `equipment_code`. quantity: type: integer default: 1 seal_number: type: string CargoLine: type: object required: [length, width, height, weight] properties: shipment: type: string description: > Groups cargo into shipments (drops/stops). Boxes of the shipments listed first are loaded so that they come out first — this is what makes multi-stop routes unload in the right order. example: STOP-1 box_id: type: string description: Your part/box number. Echoed back, including in `unplaced`. example: PN-4471 mark: type: string quantity: type: integer default: 1 length: type: number width: type: number height: type: number weight: type: number description: Weight of ONE box. stackable: type: boolean default: false description: Whether other boxes may be stacked ON TOP of this one. stack_weight: type: number default: 0 description: Maximum weight this box can carry on top of it. turnable: type: integer enum: [0, 1, 2] default: 0 description: '0 = no rotation, 1 = around the vertical axis only, 2 = any rotation.' priority: type: integer default: 0 floor_only: type: boolean default: false description: The box must stand on the container floor. Plan: type: object properties: plan_id: type: integer reference: type: string nullable: true status: type: string enum: [calculated, partial] description: '`partial` means some boxes did not fit — see `unplaced`.' share_url: type: string description: > Public, read-only link to the interactive 3D loading plan. Paste it onto the order in your ERP; whoever opens it needs no account. example: https://loadcargo.in/share/a5723ccb... viewer_url: type: string description: The plan in the web app (requires sign-in). summary: type: object properties: containers_used: type: integer boxes_placed: type: integer boxes_unplaced: type: integer total_weight: type: number total_volume: type: number containers: type: array items: $ref: '#/components/schemas/ContainerResult' unplaced: type: array description: Cargo that did not fit, grouped by `box_id`. items: type: object properties: box_id: type: string quantity: type: integer reason: type: string example: no_fit ContainerResult: type: object properties: index: type: integer equipment_code: type: string equipment_type: type: string boxes: type: integer weight: type: number volume: type: number fill_volume_pct: type: number fill_weight_pct: type: number length_remains: type: number description: Free length left at the door end. PlacedBox: type: object properties: box_id: type: string mark: type: string container_index: type: string load_order: type: integer description: The sequence in which the box is loaded. x: type: number description: Origin corner, across the width. y: type: number description: Origin corner, along the length. z: type: number description: Origin corner, height above the floor (floor = 0). z_top: type: number description: Height of the box's top face — its stacking height. length: type: number description: Dimension AS PLACED (already rotated). width: type: number height: type: number weight: type: number turnid: type: string description: Which rotation was applied. Equipment: type: object properties: code: type: string description: Pass this as `equipment_code` when creating a plan. example: Maersk 40 standard equipment_id: type: integer type: type: string example: ctr length: type: number width: type: number height: type: number payload: type: number tare_weight: type: number door_width: type: number nullable: true door_height: type: number nullable: true unit: type: string