Documentación API

Referencia API CartonPilot

Guía completa para integrar la API de optimización de embalaje CartonPilot en tus aplicaciones.

API Interactiva (Swagger)

Descripción general

La API CartonPilot proporciona optimización de embalaje 3D para envíos de comercio electrónico. Dado un conjunto de tamaños de cajas disponibles y artículos a enviar, la API calcula la configuración de embalaje óptima para minimizar los costos de envío mientras maximiza la utilización de las cajas.

Optimización rápida

Resultados en milisegundos con algoritmos avanzados

Embalaje 3D

Embalaje 3D real con soporte de rotación

Restricciones de peso

Respeta los límites de peso para cada tipo de caja

Visualización

Datos de colocación 3D estructurados con salida ASCII y HTML opcional (Starter en adelante)

Tools & SDKs

Explore the API interactively in the live API explorer, or download the Postman collection with ready-to-run requests for every endpoint (set the apiKey collection variable and go). Official TypeScript and Python SDKs - with typed requests, async job polling, and webhook signature verification - are headed to npm and PyPI; contact us for early access.

Autenticación

Todas las solicitudes API requieren autenticación usando una clave API. Incluye tu clave API en los encabezados de la solicitud:

Authorization header
Authorization: Bearer YOUR_API_KEY

Important: Mantén tus claves API seguras. Nunca las expongas en código del lado del cliente o en repositorios públicos.

Puedes generar claves API desde tu panel de control.

Endpoints

POST/api/v1/shipping-optimize

Optimizar el embalaje de un conjunto de artículos en las cajas disponibles.

Cuerpo de la solicitud

Objeto JSON que contiene boxes, items y options (ver Formato de solicitud)

GET/api/v1/shipping-optimize

Recuperar capacidades de la API, estadísticas de uso y ejemplos de solicitudes para tu nivel.

Respuesta

Objeto JSON con capacidades del nivel, información de cuota y estadísticas de uso

GET/api/v1/batch-jobs

List your 20 most recent async batch jobs (statuses only). See Batch Orders for details.

Respuesta

JSON object with a jobs array of job status objects

GET/api/v1/batch-jobs/{id}

Poll the status of an async batch job submitted with "async": true. When the job is completed, the response includes the full batch result.

Respuesta

Job status object: jobId, status (pending | processing | completed | failed), totalItems, timestamps, plus result when completed or error when failed

Conjuntos de cajas

Los conjuntos de cajas te permiten guardar y reutilizar configuraciones de cajas en las solicitudes API. En lugar de enviar el array completo de cajas con cada solicitud, puedes referenciar un conjunto guardado por su clave única.

Tip: Crea conjuntos de cajas separados para diferentes escenarios de envío, como cajas de cartón estándar vs cajas aisladas para artículos sensibles a la temperatura.

Gestión de conjuntos de cajas

Crea y gestiona tus conjuntos de cajas desde el panel de conjuntos de cajas. Cada conjunto obtiene una clave única como bs_abc123xyz456.

Uso de conjuntos de cajas en solicitudes

Usa boxSetKey en lugar del array boxes:

POST /api/v1/shipping-optimize
{
  "boxSetKey": "bs_abc123xyz456",
  "items": [
    {
      "id": "item-001",
      "name": "Product A",
      "dimensions": { "length": 4, "width": 3, "height": 2 },
      "weight": 1.5,
      "quantity": 2
    }
  ],
  "options": {
    "objective": "fewest-parcels"
  }
}

Límites de conjuntos de cajas por nivel

NivelConjuntos máx
Free1 (max 10 boxes)
Starter2 (max 20 boxes each)
Growth5 (max 50 boxes each)
Scale50 (max 100 boxes each)

Formato de solicitud

El cuerpo de la solicitud POST debe incluir boxes o boxSetKey (no ambos), más items (pedido único) u orders (lote), y opcionalmente options. Las dimensiones y pesos son independientes de las unidades — usa cualquier unidad (pulgadas/lbs, cm/kg) siempre que sea consistente dentro de una solicitud.

Top-Level Fields

CampoTipoRequeridoDescripción
boxesarrayYes*Available boxes for packing (see Box Object). *Either boxes or boxSetKey is required, not both
boxSetKeystringYes*Saved box set key (e.g. bs_abc123xyz456) used instead of inline boxes (see Box Sets)
catalogKeystringNoSaved item catalog key (e.g. ic_def789uvw012). Required when any items entry is a { sku, quantity } catalog reference (see SKU Catalog References below)
itemsarrayYes*Items to pack for a single order (see Item Object). Entries may alternatively be { sku, quantity } catalog references. *Either items or orders is required, not both
ordersarrayYes*Array of { orderId, items } objects for batch processing (see Batch Orders). Each order may also carry a zone for rate-card lookups, overriding options.zone (see Rate Cards)
asyncbooleanNoProcess a batch in the background (requires orders; default false). Returns 202 with a job to poll at /api/v1/batch-jobs/{jobId} and allows larger batches (see Async Batch Processing)
webhookobjectNo{ url, secret? } - async batches only. CartonPilot POSTs the job outcome to url when it completes or fails; secret enables HMAC signatures (see Completion Webhooks)
optionsobjectNoOptimization options (see Options Object)

Objeto Box

CampoTipoRequeridoDescripción
idstringYesIdentificador único para el tipo de caja
namestringNoNombre legible
dimensionsobjectYes{ length, width, height } en tu unidad preferida
weightCapacitynumberNoLímite de peso estructural de la caja - peso máximo que el material de la caja puede soportar físicamente
weightnumberNoTare weight of the empty box. Added to the contents weight to compute each shipment's totalWeight
costnumberNoCosto por caja para la optimización

Objeto Item

FieldTypeRequiredDescription
idstringYesUnique identifier for the item
namestringNoHuman-readable name
dimensionsobjectYes{ length, width, height } in your preferred unit
weightnumberNoItem weight for constraint checking
quantitynumberNoNumber of this item to pack (default: 1)
fragilebooleanNoMark as fragile for special handling
keepUprightbooleanNoPrevent vertical rotation
fragilityLevelnumberNoNivel de fragilidad de 0 (robusto) a 5 (extremadamente frágil). Usado por cartonpilot-physics para decisiones de apilamiento. (Solo Pro+)
maxTopLoadnumberNoPeso máximo que este artículo puede soportar encima. Usado por cartonpilot-physics para aplicar límites de capacidad de carga. (Solo Pro+)

SKU Catalog References

Instead of repeating dimensions on every request, save your product dimensions once as an item catalog (managed via /api/item-catalogs; keys look like ic_def789uvw012). Then send catalogKey on the request and reference items by SKU: any entry in items (or an order's items in a batch) may be { "sku": "WIDGET-1", "quantity": 2 } instead of a full item object (quantity defaults to 1). Inline items and SKU references can be mixed freely in the same request.

POST /api/v1/shipping-optimize · SKU references
{
  "boxSetKey": "bs_abc123xyz456",
  "catalogKey": "ic_def789uvw012",
  "items": [
    { "sku": "WIDGET-1", "quantity": 2 },
    { "sku": "GADGET-7" },
    { "id": "custom-1", "dimensions": { "length": 4, "width": 3, "height": 2 }, "weight": 1.5 }
  ],
  "options": { "objective": "fewest-parcels" }
}

Referencing a SKU that doesn't exist in the catalog returns 400 invalid_request with an unknownSkus list; using SKU references without a catalogKey also returns 400. SKUs per catalog are tier-limited: Free 100, Starter 1,000, Growth 10,000, Scale 50,000 (with 1 / 2 / 5 / 50 catalogs per plan respectively).

Objeto Options

CampoTipoPredeterminadoDescripción
objectivestring"fewest-parcels"Recommended. Lets the engine pick the algorithm: "fewest-parcels" (minimize parcels, uses cartonpilot-ultra), "lowest-cost" (cartonpilot-ultra with cost prioritization), "lowest-billable-weight" (minimize carrier billable weight, uses cartonpilot-ultra; requires dimDivisor), "lowest-invoice-cost" (minimize estimated carrier invoice cost against your saved rate card; requires rateCardKey and a zone — see Rate Cards), "fastest" (first-fit)
algorithmstring-Advanced. Explicit packing algorithm override (see Algorithms section). Takes precedence over objective. If neither is set, your account's default algorithm (dashboard setting) or cartonpilot-ultra is used
allowRotationbooleantrueAllow items to be rotated for better fit
prioritizestring"space""space" | "cost" | "speed" | "consolidation" | "billable-weight" | "invoice-cost" ("billable-weight" requires dimDivisor; "invoice-cost" requires rateCardKey and a zone)
dimDivisornumber-Your carrier's dimensional-weight divisor (e.g. 139 for inches/lbs, 5000 for cm/kg, or your negotiated value). Must match the units used in the request. When provided, every shipment includes a billing object and the summary includes totalBillableWeight. Overrides a rate card's divisor when both are present
rateCardKeystring-Key of a saved rate card (e.g. rc_abc123xyz456) for invoice-cost estimation and optimization. Requires a zone (options.zone or per-order zones). See Rate Cards below
zonestring | number-Shipping zone for rate-card lookups; must exist in the referenced rate card. For batch requests this is the default — a zone on an individual order overrides it
enableVisualizationbooleanfalseInclude structured placement data per shipment in a visualization field for client-side rendering (not available on Free tier)
visualizationFormatstring"data""data" | "ascii" | "html". "ascii" adds an asciiVisualization string per shipment; "html" adds visualizationHtml and shipmentVisualizations HTML strings at the top level. Requires enableVisualization
maxShipmentWeightnumber-Carrier weight limit - shipments above this threshold incur overweight fees
oversizedItemHandlingstring"unpacked""custom-box" | "unpacked" - How to handle items larger than any available box
overweightItemHandlingstring"allow""allow" | "unpacked" - How to handle items heavier than maxShipmentWeight (defaults to "allow" when maxShipmentWeight is set)

Most carriers bill against the greater of a parcel's actual weight and its dimensional weight (box volume divided by your carrier's divisor). Supply your contract's divisor in options.dimDivisor and every shipment includes a billing breakdown of actual, dimensional, and billable weight. Set options.objective to "lowest-billable-weight" and the engine also picks boxes to minimize what carriers bill. Billable weight is a major driver of freight cost, so this usually reduces it — but it is not the only driver, and it does not account for zone pricing, weight breaks or surcharges in your specific agreement. For a rate-aware answer, use "lowest-invoice-cost" with a configured rate card, which prices each candidate against your actual contract.

Rate Cards (Invoice-Cost Optimization)

For true invoice-cost optimization, upload your negotiated carrier rates as a rate card (managed via /api/rate-cards; keys look like rc_abc123xyz456). A card contains a zone × weight rate table (rates: strictly increasing weight breaks, each with the same zone keys), an optional negotiated DIM divisor, an optional maxPackageWeight (a hard cap — the optimizer will never build a parcel heavier than this; it is applied as maxShipmentWeight, so all items need weights), and an optional overweight surcharge (overweightThreshold + overweightFee — e.g. UPS's heavy-package fee for parcels over 50 lb actual weight — priced into the cost function rather than enforced as a limit). Everything is unit-agnostic: weights and the divisor just need to use the same units as your request.

POST /api/rate-cards
{
  "name": "UPS Ground 2026",
  "carrier": "UPS",
  "service": "Ground",
  "currency": "USD",
  "dimDivisor": 139,
  "maxPackageWeight": 150,
  "overweightThreshold": 50,
  "overweightFee": 24.00,
  "rates": [
    { "weight": 1,  "zoneRates": { "2": 9.51,  "4": 10.02, "7": 11.33 } },
    { "weight": 5,  "zoneRates": { "2": 11.87, "4": 13.44, "7": 16.09 } },
    { "weight": 10, "zoneRates": { "2": 14.52, "4": 17.91, "7": 23.86 } },
    { "weight": 50, "zoneRates": { "2": 32.10, "4": 44.75, "7": 68.20 } }
  ]
}

Reference the card on optimize requests with options.rateCardKey plus a zone: options.zone for single orders (or as a batch-wide default), or a zone on each entry in orders (per-order zones override the request-level one). With a rate card present, every shipment's billing block gains an invoice estimate ({ zone, baseRate, overweightFee, total, currency }), dimWeight/billableWeight use the card's DIM divisor (unless options.dimDivisor overrides it), and the order summary — and batch summary — gain totalInvoiceCost and currency. Billable weights beyond the card's last weight break are billed at the last break's rate; the overweight surcharge keys off actual weight (contents plus box tare), matching how carriers assess heavy-package fees.

Set options.objective to "lowest-invoice-cost" and the optimizer downsizes each shipment to the box that minimizes your estimated freight (rate table + DIM weight + overweight surcharge) plus box material cost. It also composes with an explicit options.algorithm, since it runs as a post-pass.

POST /api/v1/shipping-optimize · rate card
{
  "boxSetKey": "bs_abc123xyz456",
  "catalogKey": "ic_def789uvw012",
  "orders": [
    { "orderId": "ORD-001", "zone": "4", "items": [{ "sku": "WIDGET-1", "quantity": 2 }] },
    { "orderId": "ORD-002", "zone": "7", "items": [{ "sku": "GADGET-7" }] }
  ],
  "options": { "objective": "lowest-invoice-cost", "rateCardKey": "rc_abc123xyz456" }
}

Using an unknown rateCardKey returns 404 not_found; a zone that isn't in the card returns 400 invalid_request with an availableZones list; "lowest-invoice-cost" without rateCardKey, or rateCardKey without any zone, also returns 400. Rate cards are available on every plan; saved cards are tier-limited: Free 1, Starter 3, Growth 10, Scale 50.

Understanding Weight Constraints

The API supports two distinct types of weight constraints:

Box Structural Limit (weightCapacity)

Set on each box type. This represents the maximum weight the box material can physically hold without breaking. The optimizer will not place items that would exceed this structural limit.

"boxes": [{ "id": "medium", "weightCapacity": 25, ... }]
Carrier Shipment Limit (maxShipmentWeight)

Set in options. This represents the carrier threshold above which overweight fees apply. All shipments will be optimized to stay under this limit regardless of box capacity. When this option is set, all items must have a weight property defined.

"options": { "maxShipmentWeight": 50 }
Overweight Item Handling (overweightItemHandling)

When a single item exceeds the maxShipmentWeight, this option controls how it's handled:

  • "allow" (default): Item is allowed to ship alone in a box, weight limit waived for this shipment
  • "unpacked": Item is placed in the unpackedItems array with reason
"options": { "maxShipmentWeight": 50, "overweightItemHandling": "unpacked" }

Both constraints are enforced simultaneously. A shipment must satisfy both the box structural capacity and the carrier weight limit.

Pedidos por lotes

Process multiple orders in a single API request. Instead of sending individual items, you can send an array of orders, each with its own items. The API will optimize packing for each order independently and return consolidated results.

Availability: All tiers

Batch order processing is available for all subscription tiers with the same limits.

Batch Limits

Limit TypeSync (default)Async ("async": true)
Max Orders per Batch50500
Max Items per Order100100
Max Total Items per Batch5005,000

Batch Request Format

Use the orders array instead of items. Each order must have an orderId and its own items array.

POST /api/v1/shipping-optimize · batch
{
  "boxes": [
    { "id": "small", "name": "Small Box", "dimensions": { "length": 10, "width": 8, "height": 6 }, "cost": 2.50 },
    { "id": "medium", "name": "Medium Box", "dimensions": { "length": 14, "width": 12, "height": 10 }, "cost": 4.00 }
  ],
  "orders": [
    {
      "orderId": "ORD-001",
      "items": [
        { "id": "item-1", "name": "Book", "dimensions": { "length": 9, "width": 6, "height": 1.5 }, "weight": 1.2, "quantity": 2 }
      ]
    },
    {
      "orderId": "ORD-002",
      "items": [
        { "id": "item-2", "name": "Laptop", "dimensions": { "length": 14, "width": 10, "height": 2 }, "weight": 4.5, "quantity": 1 },
        { "id": "item-3", "name": "Charger", "dimensions": { "length": 4, "width": 3, "height": 2 }, "weight": 0.3, "quantity": 3 }
      ]
    }
  ],
  "options": {
    "objective": "fewest-parcels"
  }
}

Batch Response Format

The response includes results for each order plus an overall summary.

200 OK · batch response
{
  "success": true,
  "batch": true,
  "results": [
    {
      "orderId": "ORD-001",
      "success": true,
      "data": {
        "shipments": [...],
        "unpackedItems": [],
        "summary": { "totalShipments": 1, "totalCost": 2.50, ... }
      }
    },
    {
      "orderId": "ORD-002",
      "success": true,
      "data": {
        "shipments": [...],
        "unpackedItems": [],
        "summary": { "totalShipments": 1, "totalCost": 4.00, ... }
      }
    }
  ],
  "summary": {
    "totalOrders": 2,
    "successfulOrders": 2,
    "failedOrders": 0,
    "totalBoxesUsed": 2,
    "totalCost": 6.50,
    "averageUtilization": 42.5,
    "totalItemsPacked": 3,
    "totalItemsUnpacked": 0
  },
  "metadata": {
    "algorithm": "cartonpilot-ultra",
    "executionTimeMs": 45,
    "timestamp": "2025-12-06T12:00:00.000Z",
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "tier": "BASIC"
  }
}

Async Batch Processing

For larger batches, set "async": true on a batch request (requires orders). This raises the limits to 500 orders, 100 items per order, and 5,000 total items. Instead of waiting for results, the API responds immediately with 202 Accepted, a jobId, and a Location header pointing at the job status URL. Your monthly item quota is consumed when the job is submitted.

POST /api/v1/shipping-optimize · async batch
{
  "boxes": [
    { "id": "standard", "dimensions": { "length": 12, "width": 10, "height": 8 }, "cost": 3.00 }
  ],
  "orders": [
    {
      "orderId": "ORD-001",
      "items": [
        { "id": "item-1", "dimensions": { "length": 4, "width": 3, "height": 2 }, "quantity": 2 }
      ]
    }
  ],
  "async": true,
  "options": {
    "objective": "fewest-parcels"
  }
}
202 Accepted · async response
{
  "success": true,
  "jobId": "cm0abc123",
  "status": "pending",
  "statusUrl": "/api/v1/batch-jobs/cm0abc123",
  "totalOrders": 1,
  "totalItems": 2,
  "requestId": "550e8400-e29b-41d4-a716-446655440000"
}

Poll GET /api/v1/batch-jobs/{id} (same API-key authentication) to track the job. The response contains jobId, status (pending | processing | completed | failed), totalItems, and createdAt / startedAt / completedAt timestamps. When the status is completed, the full batch response (same format as the sync batch response above) is included under result; when failed, the failure reason is included under error. You can also list your 20 most recent jobs with GET /api/v1/batch-jobs. Jobs (including their request payloads and results) are automatically deleted 30 days after submission.

Completion Webhooks

Instead of polling, add a webhook to an async batch request and CartonPilot will POST the outcome to your URL when the job completes or fails. URLs must use https (plain http is allowed for localhost during development). Deliveries are retried twice - after 5 and 30 seconds - and the outcome is reported as webhook.deliveryStatus (pending | delivered | failed: <reason>) when you poll the job.

POST /api/v1/shipping-optimize · async batch with webhook
{
  "boxes": [ ... ],
  "orders": [ ... ],
  "async": true,
  "webhook": {
    "url": "https://example.com/hooks/cartonpilot",
    "secret": "whsec_your_signing_secret"
  },
  "options": { "objective": "fewest-parcels" }
}
Webhook delivery payload
{
  "event": "batch_job.completed",
  "jobId": "cm0abc123",
  "status": "completed",
  "statusUrl": "/api/v1/batch-jobs/cm0abc123",
  "totalOrders": 120,
  "totalItems": 480,
  "summary": { "totalOrders": 120, "successfulOrders": 120, "failedOrders": 0, "totalBoxesUsed": 143, "totalCost": 214.5, "averageUtilization": 74.2, "totalItemsPacked": 480, "totalItemsUnpacked": 0 },
  "timestamp": "2026-07-27T04:29:12.982Z"
}

Failed jobs send "event": "batch_job.failed" with an error field instead of summary. Every delivery carries X-CartonPilot-Event and X-CartonPilot-Job-Id headers. When you supply a secret (8+ characters), deliveries are signed so you can verify they came from CartonPilot: the X-CartonPilot-Signature header contains sha256= followed by the hex HMAC-SHA256 of the raw request body using your secret. Compute the same HMAC on your side and compare with a timing-safe comparison.

Verifying the signature (Node.js)
import { createHmac, timingSafeEqual } from "crypto";

function verifyCartonPilotWebhook(rawBody, signatureHeader, secret) {
  const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
  return timingSafeEqual(Buffer.from(signatureHeader), Buffer.from(expected));
}

Backward Compatibility

The original single-order format using items array continues to work. Batch processing is only triggered when the orders array is present. Provide either items or orders, not both.

Formato de respuesta

Successful responses include shipment details, packing statistics, and metadata.

Response Structure

FieldDescription
successBoolean indicating request success
data.shipmentsArray of shipment objects with packed items (see shipment fields below)
data.shipments[].utilization{ volume, weight, efficiency } utilization percentages for the shipment
data.shipments[].contentsWeightTotal weight of the packed items only (excludes the box itself)
data.shipments[].totalWeightcontentsWeight plus the box tare weight, when box.weight is provided
data.shipments[].billing{ actualWeight, dimWeight, billableWeight } carrier billing figures, when options.dimDivisor or a rate card is provided. actualWeight is contents plus box tare (unrounded), dimWeight is box volume / dimDivisor rounded up, billableWeight is the greater of the two — what a carrier bills against. With a rate card, also includes an invoice object: { zone, baseRate, overweightFee, total, currency } (see Rate Cards)
data.shipments[].visualizationStructured placement data (boxDimensions + item placements) for client-side rendering, when enableVisualization is true
data.shipments[].asciiVisualizationASCII rendering of the shipment, only when visualizationFormat is "ascii"
data.unpackedItemsItems that couldn't fit in any box
data.summaryAggregated statistics: totalShipments, totalCost, averageUtilization, itemsSuccessfullyPacked, itemsUnpacked, plus totalBillableWeight (sum of per-shipment billable weights) when options.dimDivisor is provided, and totalInvoiceCost + currency (sum of per-shipment invoice estimates) when a rate card is used — the batch summary gains the same two fields
data.suggestionsOptimization suggestions
metadataalgorithm, executionTimeMs (number, milliseconds), timestamp, requestId (uuid, also sent as the X-Request-Id header), tier, and an optional note (e.g. when physics parameters were ignored)
visualizationHtmlCombined 3D HTML visualization, included with shipmentVisualizations (per-shipment HTML strings) only when visualizationFormat is "html" (not available on Free tier)

Box Structure in Shipments

Each shipment contains a box object with the following fields:

FieldTypeDescription
idstringBox ID (from your submitted boxes, or "custom-*" for oversized items)
namestringBox name (or "Custom Box" for oversized items)
dimensionsobject{ length, width, height } of the box
costnumberBox cost (optional, 0 for custom boxes)
weightnumberTare weight of the empty box (only present when supplied in the request)
typestring"custom" when oversizedItemHandling is "custom-box" (only present for custom boxes)

Packed Items Structure

Each packed item in a shipment includes the following fields:

FieldTypeDescription
itemIdstringOriginal item ID as submitted in the request
itemIndexnumberIndex within quantity expansion (0, 1, 2, ...). For items with quantity > 1, each instance gets a unique index.
positionobject{ x, y, z } coordinates of item placement in the box
rotationobjectAxis mapping showing how item was rotated to fit
rotatedDimensionsobject{ length, width, height } after rotation applied

Rate Limit Headers

Every response includes rate limit information and a unique request identifier (also available as metadata.requestId in the body):

Response headers
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 985
X-RateLimit-Reset: 2025-11-30T00:00:00.000Z
X-Request-Id: 550e8400-e29b-41d4-a716-446655440000

Algoritmos

All 8 algorithms are available on every subscription tier. For most integrations we recommend setting options.objective ("fewest-parcels", "lowest-cost", "lowest-billable-weight", "lowest-invoice-cost", or "fastest") and letting the engine pick the algorithm; options.algorithm remains available as a power-user override and takes precedence over objective.

first-fitAll Tiers

Fast, simple algorithm that places items in the first available space. Best for speed-critical applications. Used by the "fastest" objective.

best-fitAll Tiers

Finds the best fitting space for each item, improving utilization over first-fit.

guillotineAll Tiers

Divides space into rectangles for efficient 2D packing. Good balance of speed and utilization.

max-rectsAll Tiers

Advanced algorithm maintaining multiple free rectangles for optimal packing.

cartonpilot-ultraAll Tiers (Default)

Our smartest algorithm, optimized for both binning (grouping items into separate shipments) and packing (placing items into the smallest possible box). Uses look-ahead optimization, post-pack consolidation, and multi-pass refinement to minimize total boxes and maximize space utilization. Best for most use cases. Used by the "fewest-parcels", "lowest-cost", "lowest-billable-weight", and "lowest-invoice-cost" objectives.

cartonpilot-fitAll Tiers

Combines global box selection strategy with fast 3D placement. Uses O(items × boxes × positions) complexity for efficient packing with excellent results.

cartonpilot-maxAll Tiers

Weight-based grouping algorithm that groups items by order, splits bundles exceeding weight limits, consolidates small bundles, and validates each bundle with 3D packing. Ideal for multi-order shipments where weight-based grouping is preferred.

cartonpilot-physicsAll Tiers

Physics-aware packing algorithm that prioritizes heavier items at the bottom for stability, respects fragility levels (0-5), enforces load capacity limits, and validates center of gravity balance. Includes support area validation (70% minimum) and rotation constraints for fragile items. Advanced features (fragilityLevel, maxTopLoad) require the Growth plan or above.

Niveles de suscripción

Choose the plan that fits your needs. All 8 algorithms are available on every tier — tiers differ only by request limits, monthly item quota, visualization, and physics parameters.

FeatureFree ($0)Starter ($49/mo)Growth ($149/mo)Scale ($399/mo)
Items/Month1,00010,00050,000250,000
Max Items per Request10251001,000
Max Box Types per Request102050100
API Keys11510
Rate Cards131050
Batch Orders (max/items/total)50/100/50050/100/50050/100/50050/100/500
3D Visualization-
Physics Parameters (fragilityLevel, maxTopLoad)--
AlgorithmsAll 8All 8All 8All 8
Overage Rate (per 1000 items)$8.00$6.00$4.00$3.00

All Algorithms on All Tiers

  • Algorithms: All 8 packing algorithms are available on every tier, including FREE.
  • What differs: items per request, box types per request, and monthly item quota.
  • Visualization: not available on the FREE tier.
  • Physics parameters: fragilityLevel and maxTopLoad require Growth or Scale.

Límites de tasa

Why We Count Items, Not API Requests

Batching multiple orders into a single API call is more efficient for everyone. Charging per item optimized aligns with our CPU costs and encourages you to batch orders for maximum efficiency. This means you can send 100 orders in one batch request and pay the same as 100 individual requests with one order each.

Usage limits are based on items processed per month (counting item quantities). Limits reset on the 1st of each month. When you exceed your limit, the API returns a 429 status code with details about your usage.

TierMonthly Item LimitOverage Rate
Free1,000 items$8.00 / 1,000 items
Starter10,000 items$6.00 / 1,000 items
Growth50,000 items$4.00 / 1,000 items
Scale250,000 items$3.00 / 1,000 items

Rate Limit Response

429 Too Many Requests
{
  "error": "quota_exceeded",
  "message": "This request would use 50 items, but you only have 25 items remaining in your monthly quota of 1000 items.",
  "itemsRequested": 50,
  "itemsRemaining": 25,
  "monthlyLimit": 1000,
  "resetAt": "2026-02-01T00:00:00.000Z",
  "requestId": "550e8400-e29b-41d4-a716-446655440000"
}

Response Headers

Every API response includes usage headers:

  • X-RateLimit-Limit: Your monthly item limit
  • X-RateLimit-Remaining: Items remaining after this request
  • X-RateLimit-Reset: When your quota resets (1st of next month)
  • X-Items-Used: Total items used this month
  • X-Request-Id: Unique identifier for this request (matches metadata.requestId)

Códigos de error

All error responses use a consistent envelope with a machine-readable error code, a human-readable message, optional detail fields, and a requestId you can include in support requests (also sent as the X-Request-Id header). Validation failures (400 invalid_request) include an issues array pointing at the offending fields. Requests are validated strictly: dimensions must be positive numbers, unknown algorithms are rejected, either boxes or boxSetKey is required (not both), and either items or orders is required (not both).

400 Bad Request · error envelope
{
  "error": "invalid_request",
  "message": "Request validation failed",
  "issues": [
    { "path": "items.0.dimensions.length", "message": "Number must be greater than 0" },
    { "path": "boxes", "message": "Either 'boxes' or 'boxSetKey' is required" }
  ],
  "requestId": "550e8400-e29b-41d4-a716-446655440000"
}
StatusErrorDescription
400invalid_requestRequest body failed validation (includes an issues array)
401authentication_failedInvalid or missing API key
403endpoint_not_allowedThis API key is restricted from the requested endpoint
403algorithm_not_allowedThis API key is restricted from the requested algorithm
400 / 403limit_exceededToo many items or box types for your tier (403), or batch limits exceeded (400)
404not_foundReferenced resource (e.g. boxSetKey, catalogKey, or rateCardKey) not found
409idempotency_conflictIdempotency-Key reused with a different request payload
429quota_exceededMonthly item quota exhausted
500internal_errorServer-side error, please retry

Idempotency

To retry a POST /api/v1/shipping-optimize request safely, send an optional Idempotency-Key header (any unique string up to 200 characters). If a request with the same key and the same payload is received again within 24 hours, the stored response is replayed — marked with an Idempotency-Replayed: true response header — without consuming your quota a second time. Reusing a key with a different payload returns 409 idempotency_conflict. This works for sync single and batch requests (200) as well as async submits (202), where the replay returns the same jobId.

Idempotency-Key header
Idempotency-Key: order-ORD-001-attempt-1

Ejemplos

Ejemplo de solicitud

POST /api/v1/shipping-optimize
{
  "boxes": [
    {
      "id": "b1-box",
      "name": "Box B1",
      "dimensions": {
        "length": 7,
        "width": 7,
        "height": 12
      },
      "weightCapacity": 25,
      "weight": 0.4,
      "cost": 1.18
    },
    {
      "id": "b3-box",
      "name": "Box B3",
      "dimensions": {
        "length": 11,
        "width": 10,
        "height": 14
      },
      "weightCapacity": 35,
      "weight": 0.6,
      "cost": 2.11
    },
    {
      "id": "b7-box",
      "name": "Box B7",
      "dimensions": {
        "length": 20,
        "width": 16,
        "height": 18
      },
      "weightCapacity": 55,
      "weight": 0.9,
      "cost": 3.98
    }
  ],
  "items": [
    {
      "id": "BOOK-001",
      "name": "Hardcover Book",
      "dimensions": {
        "length": 9.5,
        "width": 7.5,
        "height": 1.5
      },
      "weight": 1.8,
      "quantity": 2
    },
    {
      "id": "LAPTOP-COMP",
      "name": "Laptop Computer",
      "dimensions": {
        "length": 18,
        "width": 11,
        "height": 4.5
      },
      "weight": 6.8,
      "quantity": 1
    }
  ],
  "options": {
    "objective": "fewest-parcels",
    "allowRotation": true,
    "dimDivisor": 139,
    "enableVisualization": true
  }
}

Ejemplo de respuesta

200 OK · response
{
  "success": true,
  "data": {
    "shipments": [
      {
        "box": {
          "id": "b7-box",
          "name": "Box B7",
          "dimensions": {
            "length": 20,
            "width": 16,
            "height": 18
          },
          "cost": 3.98,
          "weight": 0.9
        },
        "packedItems": [
          {
            "itemId": "BOOK-001",
            "itemIndex": 0,
            "position": {
              "x": 0,
              "y": 0,
              "z": 0
            },
            "rotation": {
              "lengthAxis": "x",
              "widthAxis": "y",
              "heightAxis": "z"
            },
            "rotatedDimensions": {
              "length": 9.5,
              "width": 7.5,
              "height": 1.5
            }
          },
          {
            "itemId": "BOOK-001",
            "itemIndex": 1,
            "position": {
              "x": 9.5,
              "y": 0,
              "z": 0
            },
            "rotation": {
              "lengthAxis": "x",
              "widthAxis": "y",
              "heightAxis": "z"
            },
            "rotatedDimensions": {
              "length": 9.5,
              "width": 7.5,
              "height": 1.5
            }
          },
          {
            "itemId": "LAPTOP-COMP",
            "itemIndex": 0,
            "position": {
              "x": 0,
              "y": 0,
              "z": 1.5
            },
            "rotation": {
              "lengthAxis": "x",
              "widthAxis": "y",
              "heightAxis": "z"
            },
            "rotatedDimensions": {
              "length": 18,
              "width": 11,
              "height": 4.5
            }
          }
        ],
        "utilization": {
          "volume": 18.2,
          "weight": 18.9,
          "efficiency": 18.2
        },
        "contentsWeight": 10.4,
        "totalWeight": 11.3,
        "billing": {
          "actualWeight": 11.3,
          "dimWeight": 42,
          "billableWeight": 42
        },
        "visualization": {
          "boxDimensions": {
            "length": 20,
            "width": 16,
            "height": 18
          },
          "items": [
            {
              "id": "BOOK-001_0",
              "name": "Hardcover Book",
              "position": {
                "x": 0,
                "y": 0,
                "z": 0
              },
              "dimensions": {
                "length": 9.5,
                "width": 7.5,
                "height": 1.5
              },
              "color": "#FF6B6B"
            },
            {
              "id": "BOOK-001_1",
              "name": "Hardcover Book",
              "position": {
                "x": 9.5,
                "y": 0,
                "z": 0
              },
              "dimensions": {
                "length": 9.5,
                "width": 7.5,
                "height": 1.5
              },
              "color": "#4ECDC4"
            },
            {
              "id": "LAPTOP-COMP_0",
              "name": "Laptop Computer",
              "position": {
                "x": 0,
                "y": 0,
                "z": 1.5
              },
              "dimensions": {
                "length": 18,
                "width": 11,
                "height": 4.5
              },
              "color": "#45B7D1"
            }
          ]
        }
      }
    ],
    "unpackedItems": [],
    "summary": {
      "totalShipments": 1,
      "totalCost": 3.98,
      "averageUtilization": 18.2,
      "itemsSuccessfullyPacked": 3,
      "itemsUnpacked": 0,
      "totalBillableWeight": 42
    },
    "suggestions": [
      "CartonPilot-Ultra algorithm: Enhanced with look-ahead and consolidation"
    ]
  },
  "metadata": {
    "algorithm": "cartonpilot-ultra",
    "executionTimeMs": 32,
    "timestamp": "2025-12-06T12:00:00.000Z",
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "tier": "BASIC"
  }
}

Ejemplo cURL

cURL
curl -X POST https://cartonpilot.com/api/v1/shipping-optimize \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "boxes": [
      {
        "id": "small-box",
        "dimensions": { "length": 10, "width": 8, "height": 6 }
      }
    ],
    "items": [
      {
        "id": "item-1",
        "dimensions": { "length": 4, "width": 3, "height": 2 },
        "quantity": 1
      }
    ],
    "options": {
      "objective": "fewest-parcels"
    }
  }'

¡Pruébalo ahora!

Prueba la API de forma interactiva en nuestro explorador Swagger: cada endpoint, esquema de solicitud y ejemplo está disponible.

Abrir explorador de API