{
  "info": {
    "name": "CartonPilot API",
    "description": "CartonPilot is a 3D bin-packing (cartonization) REST API for e-commerce shipments: send your boxes and items (or saved box-set / item-catalog keys) to POST /api/v1/shipping-optimize and get back optimized shipments with per-box item placements, utilization, billing figures (dimensional weight, rate-card invoice estimates), and packing suggestions. It supports single orders, synchronous batches (up to 50 orders), async batches (up to 500 orders, polled via /api/v1/batch-jobs or delivered to a webhook), objective-based optimization (fewest-parcels, lowest-cost, lowest-billable-weight, lowest-invoice-cost, fastest), and safe retries via the Idempotency-Key header. Authenticate every request with your API key: Authorization: Bearer sk_... Full documentation: https://cartonpilot.com/docs",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://cartonpilot.com",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "sk_your_api_key",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Optimize",
      "description": "3D bin-packing optimization requests against POST /api/v1/shipping-optimize.",
      "item": [
        {
          "name": "Single order (objective: fewest-parcels)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "description": "Pack a single order into the fewest parcels. Boxes are defined inline (each requires id + dimensions; weightCapacity is the max contents weight, weight is the box tare weight, cost is per-box cost). Dimensions and weights are unit-agnostic as long as they are consistent within the request.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"boxes\": [\n    { \"id\": \"small\", \"name\": \"Small Box\", \"dimensions\": { \"length\": 10, \"width\": 8, \"height\": 6 }, \"weightCapacity\": 10, \"weight\": 0.4, \"cost\": 2.5 },\n    { \"id\": \"medium\", \"name\": \"Medium Box\", \"dimensions\": { \"length\": 15, \"width\": 12, \"height\": 10 }, \"weightCapacity\": 25, \"weight\": 0.7, \"cost\": 4.0 }\n  ],\n  \"items\": [\n    { \"id\": \"item1\", \"name\": \"Widget\", \"dimensions\": { \"length\": 5, \"width\": 4, \"height\": 3 }, \"weight\": 1.5, \"quantity\": 3 },\n    { \"id\": \"item2\", \"name\": \"Gadget\", \"dimensions\": { \"length\": 6, \"width\": 5, \"height\": 4 }, \"weight\": 2.8 }\n  ],\n  \"options\": { \"objective\": \"fewest-parcels\", \"allowRotation\": true }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v1/shipping-optimize",
              "host": ["{{baseUrl}}"],
              "path": ["api", "v1", "shipping-optimize"]
            }
          }
        },
        {
          "name": "Single order - lowest billable weight (dimDivisor)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "description": "Minimize carrier billable weight (max of actual and dimensional weight). options.dimDivisor is required for this objective - use your carrier's DIM divisor (e.g. 139 for in/lb, 5000 for cm/kg). Each shipment in the response includes a `billing` block and the summary includes totalBillableWeight.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"boxes\": [\n    { \"id\": \"small\", \"dimensions\": { \"length\": 10, \"width\": 8, \"height\": 6 }, \"weight\": 0.4, \"cost\": 2.5 },\n    { \"id\": \"medium\", \"dimensions\": { \"length\": 15, \"width\": 12, \"height\": 10 }, \"weight\": 0.7, \"cost\": 4.0 }\n  ],\n  \"items\": [\n    { \"id\": \"item1\", \"dimensions\": { \"length\": 5, \"width\": 4, \"height\": 3 }, \"weight\": 1.5, \"quantity\": 3 }\n  ],\n  \"options\": { \"objective\": \"lowest-billable-weight\", \"dimDivisor\": 139 }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v1/shipping-optimize",
              "host": ["{{baseUrl}}"],
              "path": ["api", "v1", "shipping-optimize"]
            }
          }
        },
        {
          "name": "Single order - lowest invoice cost (rate card)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "description": "Minimize estimated carrier invoice cost against a saved rate card (upload negotiated rates via the /api/rate-cards dashboard endpoints). options.rateCardKey is required for this objective, plus a zone (options.zone here, or per-order zones on batch requests). Each shipment's billing block includes an invoice estimate and the summary includes totalInvoiceCost. Unknown rate card keys return 404 not_found; zones missing from the card return 400 with availableZones.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"boxes\": [\n    { \"id\": \"small\", \"dimensions\": { \"length\": 10, \"width\": 8, \"height\": 6 }, \"weight\": 0.4, \"cost\": 2.5 },\n    { \"id\": \"medium\", \"dimensions\": { \"length\": 15, \"width\": 12, \"height\": 10 }, \"weight\": 0.7, \"cost\": 4.0 }\n  ],\n  \"items\": [\n    { \"id\": \"item1\", \"dimensions\": { \"length\": 5, \"width\": 4, \"height\": 3 }, \"weight\": 1.5, \"quantity\": 3 }\n  ],\n  \"options\": { \"objective\": \"lowest-invoice-cost\", \"rateCardKey\": \"rc_abc123xyz456\", \"zone\": \"4\" }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v1/shipping-optimize",
              "host": ["{{baseUrl}}"],
              "path": ["api", "v1", "shipping-optimize"]
            }
          }
        },
        {
          "name": "Batch orders (sync)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "description": "Optimize multiple orders in one synchronous request using orders[] instead of items[]. Sync batches allow up to 50 orders, 100 items per order, and 500 total items (quantities included), processed within a 20s budget. The response contains per-order results plus a batch-level summary.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"boxes\": [\n    { \"id\": \"standard\", \"dimensions\": { \"length\": 12, \"width\": 10, \"height\": 8 }, \"weightCapacity\": 20, \"cost\": 3.0 }\n  ],\n  \"orders\": [\n    { \"orderId\": \"ORD-001\", \"items\": [ { \"id\": \"a\", \"dimensions\": { \"length\": 4, \"width\": 3, \"height\": 2 }, \"weight\": 1.5, \"quantity\": 2 } ] },\n    { \"orderId\": \"ORD-002\", \"items\": [ { \"id\": \"b\", \"dimensions\": { \"length\": 6, \"width\": 5, \"height\": 4 }, \"weight\": 2.8 } ] }\n  ],\n  \"options\": { \"objective\": \"fewest-parcels\" }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v1/shipping-optimize",
              "host": ["{{baseUrl}}"],
              "path": ["api", "v1", "shipping-optimize"]
            }
          }
        },
        {
          "name": "Async batch + webhook",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "description": "Submit a batch in the background with \"async\": true (allows up to 500 orders / 5000 total items). Returns 202 Accepted with a jobId and statusUrl (also in the Location header); quota is consumed at submission time. Poll GET /api/v1/batch-jobs/{jobId} for status and results, or provide the optional top-level webhook field to be notified when the job finishes: CartonPilot POSTs the result to webhook.url, signing each delivery with an X-CartonPilot-Signature header of the form sha256=<hex HMAC-SHA256 of the raw body> computed with webhook.secret.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"boxes\": [\n    { \"id\": \"standard\", \"dimensions\": { \"length\": 12, \"width\": 10, \"height\": 8 }, \"weightCapacity\": 20, \"cost\": 3.0 }\n  ],\n  \"orders\": [\n    { \"orderId\": \"ORD-001\", \"items\": [ { \"id\": \"a\", \"dimensions\": { \"length\": 4, \"width\": 3, \"height\": 2 }, \"weight\": 1.5, \"quantity\": 2 } ] },\n    { \"orderId\": \"ORD-002\", \"items\": [ { \"id\": \"b\", \"dimensions\": { \"length\": 6, \"width\": 5, \"height\": 4 }, \"weight\": 2.8 } ] }\n  ],\n  \"async\": true,\n  \"webhook\": { \"url\": \"https://example.com/hooks/cartonpilot\", \"secret\": \"whsec_example\" },\n  \"options\": { \"objective\": \"fewest-parcels\" }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v1/shipping-optimize",
              "host": ["{{baseUrl}}"],
              "path": ["api", "v1", "shipping-optimize"]
            }
          }
        },
        {
          "name": "Saved box set + item catalog (SKU refs)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "description": "Use a saved box set (boxSetKey, instead of inline boxes) and a saved item catalog (catalogKey) so you can send SKU references like { \"sku\": \"WIDGET-1\", \"quantity\": 2 } instead of repeating item dimensions. catalogKey is required whenever items reference SKUs; unknown boxSetKey/catalogKey values return 404 not_found.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"boxSetKey\": \"bs_abc123xyz456\",\n  \"catalogKey\": \"ic_def789uvw012\",\n  \"items\": [\n    { \"sku\": \"WIDGET-1\", \"quantity\": 2 },\n    { \"sku\": \"GADGET-7\" }\n  ],\n  \"options\": { \"objective\": \"lowest-cost\" }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v1/shipping-optimize",
              "host": ["{{baseUrl}}"],
              "path": ["api", "v1", "shipping-optimize"]
            }
          }
        },
        {
          "name": "Retry safely (Idempotency-Key)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}",
                "description": "Unique key (max 200 chars) for safe retries; Postman generates a fresh GUID per send. Reuse the same key to retry: replays of the same key + payload return the stored response (with Idempotency-Replayed: true) without consuming quota; the same key with a different payload returns 409 idempotency_conflict. Keys expire after 24 hours."
              }
            ],
            "description": "Same optimization request with an Idempotency-Key header so network retries never double-charge your quota. Send the same key and payload again to get the stored response back (marked with an Idempotency-Replayed: true response header).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"boxes\": [\n    { \"id\": \"small\", \"name\": \"Small Box\", \"dimensions\": { \"length\": 10, \"width\": 8, \"height\": 6 }, \"weightCapacity\": 10, \"weight\": 0.4, \"cost\": 2.5 }\n  ],\n  \"items\": [\n    { \"id\": \"item1\", \"name\": \"Widget\", \"dimensions\": { \"length\": 5, \"width\": 4, \"height\": 3 }, \"weight\": 1.5, \"quantity\": 2 }\n  ],\n  \"options\": { \"objective\": \"fewest-parcels\" }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/api/v1/shipping-optimize",
              "host": ["{{baseUrl}}"],
              "path": ["api", "v1", "shipping-optimize"]
            }
          }
        },
        {
          "name": "API self-docs (GET)",
          "request": {
            "method": "GET",
            "header": [],
            "description": "Returns live documentation for your API key: tier capabilities and limits, monthly item quota usage, supported objectives and algorithms, example requests, and error codes.",
            "url": {
              "raw": "{{baseUrl}}/api/v1/shipping-optimize",
              "host": ["{{baseUrl}}"],
              "path": ["api", "v1", "shipping-optimize"]
            }
          }
        }
      ]
    },
    {
      "name": "Batch jobs",
      "description": "Status and results for async batch jobs submitted with \"async\": true.",
      "item": [
        {
          "name": "List recent jobs",
          "request": {
            "method": "GET",
            "header": [],
            "description": "List your 20 most recent async batch jobs (statuses only). Each entry includes jobId, status (pending | processing | completed | failed), totalItems, timestamps, and a statusUrl to fetch the full result.",
            "url": {
              "raw": "{{baseUrl}}/api/v1/batch-jobs",
              "host": ["{{baseUrl}}"],
              "path": ["api", "v1", "batch-jobs"]
            }
          }
        },
        {
          "name": "Get job status/results",
          "request": {
            "method": "GET",
            "header": [],
            "description": "Poll an async batch job by the jobId returned in the 202 response. While pending/processing the response contains status and timestamps; when status is \"completed\" it includes the full batch result under `result`; failed jobs include an `error` message. Unknown job ids return 404 not_found.",
            "url": {
              "raw": "{{baseUrl}}/api/v1/batch-jobs/:jobId",
              "host": ["{{baseUrl}}"],
              "path": ["api", "v1", "batch-jobs", ":jobId"],
              "variable": [
                {
                  "key": "jobId",
                  "value": "cm0abc123",
                  "description": "Job id from the async submit (202) response (jobId field / Location header)"
                }
              ]
            }
          }
        }
      ]
    }
  ]
}
