Documentation API
L'API REST de Cargamento permet à une autre application (ERP, logiciel d'entrepôt, TMS, application mobile) de calculer une palettisation, un plan de chargement et un ordre de préparation au quai, et d'enregistrer des plans consultables par un lien.
- Base :
https://cargamento.vozit.dev/api/v1 - Format : JSON en UTF-8, en entrée comme en sortie.
- Contrat OpenAPI 3 :
https://cargamento.vozit.dev/api/v1/openapi.json - Unités : centimètres, kilogrammes ; volumes en m³ sauf mention contraire.
Le calcul de l'API est exactement celui de l'outil en ligne : mêmes règles, mêmes résultats, mêmes messages.
Authentification
Chaque application reçoit une clé, créée par un administrateur dans Administration › Clés d'API. Envoyez-la dans l'en-tête Authorization :
Authorization: Bearer cg_0123456789abcdef0123456789abcdef01234567
L'en-tête X-Api-Key: cg_… est accepté aussi. La clé ne doit jamais être exposée dans du code exécuté par un navigateur public : appelez l'API depuis votre serveur.
Limites
| Limite | Valeur |
|---|---|
| Appels par clé et par minute | 120 (au-delà : 429, en-tête Retry-After: 60) |
| Taille du corps de requête | 2 Mo |
| Lignes par plan | 500 |
| Palettes unitaires par calcul | 2 000 |
Erreurs
Toute erreur renvoie un objet error :
{
"error": {
"code": "validation_error",
"message": "Saisie invalide.",
"details": [
{ "field": "carton.length", "message": "Doit être supérieur à 0." },
{ "field": "lines[2].palletCode", "message": "Type de palette inconnu « xyz »." }
]
}
}
| HTTP | code | Cause |
|---|---|---|
| 400 | invalid_json | Corps absent ou JSON illisible. |
| 401 | unauthorized | Clé absente, invalide ou révoquée. |
| 404 | not_found | Plan ou route inconnus. |
| 405 | method_not_allowed | Méthode non prévue sur cette route. |
| 413 | too_large | Corps supérieur à 2 Mo. |
| 422 | validation_error | Saisie invalide ; details liste chaque champ fautif. |
| 429 | rate_limited | Trop d'appels. |
| 500 | server_error | Erreur interne (journalisée côté serveur). |
Erreur de saisie et impossibilité métier sont distinctes : un colis plus grand que la palette n'est pas une saisie invalide. POST /palletize répond alors 200 avec result.ok = false et un result.error lisible.
Saisie des valeurs
- Les nombres peuvent être envoyés en JSON (
12.5) ou en texte ("12,5","12.5"). - Les booléens acceptent
true/false,1/0,"oui"/"non". - Les codes de référentiel (
palletCode,vehicleCode) sont résolus en cotes ; les cotes envoyées explicitement priment sur celles du référentiel.
Points d'accès
| Méthode | Route | Rôle |
|---|---|---|
| GET | /api/v1 | Description de l'API (sans clé). |
| GET | /ping | Vérifie la clé. |
| GET | /referentials | Palettes, véhicules et seuils d'alerte en vigueur. |
| POST | /palletize | Palettisation d'une référence de colis. |
| POST | /load-plan | Plan de chargement et plan de quai, sans enregistrement. |
| POST | /plans | Calcule et enregistre un plan, renvoie son lien. |
| GET | /plans | Liste des plans créés par la clé. |
| GET | /plans/{id} | Lit un plan, résultat recalculé. |
| PUT | /plans/{id} | Remplace les saisies d'un plan. |
| DELETE | /plans/{id} | Supprime un plan. |
GET /ping
curl -s https://cargamento.vozit.dev/api/v1/ping -H "Authorization: Bearer $CLE"
{ "ok": true, "key": "ERP Cyprès", "version": "v1", "time": "2026-10-19T14:02:11+02:00" }
GET /referentials
{
"pallets": [
{ "code": "eur", "label": "EUR / EPAL 120 × 80", "shortLabel": "EUR 120×80", "length": 120, "width": 80, "deckHeight": 14.4, "tare": 25, "active": true }
],
"vehicles": [
{ "code": "semi", "label": "Semi-remorque standard (13,60 m)", "length": 1360, "width": 248, "height": 270, "payload": 24000, "active": true }
],
"thresholds": { "cogMin": 0.3, "cogMax": 0.65, "lateral": 0.05 }
}
Codes livrés : palettes eur, ind, demi, quart, us ; véhicules semi, mega, p19, p12, vl20, c20, c40, c40hc. L'administrateur peut en ajouter ou les modifier : lisez cette route plutôt que de figer les codes.
POST /palletize
Corps :
| Champ | Type | Oblig. | Règle |
|---|---|---|---|
ref | texte | non | Référence du colis (120 car.). Défaut « Colis ». |
carton.length, carton.width, carton.height | nombre | oui | > 0, cm. height = sens vertical. |
carton.weight | nombre | non | ≥ 0, kg. 0 = limite de poids ignorée. |
carton.canTilt | booléen | non | Colis couchable. Défaut false. |
palletCode | texte | l’un des deux | Code du référentiel. |
pallet | objet | l’un des deux | { length, width, deckHeight, tare }, surcharge ou remplace palletCode. |
maxHeight | nombre | oui | Hauteur max palette comprise, cm. |
maxWeight | nombre | oui | Poids max palette comprise, kg. |
overhang | nombre | non | Débord autorisé par côté, cm. Défaut 0. |
orderQty | entier | non | Colis commandés. Défaut 0. |
stop | entier | non | Arrêt de livraison, ≥ 1. Défaut 1. |
stackable | booléen | non | Palettes gerbables. Défaut false. |
Il faut palletCode ou pallet (ou les deux).
curl -s https://cargamento.vozit.dev/api/v1/palletize \
-H "Authorization: Bearer $CLE" -H "Content-Type: application/json" \
-d '{"ref":"Carton 40x30x25","carton":{"length":40,"width":30,"height":25,"weight":8},
"palletCode":"eur","maxHeight":180,"maxWeight":800,"orderQty":500,"stop":2,"stackable":true}'
Réponse (le tableau layout est abrégé) :
{
"result": {
"ok": true,
"ref": "Carton 40x30x25",
"orientation": "upright",
"orientationLabel": "debout",
"footprint": { "length": 40, "width": 30 },
"cartonHeight": 25,
"perLayer": 8,
"layers": 6,
"total": 48,
"pattern": "columns",
"layout": [ { "x": 0, "y": 0, "length": 30, "width": 40 }, "…" ],
"usable": { "length": 120, "width": 80, "height": 165.6, "weight": 775 },
"overhang": 0,
"palletHeight": 164.4,
"grossWeight": 409,
"fillVolume": 0.905797,
"fillArea": 1,
"palletVolume": 1.57824,
"cartonsVolume": 1.44,
"limit": "height",
"order": {
"qty": 500, "fullPallets": 10, "pallets": 11,
"partial": { "cartons": 20, "layers": 3, "height": 89.4, "weight": 185 }
},
"loadLines": [
{ "name": "Carton 40x30x25", "palletCode": "eur", "length": 120, "width": 80, "stackable": true, "stop": 2, "height": 164.4, "weight": 409, "qty": 10 },
{ "name": "Carton 40x30x25 (incomplète)", "palletCode": "eur", "length": 120, "width": 80, "stackable": true, "stop": 2, "height": 89.4, "weight": 185, "qty": 1 }
]
},
"input": { "…": "saisie normalisée" }
}
| Champ du résultat | Sens |
|---|---|
orientation | upright (debout), side (couché sur le côté), end (couché sur le bout). |
perLayer, layers, total | Colis par couche, nombre de couches, colis par palette. |
pattern | columns ou mixed (deux sens de colis : alterner les couches en miroir). |
layout | Rectangles de la couche vue de dessus, origine au coin de la surface utile (débord compris). |
palletHeight, grossWeight | Hauteur (cm) et poids brut (kg) d'une palette complète. |
fillVolume, fillArea | Remplissage, ratio de 0 à 1 (peut dépasser 1 avec un débord). |
limit | height ou weight : ce qui arrête l'empilement. |
order | null sans quantité ; sinon palettes complètes et palette incomplète. |
loadLines | Lignes prêtes à envoyer telles quelles dans lines de /load-plan. |
Cas d'impossibilité (200) :
{ "result": { "ok": false, "error": { "code": "tooBig", "message": "Le colis est plus grand que la palette." } } }
Codes possibles : dimensions, pallet, weight, maxHeight, maxWeight, tooBig, tooHigh, tooHeavy.
POST /load-plan
Corps :
| Champ | Type | Oblig. | Règle |
|---|---|---|---|
vehicleCode | texte | l’un des deux | Code du référentiel. |
vehicle | objet | l’un des deux | { length, width, height, payload, label? }, cotes intérieures utiles. Surcharge vehicleCode. |
gap | nombre | non | Jeu entre palettes, cm. Défaut 0. |
maxStack | entier | non | Niveaux de gerbage, 1 à 4. Défaut 2. |
thresholds | objet | non | { cogMin, cogMax, lateral } en ratios (0,30 = 30 %). Défaut : seuils de l'administration. |
lines | tableau | oui | Lignes de palettes ou commandes, voir ci-dessous. 500 max. |
Il faut vehicleCode ou vehicle (ou les deux).
Ligne de palettes (palettes déjà constituées) :
| Champ | Type | Oblig. | Règle |
|---|---|---|---|
id | texte | non | Votre identifiant (60 car.), repris dans les résultats. Défaut L1, L2… |
name | texte | non | Désignation affichée. |
palletCode | texte | non | Complète length et width s'ils sont absents. |
length, width | nombre | sans palletCode | Emprise d'une palette chargée (débord compris), cm. |
height | nombre | oui | Hauteur d'une palette chargée, cm. |
weight | nombre | non | Poids d'une palette chargée, kg. |
qty | entier | non | Nombre de palettes. Défaut 1. |
stackable | booléen | non | Défaut false. |
stop | entier | non | Arrêt, 1 = premier livré. Défaut 1. |
Ligne de commande : une ligne qui contient carton est une commande de colis. Elle accepte tous les champs de /palletize (plus id et name), est palettisée puis remplacée par ses palettes complètes (identifiant id) et sa palette incomplète (identifiant id-partial). Le résumé de chaque palettisation est renvoyé dans orders.
curl -s https://cargamento.vozit.dev/api/v1/load-plan \
-H "Authorization: Bearer $CLE" -H "Content-Type: application/json" \
-d '{
"vehicleCode": "p19",
"maxStack": 2,
"lines": [
{ "id": "CMD-1", "name": "Commande Dupont", "stop": 2,
"carton": { "length": 40, "width": 30, "height": 25, "weight": 8 },
"palletCode": "eur", "maxHeight": 180, "maxWeight": 800, "orderQty": 500 },
{ "id": "P-2", "name": "Palettes prêtes", "palletCode": "demi",
"height": 90, "weight": 150, "qty": 4, "stackable": true, "stop": 1 }
]
}'
Réponse (tableaux abrégés au premier élément) :
{
"plan": {
"vehicle": { "code": "p19", "label": "Porteur 19 t (7,50 m)", "length": 750, "width": 248, "height": 260, "payload": 10000 },
"gap": 0,
"maxStack": 2,
"thresholds": { "cogMin": 0.3, "cogMax": 0.65, "lateral": 0.05 },
"lines": [ { "index": 0, "id": "CMD-1", "name": "Commande Dupont", "stop": 2 } ],
"rows": [ { "row": 1, "x": 0, "depth": 120, "stop": 2, "steps": [1, 2, 3] } ],
"placements": [
{
"step": 1, "row": 1, "x": 0, "y": 4, "alongL": 120, "acrossW": 80,
"side": "Gauche", "crosswise": false,
"stack": {
"units": ["CMD-1"], "levels": 1, "length": 120, "width": 80,
"height": 164.4, "weight": 409, "volume": 1578240, "stop": 2,
"baseLineIndex": 0, "topLineIndex": 0, "mixed": false, "content": "Commande Dupont"
}
}
],
"overflow": [],
"indicators": {
"palletsRequested": 15, "palletsLoaded": 15, "floorSpots": 13, "rows": 5,
"loadingMeters": 500, "remainingLength": 250,
"weight": 4875, "payload": 10000, "volume": 18.3686, "boxVolume": 48.36,
"cog": { "x": 242.5, "y": 124, "xRatio": 0.323333, "yOffset": 0 },
"gauges": {
"weight": { "ratio": 0.4875, "level": "ok" },
"loadingMeters": { "ratio": 0.666667, "level": "ok" },
"floor": { "ratio": 0.619355, "level": "ok" },
"volume": { "ratio": 0.379831, "level": "ok" }
}
},
"alerts": [ { "level": "ok", "code": "ok", "message": "Tout est chargé, dans les limites de poids et de dimensions." } ],
"dock": {
"width": 248, "depth": 500, "area": 12.4,
"rows": [ { "row": 1, "y": 0, "depth": 120 } ],
"spots": [ { "step": 1, "row": 1, "x": 4, "y": 0, "width": 80, "depth": 120, "levels": 1, "lineIndex": 0 } ],
"sequence": [ { "step": 1, "row": 1, "side": "Gauche", "content": "Commande Dupont", "crosswise": false, "height": 164.4, "weight": 409, "stop": 2, "lineIndex": 0 } ]
}
},
"orders": [
{ "lineIndex": 0, "id": "CMD-1", "ref": "Colis", "perLayer": 8, "layers": 6, "perPallet": 48,
"palletHeight": 164.4, "grossWeight": 409, "limit": "height",
"order": { "qty": 500, "fullPallets": 10, "pallets": 11, "partial": { "cartons": 20, "layers": 3, "height": 89.4, "weight": 185 } } }
],
"input": { "vehicle": { "…": "…" }, "lines": [ { "id": "CMD-1", "…": "…" }, { "id": "CMD-1-partial", "…": "…" }, { "id": "P-2", "…": "…" } ] }
}
Repère : origine à l'avant gauche du plancher (côté cabine, côté conducteur). x le long du camion vers les portes, y en travers vers la droite.
| Champ | Sens |
|---|---|
placements[].step | Numéro d'ordre de chargement (1 = premier chargé, au fond côté cabine). |
placements[].x, y, alongL, acrossW | Emprise au sol de la pile, cm. |
placements[].side | Gauche, Centre ou Droite (tiers de largeur ; Centre si seule sur sa rangée). |
placements[].crosswise | true si la palette est tournée en travers. |
mode | auto pour un plan calculé par l'API. |
stack.units | Identifiants des lignes, de la palette du bas à celle du haut. |
stack.unitKeys | Identifiant de chaque palette unitaire, « id de ligne # rang » (ex. CMD-1#3), du bas vers le haut. |
placements[].issues | Contrôles de disposition ; toujours vide pour un plan calculé (rempli dans le chargement manuel de l'outil). |
stack.volume | Volume des palettes de la pile, en cm³. |
stack.mixed | La pile mélange deux lignes. |
overflow[] | Palettes non chargées : lineId, lineName, unitKey, reason (height, width, space), reasonLabel. |
indicators.loadingMeters | Fin de la dernière rangée, cm (mètres de plancher × 100). |
indicators.cog | Centre de gravité (cm), xRatio = position en part de la longueur, yOffset = écart au milieu (négatif = gauche). |
indicators.gauges.*.level | ok, warn (orange) ou bad (rouge), seuils de la fiche §6.1. |
alerts[].level | blocking, warning ou ok. Codes : notLoaded, overweight, cogLength, cogLateral, ok. |
dock | Plan de quai : plancher pivoté, y = distance depuis la porte de quai. sequence = tableau d'ordre de chargement. |
POST /plans
Même corps que /load-plan, avec deux champs facultatifs : name (nom affiché) et reference (votre numéro de tournée ou de commande, 190 car., filtrable). Réponse 201 :
{
"id": "Hk3mQ7pXa2Lr9TbN",
"name": "Tournée Lyon du 21/10",
"reference": "TRN-2026-1021",
"url": "https://cargamento.vozit.dev/p/Hk3mQ7pXa2Lr9TbN",
"createdAt": "2026-10-19T14:05:00+02:00",
"updatedAt": "2026-10-19T14:05:00+02:00",
"input": { "…": "saisie normalisée enregistrée" },
"plan": { "…": "même structure que /load-plan" },
"orders": []
}
url ouvre l'outil sur l'écran Camion avec ce plan : à envoyer au chef de quai ou à afficher dans votre application. Il faut un compte Cargamento pour l'ouvrir : sans session, la personne passe par la page de connexion puis arrive sur le plan. L'identifiant est aléatoire (16 caractères) et la page n'est pas indexée.
Seules les saisies sont enregistrées ; le résultat est recalculé à chaque lecture. Le véhicule est enregistré avec ses cotes : modifier ensuite le référentiel ne change pas le plan.
GET /plans
Paramètres : limit (1 à 200, défaut 50), offset, reference (égalité exacte). Ne renvoie que les plans créés par la clé appelante.
{ "data": [ { "id": "Hk3mQ7pXa2Lr9TbN", "name": "Tournée Lyon du 21/10", "reference": "TRN-2026-1021", "url": "…", "createdAt": "…", "updatedAt": "…" } ], "limit": 50, "offset": 0 }
GET /plans/{id}, PUT /plans/{id}, DELETE /plans/{id}
GETrenvoie la même structure que la création.PUTprend le même corps quePOST /planset remplace les saisies ;nameetreferenceabsents sont conservés.DELETErenvoie{ "deleted": true, "id": "…" }; le lien partagé cesse de fonctionner.
Un plan créé par une autre clé répond 404.
Recette d'intégration type
- Au démarrage,
GET /referentialspour proposer les véhicules et palettes à l'utilisateur. - À la préparation d'une tournée, envoyez
POST /plansavec une ligne de commande par référence (champcarton) ou une ligne de palettes par lot déjà constitué,stop= rang de livraison,reference= votre numéro de tournée. - Si
plan.alertscontient une alerteblocking, proposez un autre véhicule ou de scinder la tournée. - Enregistrez
ideturl; affichez le lien au chef de quai. - Si la tournée change,
PUT /plans/{id}.
Exemple en PHP
$ch = curl_init('https://cargamento.vozit.dev/api/v1/load-plan');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('CARGAMENTO_KEY'), 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode([
'vehicleCode' => 'semi',
'lines' => [['id' => 'A', 'palletCode' => 'eur', 'height' => 150, 'weight' => 500, 'qty' => 33]],
]),
]);
$res = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
echo $res['plan']['indicators']['loadingMeters'] / 100, " m de plancher\n"; // 13.2
Exemple en JavaScript (Node 18+, côté serveur)
const res = await fetch('https://cargamento.vozit.dev/api/v1/palletize', {
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CARGAMENTO_KEY}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ carton: { length: 40, width: 30, height: 25, weight: 8 }, palletCode: 'eur', maxHeight: 180, maxWeight: 800 }),
});
const { result } = await res.json();
console.log(result.ok ? `${result.total} colis par palette` : result.error.message);
Versions
La version v1 est stable : on n'y retire ni ne renomme aucun champ. Les ajouts (nouveaux champs, nouvelles routes) sont annoncés dans le journal des versions. Une rupture ferait l'objet d'une v2 en parallèle.