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.
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 :
- Connectez-vous à votre tableau de bord Voyage+
- Allez dans Paramètres → API & Intégrations
- Cliquez sur « Générer une clé API »
- 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 -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 -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
| Code | Cause | Solution |
|---|---|---|
| 200 | Succès | — |
| 401 | Clé manquante, invalide ou révoquée | Vérifiez votre clé ou générez-en une nouvelle dans le tableau de bord |
| 401 | Agence non Plan Entreprise | Passez au Plan Entreprise (69 USD/mois) |
| 429 | Rate limit dépassé | Attendez 1 minute ou réduisez la fréquence de vos appels |
| 422 | Paramètre invalide (date au mauvais format, page négative, etc.) | Vérifiez le format attendu dans le tableau des paramètres de l'endpoint |
| 500 | Erreur serveur | Contactez walborncorp@gmail.com |
Rate limiting
Pour garantir la stabilité de la plateforme, chaque agence est limitée par :
| Type d'endpoint | Limite | Cache |
|---|---|---|
| Endpoints standards (/bookings, /trips, /financials, /branches) | 120 req/min | 30 à 120 secondes |
| Endpoints d'agrégation (/branches/metrics, /daily, /monthly) | 60 req/min | 300 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ètre | Type | Requis | Description |
|---|---|---|---|
| start_date | date | Non | Date de voyage minimale |
| end_date | date | Non | Date de voyage maximale |
| status | string | Non | CONFIRMED (défaut), CANCELLED, PENDING, ALL |
| page | int | Non | Numéro de page (défaut: 1) |
| page_size | int | Non | Réservations par page (défaut: 100, max: 500) |
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ètre | Type | Requis | Description |
|---|---|---|---|
| date | date | Non | Filtrer par date de départ |
| page | int | Non | Numéro de page |
| page_size | int | Non | Par page (défaut: 100, max: 500) |
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ètre | Type | Requis | Description |
|---|---|---|---|
| start_date | date | Oui | Début de période |
| end_date | date | Oui | Fin de période |
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ètre | Type | Requis | Description |
|---|---|---|---|
| include_siege | bool | Non | Inclure le siège (défaut: false) |
| page | int | Non | Numéro de page |
| page_size | int | Non | Par 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ètre | Type | Requis | Description |
|---|---|---|---|
| start_date | date | Oui | Début de période |
| end_date | date | Oui | Fin de période |
| branch_id | UUID | Non | Filtrer une succursale |
GET /branches/daily
Séries temporelles journalières par succursale. Maximum 90 jours par requête.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
| start_date | date | Oui | Début (max 90j avant end_date) |
| end_date | date | Oui | Fin |
| branch_id | UUID | Non | Filtrer une succursale |
| include_siege | bool | Non | Inclure le siège |
GET /branches/monthly
Séries temporelles mensuelles par succursale. Maximum 24 mois.
| Paramètre | Type | Requis | Description |
|---|---|---|---|
| months | int | Non | Nombre de mois (défaut: 12, max: 24) |
| branch_id | UUID | Non | Filtrer une succursale |
| include_siege | bool | Non | Inclure 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_atpour détecter une activité anormale
Rotation des clés
- Générez une nouvelle clé dans le dashboard
- Mettez à jour votre intégration avec la nouvelle clé
- Vérifiez que tout fonctionne
- 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
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éservations | booking.created, booking.confirmed, booking.cancelled, booking.expired, booking.updated, booking.deleted |
| Voyages | trip.created, trip.status.ongoing, trip.status.completed, trip.modified |
| Offres | offer.created, offer.activated, offer.deactivated, offer.deleted |
| Succursales | branch.created, branch.updated |
| Wallet | wallet.deposit.created, wallet.withdrawal.requested, wallet.withdrawal.completed, wallet.transfer.created, wallet.commission_debit.created |
| Abonnements | subscription.activated, subscription.expired, subscription.expiring_soon |
Retry avec backoff exponentiel
| Tentative | Délai | Statut |
|---|---|---|
| 1 | Immédiat | PENDING → RETRYING |
| 2 | +1 minute | RETRYING |
| 3 | +5 minutes | RETRYING |
| 4 (dernière) | +30 minutes | FAILED |
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 ».