Documentation publique · v1

API Partenaires Pikoli

Tout ce qu'il vous faut pour intégrer Pikoli dans votre tunnel de vente : créer des commandes, suivre les livraisons, recevoir les webhooks de statut. Cette page documente uniquement les endpoints accessibles aux partenaires marchands B2B.

Base URL : https://api.pikoli.fr/api/v1 Stable

Vue d'ensemble

L'API Pikoli est une API REST classique. Toutes les requêtes utilisent JSON, sauf mention contraire. Toutes les réponses contiennent un objet JSON, ou un corps d'erreur standardisé décrit dans la section Codes d'erreur.

Format JSON · UTF-8 · ISO-8601 (UTC)
Auth Bearer API key par requête
Idempotence Header Idempotency-Key sur POST /orders
Versioning Path /api/v1/... stable

Authentification

Chaque partenaire reçoit deux clés à la création de son compte API : pk_test_… (sandbox) et pk_live_… (production). Passez la clé dans le header Authorization: Bearer de chaque requête.

curl https://api.pikoli.fr/api/v1/partners/quote \
  -H "Authorization: Bearer pk_live_••••••••••" \
  -H "Content-Type: application/json"
Sécurité. La clé pk_live_* donne l'accès à votre compte facturable. Ne la commitez jamais dans git et ne l'exposez pas côté navigateur — utilisez-la depuis votre serveur uniquement.

Devis · POST /partners/quote

Calcule le prix d'une livraison sans rien créer. Idéal pour afficher le coût dans votre checkout avant que le client ne valide.

Requête

POST https://api.pikoli.fr/api/v1/partners/quote
Authorization: Bearer pk_live_••••
Content-Type: application/json

{
  "pickup_quartier_id":  "a4f3c2d8-...",
  "dropoff_quartier_id": "b1e9f0aa-...",
  "weight_kg": 2.5,
  "mode": "direct"
}

Réponse 200 OK

{
  "price_cents": 250000,
  "distance_m": 4830,
  "duration_s": 720,
  "weight_surcharge_cents": 0,
  "mode_multiplier": 1.0,
  "branch": "intra_arrondissement",
  "escale_count": 0
}
ChampTypeDescription
pickup_quartier_idUUIDQuartier de retrait (cf. référentiel)
dropoff_quartier_idUUIDQuartier de livraison
weight_kgnumberPoids estimé en kg (max 30)
modeenumdirect · relay · express

Créer une commande · POST /partners/orders

Crée et planifie une livraison. La commande est facturée mensuellement (vous ne payez pas à la création). Le champ Idempotency-Key est fortement recommandé pour protéger contre les retries.

POST https://api.pikoli.fr/api/v1/partners/orders
Authorization: Bearer pk_live_••••
Idempotency-Key: order-2026-05-21-acme-4711

{
  "pickup": {
    "address": "Marché Central, N'Djamena",
    "lat": 12.1098, "lng": 15.0461,
    "quartier_id": "a4f3..."
  },
  "dropoff": {
    "address": "Quartier Klemat, lot 14",
    "lat": 12.1180, "lng": 15.0530,
    "quartier_id": "b1e9..."
  },
  "recipient": {
    "name":  "Awa Mahamat",
    "phone": "+23566010203"
  },
  "weight_kg": 2.5,
  "mode": "direct",
  "cod_amount_cents": 1500000,
  "notes": "Sonner deux fois"
}

Réponse 201 Created

{
  "nc": "NC-A92F",
  "tracking_url": "https://app.pikoli.fr/track/NC-A92F",
  "price_cents": 250000,
  "payment_required": false,
  "created_at": "2026-05-21T08:42:11Z"
}
NC = Numéro de Course. Identifiant court communiqué au destinataire (sur étiquette, SMS, QR code). C'est l'ID que vos clients verront.

Lister les commandes · GET /partners/orders

Liste paginée par curseur (pas d'offset). Filtrable par statut et par plage de dates.

GET https://api.pikoli.fr/api/v1/partners/orders?status=in_transit&limit=50
Authorization: Bearer pk_live_••••
ParamètreDescription
statuscreated, assigned, in_transit, delivered, cancelled
cursorCurseur de pagination renvoyé dans next_cursor
limit1 à 100 (défaut 25)
created_afterISO-8601, inclusif
created_beforeISO-8601, exclusif

Détail d'une commande · GET /partners/orders/{nc}

GET https://api.pikoli.fr/api/v1/partners/orders/NC-A92F
Authorization: Bearer pk_live_••••

Réponse : statut courant, historique d'événements, position du livreur (si en cours), preuve de livraison (photo + signature) une fois la course terminée.

Référentiel quartiers · GET /partners/quartiers

Liste tous les quartiers couverts par Pikoli, avec leur UUID, leur arrondissement et leur centroïde. À mettre en cache (mise à jour rare). Aujourd'hui 48 quartiers actifs à N'Djamena répartis sur 10 arrondissements.

GET https://api.pikoli.fr/api/v1/partners/quartiers
Authorization: Bearer pk_live_••••

# Réponse (extrait)
[
  {
    "id": "a4f3c2d8-...",
    "name": "Klemat",
    "arrondissement": { "id": "9b1a...", "name": "1er", "city": "N'Djamena" },
    "centroid": { "lat": 12.1180, "lng": 15.0530 }
  },
  ...
]

Webhooks

Pikoli pousse les événements de cycle de vie vers une URL HTTPS que vous configurez depuis votre portail partenaire. Chaque payload est signé HMAC-SHA256 avec votre webhook_secret dans le header X-Pikoli-Signature.

Événements émis

ÉvénementQuand
order.createdCréation d'une commande
order.assignedUn livreur accepte la course
order.in_transitLe retrait a eu lieu
order.deliveredRemise + preuve photo signée
order.cancelledAnnulation client / livreur / admin
invoice.generatedFacture mensuelle disponible

Payload (exemple)

POST https://votre-domaine.com/pikoli-webhook
X-Pikoli-Signature: sha256=ab12cd34…
Content-Type: application/json

{
  "id": "evt_8f3a...",
  "type": "order.delivered",
  "created_at": "2026-05-21T09:15:02Z",
  "data": {
    "nc": "NC-A92F",
    "delivered_at": "2026-05-21T09:14:45Z",
    "proof_url": "https://cdn.pikoli.fr/proofs/...",
    "signature_url": "https://cdn.pikoli.fr/signatures/..."
  }
}

Vérification de la signature

import hmac, hashlib

def verify(signature_header: str, body: bytes, secret: str) -> bool:
    expected = hmac.new(
        secret.encode(),
        body,
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(
        signature_header.removeprefix("sha256="),
        expected,
    )

Codes d'erreur

Toutes les erreurs suivent le format suivant :

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Request validation failed",
    "details": { ... }
  }
}
HTTPCodeCause
400VALIDATION_ERRORBody / params invalides
401INVALID_API_KEYClé manquante, invalide ou révoquée
403FORBIDDENAccès non autorisé à cette ressource
404NOT_FOUNDRessource inexistante
409CONFLICTIdempotency-Key réutilisée avec un body différent
422VALIDATION_ERRORSchéma incorrect (champ manquant, type, taille)
429RATE_LIMITEDQuota dépassé — voir header Retry-After
500INTERNAL_ERRORErreur serveur — réessayez avec backoff

Sandbox & support

Une fois votre demande d'accès validée, vous recevez par email vos deux clés pk_test_… et pk_live_…. La sandbox utilise la même base URL (https://api.pikoli.fr/api/v1) — c'est la clé qui détermine le mode. Les commandes en sandbox ne déclenchent pas de livraison réelle et ne sont pas facturées.