Packing API
Invia il carico e ricevi un piano di carico ottimizzato e fisicamente valido — posizionamenti, metri di carico, piedi lineari e posizioni dei pallet fatturabili. Lo stesso motore utilizzato nell'app, continuamente validato rispetto ai layout reali dei dispatcher. Accesso sandbox su ogni piano; accesso in produzione su Enterprise.
Guida rapida
- Accedi → clicca sul tuo nome → API → genera una chiave.
- Invia un job di packing (questo viene eseguito in sandbox — gratuito su ogni piano):
curl -X POST https://hansatic.com/api/v1/pack \
-H "Authorization: Bearer hk_live_..." \
-H "Content-Type: application/json" \
-d '{
"vehicle": "CURTAINSIDER_136",
"objective": "ldm",
"sandbox": true,
"items": [{
"name": "Machine crate",
"length": 2300, "width": 750, "height": 1400,
"weight": 300, "quantity": 7,
"stackable": false, "tiltable": false
}]
}'{
"job_id": "5e3617d1-abe4-4549-b7e6-d18c6baf814f",
"status": "queued",
"sandbox": true,
"status_url": "https://hansatic.com/api/v1/jobs/5e3617d1-..."
}Interroga status_url (stesso header di autenticazione) finché status non è "done":
{
"job_id": "5e3617d1-...",
"status": "done",
"result": {
"placed_count": 7,
"unplaced_count": 0,
"units": "metric",
"metrics": {
"ldm_m": 5.25,
"span_mm": 5250,
"linear_ft": 17.2,
"pallet_positions_eur": 14,
"total_weight_kg": 2100,
"overlaps": 0
},
"items": [
{ "id": "...", "name": "Machine crate", "placed": true,
"x": 8370, "y": 0, "z": 0,
"rotation_deg": 90, "tilt_deg": 0, "...": "..." }
],
"plan_url": "https://hansatic.com/en/app/plans/..."
}
}Endpoint
POST /api/v1/pack
Convalida e mette in coda un job di packing. Restituisce 202 con un job_id. Supporta l'header Idempotency-Key — i tentativi ripetuti con la stessa chiave restituiscono il job originale invece di eseguire un nuovo packing.
GET /api/v1/jobs/{id}
Stato del job e, al termine, il risultato completo. I risultati vengono conservati per 30 giorni; i piani di produzione rimangono permanentemente nel tuo workspace.
GET /api/v1/openapi.json
Il contratto OpenAPI 3.1 leggibile dalla macchina — importalo in Postman, generatori di codice o indirizza il tuo strumento di coding AI. I codici dei veicoli sono iniettati dal motore live, quindi è sempre aggiornato. Apri la specifica ↗
Campi della risposta
L'oggetto result di un job completato:
| Campo | Tipo | Descrizione |
|---|---|---|
| placed_count / unplaced_count | integer | Quanti articoli sono stati caricati / non sono stati caricati. |
| units | "metric" | "imperial" | Le unità di misura in cui sono espressi tutti i valori numerici (riprende la richiesta). |
| metrics.ldm_m | number | Metri di carico — l'estensione del blocco di carico (max fine − min inizio). |
| metrics.linear_ft | number | La stessa estensione in piedi lineari (US LTL). |
| metrics.span_mm | integer | L'estensione in mm, sempre metrica. |
| metrics.pallet_positions_eur | integer | null | Posizioni pallet EUR fatturabili. null se il layout è troppo grande per essere misurato. |
| metrics.total_weight_kg | number | Peso caricato, sempre in kg. |
| metrics.overlaps | integer | Coppie interpenetranti — sempre 0 per un layout valido. |
| items[].placed | boolean | Indica se l'articolo è stato inserito nel layout. |
| items[].x / y / z | number | null | Posizione nelle unità della richiesta: x dall'estremità della porta, y dalla parete sinistra, z dal pavimento. Tutti e tre i valori sono null se placed è false. |
| items[].rotation_deg / tilt_deg | 0 | 90 | Orientamento: rotazione attorno all'asse verticale; tilt = appoggiato su un lato. |
| items[].length / width / height / weight | number | L'articolo come inviato, riportato nelle unità della richiesta. |
| plan_url | string | null | Il piano creato nell'app. null per i job in sandbox. |
Versionamento e stabilità
Il contratto è versionato nel percorso (/api/v1/). All'interno di v1, le modifiche sono solo additive — nuovi campi opzionali nella richiesta, nuovi campi nella risposta, nuovi codici di veicoli e codici di errore possono apparire, e i client devono ignorare i campi che non riconoscono. I campi esistenti non cambiano mai significato o tipo, e nulla viene rimosso. Una modifica breaking comporta un nuovo percorso di versione, con v1 mantenuto in parallelo per almeno 12 mesi e avviso di deprecazione inviato via email a ogni workspace con una chiave attiva.
Campi della richiesta
| Campo | Tipo | Descrizione |
|---|---|---|
| vehicle | string | Un codice veicolo dalle tabelle seguenti. Oppure passa {customVehicleId} (un veicolo dalla flotta del tuo workspace). |
| objective | "ldm" | "pallets" | Minimizza i metri di carico o le posizioni pallet fatturabili. Predefinito "ldm". |
| units | "metric" | "imperial" | Come vengono interpretate e restituite le dimensioni e i pesi degli articoli: mm/kg (predefinito) o in/lb. Le posizioni nella risposta seguono le stesse unità. |
| items | array, required | Il manifesto del carico (campi sotto). |
| sandbox | boolean | Modalità test: risultato completo, nulla viene salvato. Disponibile su ogni piano. |
| name | string | Nome del piano (modalità produzione). |
| ldm_limit_m | number | Limita il carico ai primi N metri di carico (camion). |
| budget_ms | number | Budget di tempo per l'ottimizzazione (produzione; predefinito 30000, massimo 300000). |
| callback_url | https URL | Risultato inviato tramite POST al completamento del job, firmato (vedi Webhook). |
Campi degli articoli
| Campo | Tipo | Descrizione |
|---|---|---|
| length / width / height | number, required | Dimensioni dell'articolo, lette nelle unità della richiesta (mm per impostazione predefinita, pollici con "units": "imperial"). |
| weight | number, required | Peso dell'articolo (kg per impostazione predefinita, lb con "units": "imperial"). |
| quantity | integer, default 1 | Espande in unità individuali. |
| name | string, optional | Mostrato nel piano e nella risposta. |
| stackable | boolean, default true | Altro carico può essere appoggiato sopra questo articolo. |
| max_stack | number, optional | Peso massimo che questo articolo può sostenere sopra (stesse unità del peso). |
| fragile | boolean, default false | Gli articoli fragili vengono posizionati solo sugli strati superiori. |
| tiltable | boolean, default true | L'articolo può essere appoggiato su un lato. |
| destination_stop | integer, default 1 | Fermata di consegna per il carico LIFO multi-fermata. |
Codici veicolo
Generati dalle specifiche del motore live. I veicoli personalizzati dalla flotta del tuo workspace funzionano tramite custom_vehicle_id.
Camion e rimorchi
| Codice | Veicolo | Interno (L×L×A) | Carico utile |
|---|---|---|---|
| CURTAINSIDER_136 | 13.6m Curtainsider | 13.62 × 2.48 × 2.70 m | 24 t |
| BOX_136 | 13.6m Box Trailer | 13.62 × 2.46 × 2.68 m | 24 t |
| MEGA_136 | 13.6m Mega Trailer | 13.62 × 2.48 × 3.00 m | 22.5 t |
| MEGA_CURTAIN_136 | 13.6m Mega Curtainsider | 13.62 × 2.48 × 3.00 m | 22 t |
| FLATBED_136 | 13.6m Flatbed | 13.62 × 2.48 × 3.00 m | 25 t |
| REEFER_136 | 13.6m Reefer | 13.32 × 2.44 × 2.54 m | 22 t |
| CURTAINSIDER_75 | 7.5m City Curtainsider | 7.50 × 2.48 × 2.70 m | 12 t |
| SWAPBODY_715 | 7.15m Swap Body | 7.15 × 2.48 × 2.70 m | 12 t |
| SWAPBODY_745 | 7.45m Swap Body | 7.45 × 2.48 × 2.70 m | 12 t |
| DRY_VAN_53 | 53ft Dry Van | 16.15 × 2.59 × 2.74 m | 20 t |
| FLATBED_53 | 53ft Flatbed | 16.15 × 2.59 × 3.66 m | 22 t |
| REEFER_53 | 53ft Reefer | 15.85 × 2.46 × 2.59 m | 18.5 t |
| CURTAINSIDER_125_CN | 12.5m Curtainsider (CN) | 12.50 × 2.50 × 2.80 m | 27 t |
| FLATBED_40_IN | 40ft Flatbed (IN) | 12.19 × 2.50 × 3.50 m | 26 t |
| BOX_138_BR | 13.8m Box Van (BR) | 13.80 × 2.50 × 2.90 m | 28 t |
| CURTAINSIDER_12_ZA | 12m Curtainsider (ZA) | 12.00 × 2.48 × 2.70 m | 28 t |
Container
| Codice | Container | Interno (L×L×A) | Carico utile |
|---|---|---|---|
| 20DC | 20' Dry Container | 5.89 × 2.35 × 2.39 m | 21.727 t |
| 20HC | 20' High Cube | 5.89 × 2.35 × 2.70 m | 21.58 t |
| 40DC | 40' Dry Container | 12.03 × 2.35 × 2.39 m | 26.63 t |
| 40HC | 40' High Cube | 12.03 × 2.35 × 2.70 m | 26.58 t |
| 45HC | 45' High Cube | 13.56 × 2.35 × 2.70 m | 25.68 t |
| 20OT | 20' Open Top | 5.89 × 2.35 × 2.39 m | 21.64 t |
| 40OT | 40' Open Top | 12.03 × 2.35 × 2.39 m | 26.58 t |
| 20RF | 20' Reefer | 5.44 × 2.29 × 2.27 m | 21 t |
| 40RF | 40' Reefer | 11.58 × 2.29 × 2.21 m | 25.6 t |
| 40RFHC | 40' Reefer High Cube | 11.58 × 2.29 × 2.54 m | 25.88 t |
| 53DC | 53' Domestic | 16.15 × 2.59 × 2.90 m | 26 t |
Livelli di accesso e limiti
| Sandbox (ogni piano) | Produzione (Enterprise) | |
|---|---|---|
| Chiamate | 25 riuscite / mese | 5.000 operazioni riuscite / mese |
| Articoli per manifesto | 20 | 5,000 |
| Job simultanei | 1 | 10 |
| Budget di ottimizzazione | 10 s | 30 s predefinito, fino a 300 s |
| Persistenza | Niente viene salvato | Ogni packing crea un piano reale nel tuo workspace |
Vengono conteggiati solo gli imballaggi riusciti — le validazioni fallite e i job non completati non vengono addebitati sulla quota. Entrambi i contatori si azzerano il 1° del mese (UTC). I risultati della sandbox sono completi e non limitati — il limite riguarda l'accesso, non l'output. Hai bisogno di più di 5.000 imballaggi al mese? Contattaci per soluzioni su volumi elevati e prezzi OEM.
Webhook
Passa callback_url per ricevere il risultato tramite POST al completamento del job. Ogni callback include X-Hansatic-Signature: un HMAC-SHA256 del corpo grezzo con il segreto webhook della tua chiave (mostrato una sola volta alla creazione della chiave). Verificalo:
const crypto = require('crypto');
function verify(rawBody, signatureHeader, webhookSecret) {
const expected = crypto.createHmac('sha256', webhookSecret)
.update(rawBody).digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected), Buffer.from(signatureHeader));
}La consegna avviene con un singolo tentativo e timeout di 5s — status_url rimane sempre la fonte di verità.
Errori
Tutti gli errori sono { "error": { "code", "message", ... } } — codice leggibile dalla macchina, messaggio leggibile dall'utente e field quando un input specifico è errato.
| Codice | HTTP | Significato |
|---|---|---|
| invalid_key | 401 | Chiave API mancante, malformata o revocata. |
| invalid_json | 400 | Il corpo non è un JSON valido. |
| invalid_vehicle | 400 | Codice veicolo / {customVehicleId} sconosciuto. |
| invalid_items | 400 | Un campo dell'articolo è mancante, non numerico o fuori intervallo. |
| suspicious_dimensions | 400 | Tutte le dimensioni inferiori a 100 mm — probabilmente pollici inviati come mm. Passa "units": "imperial". |
| invalid_units | 400 | units deve essere "metric" o "imperial". |
| invalid_objective | 400 | objective deve essere "ldm" o "pallets". |
| invalid_callback | 400 | callback_url deve essere https. |
| too_many_items | 400 | Il manifesto supera il limite di articoli per il tuo livello di accesso. |
| upgrade_required | 402 | La modalità produzione richiede il piano Enterprise. Usa la sandbox o effettua l'upgrade. |
| sandbox_limit_reached | 402 | Limite mensile sandbox raggiunto — si ripristina il 1° del mese. |
| quota_exceeded | 402 | Quota mensile di produzione di 5.000 imballaggi esaurita — si azzera il 1°. Per volumi superiori: contattaci. |
| too_many_jobs | 429 | Limite di job simultanei raggiunto. Interroga i job in esecuzione e riprova. |
| not_found | 404 | Nessun job trovato per questo workspace. |
Domande, limiti più alti o un connettore di cui hai bisogno (Zapier, Excel, MCP)? hello@hansatic.com