Aller au contenu

DocumentationAPI v1

Intégrer Kolia.

Authentification, devis, expéditions, statuts, suivi, webhooks, erreurs : tout ce qu'il faut pour brancher la livraison à votre produit.

https://api.kolia.juali.pro/v1JSON · UTF-8 · montants en FCFA

Introduction

L'API Kolia est une API REST : des URL prévisibles, des corps JSON, des codes HTTP standard. Toutes les routes sont préfixées par /v1 et servies en HTTPS depuis https://api.kolia.juali.pro.

Les montants sont des entiers en FCFA, arrondis au multiple de 5. Les champs et les statuts sont en français : ils reprennent le vocabulaire du terrain.

Le débit est limité à 300 requêtes par minute par adresse IP ; au-delà, l'API répond 429. Chaque clé a en plus son propre quota journalier.

Environnement de test. Une clé de bac à sable (kolia_tk_) crée des expéditions isolées qui se livrent seules : ni livreur, ni paiement, ni message réels.

Démarrage

  1. Créez votre compte commerçant sur app.kolia.juali.pro, puis déclarez votre point de collecte.
  2. Créez une clé dans l'espace commerçant, rubrique Développeurs — réelle ou bac à sable. Elle n'est affichée qu'une seule fois.
  3. Demandez un devis, créez l'expédition dans les quinze minutes, suivez-la par webhook.
TerminalcURL
# Devis
curl -X POST https://api.kolia.juali.pro/v1/quotes \
  -H "X-API-Key: $KOLIA_API_KEY" -H "Content-Type: application/json" \
  -d '{"pickupPointId":"b3e1…","destLat":6.3525,"destLng":2.3675,"modePaiement":"PREPAYE"}'

# Expédition
curl -X POST https://api.kolia.juali.pro/v1/shipments \
  -H "X-API-Key: $KOLIA_API_KEY" -H "Content-Type: application/json" \
  -d '{"quoteId":"9f0c…","pickupPointId":"b3e1…","destinataireNom":"Afi H.",
       "destinataireTel":"+22997000000","destAdresse":"Fidjrossè","contenu":"Colis"}'

Authentification

Les codes de vérification des comptes (inscription, mot de passe oublié) partent par e-mail, ou sont générés par une application d'authentification (Google Authenticator, Microsoft Authenticator…). Aucun code n'est envoyé par SMS ni par WhatsApp. Un compte protégé par une application répond à POST /v1/auth/login par { "mfaRequis": true, "jetonMfa": "…" }, à compléter avec le code par POST /v1/auth/mfa/verifier.

MécanismeEn-têteUsage
Clé APIX-API-Key: kolia_sk_…Intégrations serveur. Agit au nom du compte commerçant qui l'a créée, dans la limite de ses portées (*, quotes, shipments, tracking…).
JetonAuthorization: Bearer …Réservé aux applications Kolia, après connexion. Pas destiné aux intégrations.

Ne placez jamais une clé dans du code exécuté chez l'utilisateur, navigateur ou application mobile. En cas de fuite, révoquez-la depuis votre espace : elle cesse de fonctionner immédiatement.

Tarifs publics

Deux routes publiques, sans clé API : de quoi afficher un prix indicatif avant qu'un client ait un compte. Elles ne créent aucun devis et n'exposent aucune donnée interne.

La grille

GET /v1/plateforme/tarifs — zones actives et règles de calcul. Limité à 60 requêtes par minute et par adresse IP.

Réponse · 200structure
{
  "devise": "FCFA",
  "zones": [{ "id": "…", "nom": "…", "ville": "…",
              "tarifBase": 0, "tarifParKm": 0, "tarifParKg": 0,
              "fraisCodPct": 0, "fraisCodFixe": 0 }],
  "niveauxDeService": [{ "code": "STANDARD", "multiplicateurPrix": 1, "facteurDelai": 1.8 }],
  "delai": { "priseEnChargeMin": 10, "minutesParKm": 4 },
  "assurance": { "tauxPct": 1.5, "primeMinimum": 100 },
  "devisValiditeMinutes": 15,
  "arrondiFcfa": 5,
  "regles": [{ "zoneIds": ["…"], "paiementALaLivraisonOuvert": true,
                "assuranceOuverte": true,
                "majoration": { "active": true, "plages": [ … ],
                                 "surchargeMeteo": { "active": false, "pct": 15 },
                                 "plafondPct": 30 } }]
}

Une plage de majoration porte libelle, jours (0 = dimanche, vide = tous les jours), debut, fin (heure locale), pct et zones (vide = toutes). La commission de la plateforme n'apparaît jamais dans cette réponse.

L'estimation

POST /v1/plateforme/estimation — même calcul que le devis, sur la grille de la zone : computePrice, niveau de service, majoration en vigueur, frais d'encaissement, délai estimé. Limité à 20 requêtes par minute et par adresse IP (429 au-delà).

ChampTypeDescription
zoneIdtexte, requisUne zone active de GET /v1/plateforme/tarifs.
distanceKmnombre, requisDe 0,5 à 50, à vol d'oiseau.
poidsKgnombreDe 0 à 100.
serviceLevelSTANDARD | EXPRESS | PREMIUMSTANDARD par défaut.
montantCodentierMontant à encaisser : ajoute les frais d'encaissement. 403 si le paiement à la livraison est fermé pour cette zone.
Réponse · 200montants illustratifs
{
  "prixCourse": 1210,
  "fraisCod": 600,
  "majorationPct": 0.1,
  "delaiEstimeMin": 34,
  "total": 1810,
  "estimation": true
}

Une estimation n'est pas un engagement : le devis ferme, valable quinze minutes, se demande avec POST /v1/quotes, sur la distance réelle entre votre point de collecte et le destinataire, remises comprises.

Devis

POST /v1/quotes calcule le prix, les frais et le délai estimé d'une course. Le devis reste valable quinze minutes et ne sert qu'une fois.

ChampTypeDescription
pickupPointIdtexte, requisVotre point de collecte (liste dans GET /v1/merchants/me).
destLat, destLngnombres, requisCoordonnées du destinataire.
modePaiementPREPAYE | CODCourse payée d'avance, ou paiement à la livraison.
montantCodentierMontant à encaisser. Obligatoire en COD.
serviceLevelSTANDARD | EXPRESS | PREMIUMSTANDARD par défaut.
poidsKgnombrePoids du colis.
codePromotexteCode de remise.
assurance, valeurDeclareebooléen, entierAssurance colis ; la valeur déclarée est alors obligatoire.
Réponse · 201montants illustratifs
{
  "id": "9f0c…",
  "distanceKm": 5.84,
  "serviceLevel": "EXPRESS",
  "prixCourse": 2150,
  "fraisCod": 525,
  "primeAssurance": 0,
  "remise": 0,
  "majorationPct": 0,
  "delaiEstimeMin": 34,
  "valideJusqua": "2026-09-15T14:17:00.000Z"
}

Une origine hors des zones couvertes est refusée (400). Le paiement à la livraison demande une identité validée et un compte de reversement vérifié ; sans cela, l'API répond 403 et le prépayé reste disponible.

Expéditions

POST /v1/shipments crée l'expédition à partir d'un devis valide. Le dispatch démarre aussitôt : aucun autre appel n'est nécessaire.

ChampTypeDescription
quoteIdtexte, requisDevis valide et non utilisé.
pickupPointIdtexte, requisLe point de collecte du devis.
destinataireNom, destinataireTeltextes, requisLe téléphone reçoit le lien de suivi et le code de remise.
destAdressetexte, requisAdresse lisible par le livreur.
destReperetexteRepère : « face à la pharmacie », « portail bleu »…
destinataireEmailtexteDernier recours si WhatsApp et SMS échouent.
contenutexte, requisNature du colis.
instructionstexteConsignes pour le livreur.
nombreColisentier, 1 à 50Colis remis au livreur pour cette expédition (1 par défaut).
creneauDebut, creneauFindates ISO 8601Livraison planifiée : créneau de 30 min à 12 h, dans les 14 jours. La recherche du livreur démarre à temps pour une remise dans le créneau.
payViaMOMO | WALLETCourse prépayée : mobile money (par défaut) ou solde du portefeuille.
brouillonbooléenEnregistre sans lancer le dispatch ; à confirmer ensuite.

Envoyez un en-tête Idempotency-Key (par exemple l'identifiant de la commande dans votre système) : si l'appel est rejoué — coupure réseau, relance automatique —, l'API renvoie l'expédition déjà créée au lieu d'en créer une seconde. Sans cet en-tête, un devis ne sert qu'une fois : un second appel est refusé.

La réponse porte la reference (KOL-2026-XXXXX) et le statut. La page de suivi du destinataire est https://api.kolia.juali.pro/t/{reference}.

Les autres routes

RouteRôle
GET /v1/shipmentsVos expéditions, filtrables par statut, paginées (page, limit).
GET /v1/shipments/:idDétail : statut, livreur, chronologie, preuve de livraison.
PATCH /v1/shipments/:idModifier, tant que l'expédition est en brouillon ou en attente.
POST /v1/shipments/:id/confirmConfirmer un brouillon et lancer le dispatch.
POST /v1/shipments/:id/cancelAnnuler. Avant l'enlèvement, le prépayé est rendu.
POST /v1/shipments/:id/retryNouvelle tentative après un échec, dans la limite des tentatives.
POST /v1/shipments/:id/renvoyer-codeRenvoie le code de remise au téléphone du destinataire, par exemple quand votre client ne l'a pas reçu. Possible une fois le colis récupéré (PRISE_EN_CHARGE, EN_TRANSIT), 3 fois par heure au plus par expédition (429 au-delà). Réponse : { "message": "Code renvoyé au destinataire." }.
POST /v1/shipments/bulkImport en masse : jusqu'à 100 lignes, résultat ligne par ligne. Chaque referenceExterne rend la ligne rejouable sans doublon.
GET /v1/merchants/me/destinataires?q=Carnet des destinataires déjà livrés, pour pré-remplir une expédition.
GET /v1/disputes/:idDossier d'un litige : échanges, pièces jointes, historique des statuts.
POST /v1/disputes/:id/messagesÉcrire dans le dossier, avec une pièce jointe téléversée (pieceJointeUrl).

Code de remise

Le code à quatre chiffres est envoyé au destinataire dès que le livreur récupère le colis, et lui seul le connaît : le livreur ne le voit pas, et l'expéditeur non plus.

Les réponses destinées à l'expéditeur — GET /v1/shipments, GET /v1/shipments/:id, création, modification, annulation — ne contiennent plus le code. Le booléen aCodeRemise indique seulement si l'expédition en a un.

Si votre client dit ne pas l'avoir reçu, appelez POST /v1/shipments/:id/renvoyer-code : la plateforme le renvoie au téléphone du destinataire. Le renvoi ne communique jamais le code à l'expéditeur. Il n'est possible qu'en PRISE_EN_CHARGE ou EN_TRANSIT, au plus 3 fois par heure et par expédition.

POST /v1/shipments/:id/renvoyer-code
curl -X POST https://api.kolia.juali.pro/v1/shipments/9f0c…/renvoyer-code \
  -H "X-API-Key: $KOLIA_API_KEY"

→ 200 { "message": "Code renvoyé au destinataire." }

À la remise, cinq essais de saisie sont acceptés ; au-delà, la saisie est bloquée et une alerte est levée.

Bac à sable

Créez une clé de bac à sable dans l'espace commerçant (rubrique Développeurs → type « Bac à sable »), sans rien demander à personne. Elle commence par kolia_tk_ et s'utilise exactement comme une clé réelle, sur les mêmes routes.

  • Les expéditions créées avec elle sont isolées : invisibles de l'espace commerçant et de vos clés réelles, et inversement.
  • Aucun livreur n'est sollicité, aucun paiement n'est débité, aucun message n'est envoyé au destinataire. Le paiement à la livraison se teste sans vérification d'identité.
  • La livraison est simulée : l'expédition passe d'elle-même par tous les statuts jusqu'à LIVREE, en une minute environ. Vos webhooks reçoivent chaque événement, avec "sandbox": true dans donnees.

Passez en production en remplaçant la clé kolia_tk_ par une clé kolia_sk_ : rien d'autre ne change.

Statuts

Chaque expédition suit une machine à états stricte. Une transition non prévue est refusée avec 400 et le message Transition interdite.

StatutSignificationÉvénement
BROUILLONEnregistrée, dispatch non lancé—
EN_ATTENTERecherche d'un livreurshipment.created
AFFECTEEOffre envoyée à un livreurshipment.assigned
ACCEPTEELivreur en route vers la collecteshipment.accepted
EN_RAMASSAGELivreur au point de collecteshipment.pickup_started
PRISE_EN_CHARGEColis récupéréshipment.picked_up
EN_TRANSITEn route vers le destinataireshipment.in_transit
LIVREERemis, preuve enregistréeshipment.delivered
ECHECRemise impossible : absent, refus…shipment.failed
ANNULEEAnnuléeshipment.cancelled
EXPIREEAucun livreur trouvé à tempsshipment.expired
LITIGELitige ouvert, instruit par l'équipeshipment.disputed
CLOTUREERéglée et closeshipment.closed

Suivi en direct

Chaque expédition a sa page publique, https://api.kolia.juali.pro/t/{reference} : carte en direct, chronologie, notation à la livraison. Pour construire votre propre interface :

  • GET /v1/tracking/{reference} — l'état public de l'expédition, sans clé, avec l'heure d'arrivée estimée (eta) et le créneau planifié. La même estimation figure dans GET /v1/shipments/:id.
  • WebSocket (Socket.IO), espace /tracking — abonnez-vous à une référence, recevez position, statut et message.
Navigateursocket.io-client
import { io } from 'socket.io-client';

const socket = io('https://api.kolia.juali.pro/tracking');
socket.emit('subscribe', { reference: 'KOL-2026-7Q4MX' });

socket.on('position', ({ lat, lng }) => deplacerLivreur(lat, lng));
socket.on('statut', ({ statut }) => afficherStatut(statut));

Webhooks

Souscrivez une URL publique en HTTPS aux événements qui vous intéressent, ou à * pour tout recevoir. La réponse contient le secret de signature whsec_… : conservez-le.

POST /v1/developer/webhooks
{
  "url": "https://votre-boutique.bj/webhooks/kolia",
  "events": ["shipment.delivered", "shipment.failed", "payment.settled"]
}

Événements : les douze shipment.* du tableau des statuts, ainsi que payment.settled, payment.refunded et payment.reversed.

Ce que reçoit votre serveur

POST votre URL
X-Kolia-Event: shipment.delivered
X-Kolia-Delivery: 5d2a…
X-Kolia-Signature: sha256=3f9c…

{
  "type": "shipment.delivered",
  "reference": "KOL-2026-7Q4MX",
  "donnees": { … },
  "horodatage": "2026-09-15T14:26:41.000Z"
}

Répondez par un code 2xx en moins de dix secondes. Sinon, la livraison est rejouée après 1 min, 5 min, 30 min puis 2 h, et abandonnée après la cinquième tentative. Le journal de chaque webhook est dans GET /v1/developer/webhooks/:id/deliveries.

Un même événement peut arriver plus d'une fois : dédoublonnez sur X-Kolia-Delivery.

Vérifier la signature

X-Kolia-Signature vaut sha256= suivi du HMAC-SHA256 du corps brut, calculé avec votre secret. Comparez en temps constant, avant tout traitement.

import express from 'express';
import { verifyWebhookSignature } from 'kolia-sdk';

app.post('/webhooks/kolia', express.raw({ type: 'application/json' }), (req, res) => {
  const brut = req.body.toString();
  if (!verifyWebhookSignature(process.env.KOLIA_WEBHOOK_SECRET, brut, req.get('X-Kolia-Signature'))) {
    return res.status(401).end();
  }
  const evenement = JSON.parse(brut);
  // evenement.type, evenement.reference, evenement.donnees
  res.status(200).end();
});

Erreurs

Toutes les erreurs ont la même forme, avec un message en français prêt à afficher. details n'apparaît que pour les erreurs de validation.

{
  "code": "BAD_REQUEST",
  "message": "Requête invalide",
  "details": ["destLat must be a number …"]
}
HTTPCasQue faire
400Corps invalide ou champ inconnu, devis expiré ou déjà utilisé, transition interdite.Corriger la requête ; relire le statut courant.
401Clé ou jeton absent, invalide ou révoqué.Vérifier l'en-tête d'authentification.
403Action non autorisée : paiement à la livraison non débloqué, portée de clé insuffisante.Le message indique la condition à remplir.
404Ressource introuvable, ou hors de votre compte.Vérifier l'identifiant.
429Débit ou quota dépassé.Réessayer plus tard, avec un délai croissant.
5xxErreur du serveur.Réessayer ; consulter la page de statut.

SDK

Deux clients officiels, JavaScript (Node 18 et plus, zéro dépendance) et PHP (une classe, cURL natif), exposent les mêmes méthodes.

MéthodeRoute
createQuote(params)POST /v1/quotes
createShipment(params)POST /v1/shipments
getShipment(id)GET /v1/shipments/:id
cancelShipment(id)POST /v1/shipments/:id/cancel
resendDeliveryCode(id)POST /v1/shipments/:id/renvoyer-code
listShipments()GET /v1/shipments
track(reference)GET /v1/tracking/:reference
getFinances()GET /v1/merchants/me/finances
listPickupPoints()GET /v1/merchants/me
verifyWebhookSignature(secret, brut, signature)Vérification locale, en temps constant.

Les SDK ne sont pas publiés sur npm ni sur Packagist : ils vous sont remis, avec l'extension WooCommerce, sur demande à hello@juali.pro, objet « Développeurs ».