API Publique Voyage+

Connectez votre ERP (Odoo, Sage), votre CRM ou votre logiciel comptable à Voyage+ pour synchroniser automatiquement vos réservations, données financières et soldes de succursales. Pensée pour les compagnies de transport qui veulent aller plus loin.

Faire mon premier appel → Swagger interactif ↗

Ce que vous pouvez faire avec l'API

  • Exporter vos réservations vers votre ERP pour générer des factures automatiquement
  • Récupérer le chiffre d'affaires brut, net et les frais de plateforme pour votre comptabilité
  • Suivre les soldes des wallets de chaque succursale en temps réel
  • Obtenir des séries temporelles journalières et mensuelles par succursale pour vos bilans
  • Voir l'occupation des voyages planifiés avec taux de remplissage

Versionnage de l'API

L'API publique est actuellement en version v1 (URL : /api/public/). Toute rupture de compatibilité (changement de schéma de réponse, suppression d'endpoint) donnera lieu à une nouvelle version (/api/public/v2/) avec une période de dépréciation de 6 mois. Les évolutions additives (nouveau champ optionnel, nouvel endpoint) ne nécessitent pas de nouvelle version.

Conditions d'accès

L'API publique est réservée aux agences abonnées au Plan Entreprise (69 USD/mois). Pour générer une clé API :

  1. Connectez-vous à votre tableau de bord Voyage+
  2. Allez dans Paramètres → API & Intégrations
  3. Cliquez sur « Générer une clé API »
  4. Copiez la clé immédiatement — elle ne sera plus jamais affichée

Authentification

Toutes les requêtes doivent inclure un en-tête X-API-Key contenant votre clé API. La clé a le format vp_live_...

cURL
Python
JavaScript
PHP
curl -H "X-API-Key: vp_live_votre_cle_ici" \
     https://api.voyageplus.cd/api/public/branches
import httpx

headers = {"X-API-Key": "vp_live_votre_cle_ici"}
response = httpx.get("https://api.voyageplus.cd/api/public/branches", headers=headers)
print(response.json())
const response = await fetch("https://api.voyageplus.cd/api/public/branches", {
  headers: { "X-API-Key": "vp_live_votre_cle_ici" }
});
const data = await response.json();
console.log(data);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://api.voyageplus.cd/api/public/branches");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: vp_live_votre_cle_ici"]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
echo $response;

Premier appel

Commençons par récupérer la liste de vos succursales et leur solde wallet :

cURL
Python
JavaScript
PHP
curl -H "X-API-Key: vp_live_votre_cle_ici" \
     "https://api.voyageplus.cd/api/public/branches?include_siege=true"
import httpx

API_BASE = "https://api.voyageplus.cd/api/public"
API_KEY = "vp_live_votre_cle_ici"
headers = {"X-API-Key": API_KEY}

response = httpx.get(f"{API_BASE}/branches", params={"include_siege": "true"}, headers=headers)
data = response.json()

for branch in data["items"]:
    print(f"{branch['name']} — Solde : {branch['wallet_balance_cdf']} FC")
const API_BASE = "https://api.voyageplus.cd/api/public";
const API_KEY = "vp_live_votre_cle_ici";

const res = await fetch(`${API_BASE}/branches?include_siege=true`, {
  headers: { "X-API-Key": API_KEY }
});
const data = await res.json();

data.items.forEach(b => {
  console.log(`${b.name} — Solde : ${b.wallet_balance_cdf} FC`);
});
$apiBase = "https://api.voyageplus.cd/api/public";
$apiKey = "vp_live_votre_cle_ici";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiBase . "/branches?include_siege=true");
curl_setopt($ch, CURLOPT_HTTPHEADER, ["X-API-Key: " . $apiKey]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

foreach ($response["items"] as $branch) {
    echo $branch["name"] . " — Solde : " . $branch["wallet_balance_cdf"] . " FC\n";
}

Réponse :

{
  "items": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Siège",
      "city": "Kinshasa",
      "wallet_balance_cdf": "4850000.00",
      "agents_count": 5,
      "buses_count": 8,
      "lines_count": 6
    },
    {
      "id": "660e8400-e29b-41d4-a716-446655440000",
      "name": "Succursale Matadi",
      "city": "Matadi",
      "wallet_balance_cdf": "1200000.00",
      "agents_count": 3,
      "buses_count": 2,
      "lines_count": 5
    }
  ],
  "total": 2,
  "page": 1,
  "page_size": 100,
  "has_next": false
}

Codes d'erreur

CodeCauseSolution
200Succès—
401Clé manquante, invalide ou révoquéeVérifiez votre clé ou générez-en une nouvelle dans le tableau de bord
401Agence non Plan EntreprisePassez au Plan Entreprise (69 USD/mois)
429Rate limit dépasséAttendez 1 minute ou réduisez la fréquence de vos appels
422Paramètre invalide (date au mauvais format, page négative, etc.)Vérifiez le format attendu dans le tableau des paramètres de l'endpoint
500Erreur serveurContactez walborncorp@gmail.com

Rate limiting

Pour garantir la stabilité de la plateforme, chaque agence est limitée par :

Type d'endpointLimiteCache
Endpoints standards (/bookings, /trips, /financials, /branches)120 req/min30 à 120 secondes
Endpoints d'agrégation (/branches/metrics, /daily, /monthly)60 req/min300 secondes

Astuce : Si vous recevez un 429, implémentez un backoff exponentiel : attendez 2s, puis 4s, puis 8s avant de réessayer.

Endpoints

Voici les 7 endpoints disponibles. Tous sont en GET et retournent du JSON.

Format monétaire : Tous les montants sont en Francs Congolais (CDF). L'abbréviation FC dans les exemples est équivalente à CDF. Les valeurs sont sérialisées en string (ex: "4850000.00") et non en int, conformément à la sérialisation JSON de Django pour les Decimal.

GET /bookings

Liste les réservations de votre agence. Idéal pour synchroniser vos ventes vers votre ERP.

ParamètreTypeRequisDescription
start_datedateNonDate de voyage minimale
end_datedateNonDate de voyage maximale
statusstringNonCONFIRMED (défaut), CANCELLED, PENDING, ALL
pageintNonNuméro de page (défaut: 1)
page_sizeintNonRéservations par page (défaut: 100, max: 500)
cURL
Python
PHP
curl -H "X-API-Key: vp_live_..." \
     "https://api.voyageplus.cd/api/public/bookings?start_date=2026-08-01&end_date=2026-08-31&status=CONFIRMED"
response = httpx.get(f"{API_BASE}/bookings", headers=headers, params={
    "start_date": "2026-08-01",
    "end_date": "2026-08-31",
    "status": "CONFIRMED",
    "page_size": 50
})
for b in response.json()["items"]:
    print(f"{b['booking_reference']} — {b['client']['full_name']} — {b['net_agency_amount_cdf']} FC")
$url = $apiBase . "/bookings?start_date=2026-08-01&end_date=2026-08-31&status=CONFIRMED";
curl_setopt($ch, CURLOPT_URL, $url);
$response = json_decode(curl_exec($ch), true);
foreach ($response["items"] as $b) {
    echo $b["booking_reference"] . " — " . $b["client"]["full_name"] . "\n";
}

Note : Le format de date attendu si fourni est YYYY-MM-DD. Autrement, une erreur 422 sera levée.

GET /trips

Liste les voyages planifiés avec taux d'occupation en temps réel.

ParamètreTypeRequisDescription
datedateNonFiltrer par date de départ
pageintNonNuméro de page
page_sizeintNonPar page (défaut: 100, max: 500)
cURL
Python
curl -H "X-API-Key: vp_live_..." \
     "https://api.voyageplus.cd/api/public/trips?date=2026-08-20"
response = httpx.get(f"{API_BASE}/trips", headers=headers, params={"date": "2026-08-20"})
for t in response.json()["items"]:
    print(f"{t['line_departure']} → {t['line_destination']} — {t['reserved_seats']}/{t['total_seats']} sièges ({t['occupancy_rate']*100:.0f}%)")

GET /financials/summary

Synthèse financière de votre agence sur une période. Parfait pour la clôture comptable mensuelle.

ParamètreTypeRequisDescription
start_datedateOuiDébut de période
end_datedateOuiFin de période
cURL
Python
PHP
curl -H "X-API-Key: vp_live_..." \
     "https://api.voyageplus.cd/api/public/financials/summary?start_date=2026-08-01&end_date=2026-08-31"
response = httpx.get(f"{API_BASE}/financials/summary", headers=headers, params={
    "start_date": "2026-08-01", "end_date": "2026-08-31"
})
s = response.json()
print(f"CA brut: {s['total_gross_cdf']} FC")
print(f"Frais plateforme: {s['total_client_fees_cdf']} FC")
print(f"Net agence: {s['total_net_agency_cdf']} FC")
$url = $apiBase . "/financials/summary?start_date=2026-08-01&end_date=2026-08-31";
curl_setopt($ch, CURLOPT_URL, $url);
$s = json_decode(curl_exec($ch), true);
echo "CA brut: " . $s["total_gross_cdf"] . " FC\n";
echo "Net agence: " . $s["total_net_agency_cdf"] . " FC\n";

GET /branches

Liste vos succursales avec solde wallet et compteurs (agents, bus, lignes).

ParamètreTypeRequisDescription
include_siegeboolNonInclure le siège (défaut: false)
pageintNonNuméro de page
page_sizeintNonPar page (défaut: 100)

GET /branches/metrics

Agrégats par succursale sur une période : CA, frais, overage, wallet, dépôts, retraits, taux d'occupation.

ParamètreTypeRequisDescription
start_datedateOuiDébut de période
end_datedateOuiFin de période
branch_idUUIDNonFiltrer une succursale

GET /branches/daily

Séries temporelles journalières par succursale. Maximum 90 jours par requête.

ParamètreTypeRequisDescription
start_datedateOuiDébut (max 90j avant end_date)
end_datedateOuiFin
branch_idUUIDNonFiltrer une succursale
include_siegeboolNonInclure le siège

GET /branches/monthly

Séries temporelles mensuelles par succursale. Maximum 24 mois.

ParamètreTypeRequisDescription
monthsintNonNombre de mois (défaut: 12, max: 24)
branch_idUUIDNonFiltrer une succursale
include_siegeboolNonInclure le siège

Intégration Odoo

Guide complet pour synchroniser Voyage+ avec Odoo. Ce guide utilise Python avec la librairie httpx, adaptable à n'importe quel connecteur Odoo.

1. Configuration

import httpx
from datetime import date

API_BASE = "https://api.voyageplus.cd/api/public"
API_KEY = "vp_live_votre_cle_ici"
headers = {"X-API-Key": API_KEY}

2. Synchroniser les réservations du jour

today = date.today().isoformat()
response = httpx.get(f"{API_BASE}/bookings", headers=headers, params={
    "start_date": today,
    "end_date": today,
    "status": "CONFIRMED"
})

for booking in response.json()["items"]:
    # Créer une facture dans Odoo :
    # - Compte 701 (Ventes) : booking["ticket_price_cdf"]
    # - Compte 625 (Frais) : booking["client_fee_cdf"]
    # - Compte 512 (Banque) : booking["net_agency_amount_cdf"]
    print(f"{booking['booking_reference']} — {booking['client']['full_name']} — {booking['net_agency_amount_cdf']} FC")

3. Clôture comptable mensuelle

# Synthèse globale
summary = httpx.get(f"{API_BASE}/financials/summary", headers=headers, params={
    "start_date": "2026-08-01", "end_date": "2026-08-31"
}).json()

print(f"CA brut: {summary['total_gross_cdf']} FC")
print(f"Net agence: {summary['total_net_agency_cdf']} FC")

# Ventilation par succursale (pour Odoo multi-guichets)
metrics = httpx.get(f"{API_BASE}/branches/metrics", headers=headers, params={
    "start_date": "2026-08-01", "end_date": "2026-08-31"
}).json()

for metric in metrics:
    print(f"{metric['branch_name']}: {metric['net_agency_revenue_cdf']} FC (wallet: {metric['wallet_balance_cdf']} FC)")

4. Cron de synchronisation quotidienne

import schedule, time

def sync_daily():
    today = date.today().isoformat()
    # Sync réservations
    httpx.get(f"{API_BASE}/bookings", headers=headers,
                 params={"start_date": today, "end_date": today})
    # Sync finances
    httpx.get(f"{API_BASE}/financials/summary", headers=headers,
                 params={"start_date": today, "end_date": today})

# Exécuter tous les soirs à 23h00
schedule.every().day.at("23:00").do(sync_daily)
while True:
    schedule.run_pending()
    time.sleep(60)

5. Gestion multi-utilisateurs Odoo

Si votre Odoo a plusieurs utilisateurs (comptable, gérant, admin), voici comment gérer l'authentification :

import xmlrpc.client

# Authentification Odoo (db, uid, password)
odoo_url = "https://votre-odoo.com"
odoo_db = "votre_db"
odoo_username = "admin@agence.cd"
odoo_password = "mot_de_passe_odoo"

common = xmlrpc.client.ServerProxy(f"{odoo_url}/xmlrpc/2/common")
uid = common.authenticate(odoo_db, odoo_username, odoo_password, {})

# Créer un partenaire (client) depuis une réservation Voyage+
models = xmlrpc.client.ServerProxy(f"{odoo_url}/xmlrpc/2/object")
partner_id = models.execute_kw(odoo_db, uid, odoo_password,
    "res.partner", "create",
    [{
        "name": booking["client"]["full_name"],
        "phone": booking["client"]["phone"],
        "ref": booking["booking_reference"],  # Référence Voyage+ comme clé externe
    }]
)

# Créer une facture liée au partenaire
invoice_id = models.execute_kw(odoo_db, uid, odoo_password,
    "account.move", "create",
    [{
        "partner_id": partner_id,
        "move_type": "out_invoice",
        "invoice_line_ids": [(0, 0, {
            "name": f"Billet {booking['route']['departure_city']} → {booking['route']['arrival_city']}",
            "quantity": 1,
            "price_unit": float(booking["ticket_price_cdf"]),
        })],
    }]
)

Astuce multi-guichets : Si votre Odoo gère plusieurs succursales (multi-company), utilisez branch_id de la réponse Voyage+ pour mapper vers la bonne company_id Odoo. Créez une table de correspondance VP_BRANCH_ID → ODOO_COMPANY_ID dans votre connecteur.

Sécurité

Bonnes pratiques

  • Ne jamais committer la clé API dans votre code source. Utilisez une variable d'environnement
  • Stocker la clé dans une variable d'environnement VP_API_KEY
  • Révoquer immédiatement toute clé compromise dans le dashboard
  • Utiliser des clés séparées par environnement (prod, staging, dev)
  • Monitorer last_used_at pour détecter une activité anormale

Rotation des clés

  1. Générez une nouvelle clé dans le dashboard
  2. Mettez à jour votre intégration avec la nouvelle clé
  3. Vérifiez que tout fonctionne
  4. Révoquez l'ancienne clé

Webhooks temps réel

Les webhooks permettent de recevoir automatiquement les événements de votre agence (réservations, paiements, soldes wallet) vers votre ERP/CRM en temps réel — sans polling.

Comment ça marche

Quand un événement se produit (ex: réservation confirmée), Voyage+ envoie une requête HTTP POST vers l'URL que vous avez configurée dans Paramètres → API & Intégrations → Webhooks.

Chaque envoi est signé avec HMAC-SHA256 (header X-VoyagePlus-Signature). Le secret est unique par config et différent de votre clé API.

Headers reçus

POST /votre-url HTTP/1.1
Content-Type: application/json
X-VoyagePlus-Signature: sha256=a1b2c3d4e5f6...
X-VoyagePlus-Event: booking.confirmed
X-VoyagePlus-Delivery: 550e8400-e29b-41d4-a716-446655440000
User-Agent: VoyagePlus-Webhook/1.0

Vérification de la signature

Python
PHP
import hashlib, hmac

def verify_signature(raw_body, signature_header, secret):
    expected = "sha256=" + hmac.new(
        key=secret.encode(),
        msg=raw_body,
        digestmod=hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, signature_header)
function verifySignature($rawBody, $signatureHeader, $secret) {
    $expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
    return hash_equals($expected, $signatureHeader);
}

Événements disponibles (24)

CatégorieÉvénements
Réservationsbooking.created, booking.confirmed, booking.cancelled, booking.expired, booking.updated, booking.deleted
Voyagestrip.created, trip.status.ongoing, trip.status.completed, trip.modified
Offresoffer.created, offer.activated, offer.deactivated, offer.deleted
Succursalesbranch.created, branch.updated
Walletwallet.deposit.created, wallet.withdrawal.requested, wallet.withdrawal.completed, wallet.transfer.created, wallet.commission_debit.created
Abonnementssubscription.activated, subscription.expired, subscription.expiring_soon

Retry avec backoff exponentiel

TentativeDélaiStatut
1ImmédiatPENDING → RETRYING
2+1 minuteRETRYING
3+5 minutesRETRYING
4 (dernière)+30 minutesFAILED

Idempotence : Utilisez le header X-VoyagePlus-Delivery (UUID unique) comme clé de déduplication. Voyage+ peut envoyer le même événement plusieurs fois en cas de retry après timeout.

Guide complet en PDF : Télécharger le guide des webhooks

Playground interactif

Testez l'API directement depuis cette page. Entrez votre clé API et cliquez sur « Envoyer ».