Intégrez la flotte Pony à votre service
Quatre familles d'API selon ce que vous construisez : un flux public de disponibilité des véhicules (GBFS), un reporting réglementaire pour les villes (MDS & NeTEx), ou une intégration complète pour vendre le trajet dans votre propre app (MaaS).
GBFS
Position, disponibilité et tarifs des véhicules et stations, en lecture seule.
VillesMDS
Trips et status changes horodatés, pour le reporting réglementaire.
VillesNeTEx
Export XML normalisé de l'offre de mobilité partagée, par zone.
Intégration pousséeMaaS
Déverrouillage, commandes véhicule, coupons, trajets et webhooks.
Environnements
Chaque API existe en QA (bac à sable, flotte réduite) et en Production. Utilisez le sélecteur QA / Prod en haut de la page : tous les exemples de cette documentation basculent automatiquement sur le bon nom d'hôte.
| API | QA | Production |
|---|---|---|
| GBFS | gbfs-dev.getapony.com | gbfs.getapony.com |
| MDS / NeTEx | mds-dev.getapony.com | mds.getapony.com |
| MaaS | partners-dev.getapony.com | partners.getapony.com |
| MaaS TOMP | partners-dev.getapony.com/tomp | partners.getapony.com/tomp |
La QA n'expose qu'une flotte réduite de véhicules de test. Validez votre intégration ici avant toute demande de passage en production.
Authentification
Le modèle d'authentification varie selon l'API :
| API | Méthode | Portée |
|---|---|---|
| GBFS | Clé key en query param, sur tous les feeds | Lecture seule |
| MDS / NeTEx | Clé key en query param, une clé par ville/agence | Lecture seule, restreinte GCP |
| MaaS | api_key + app_id en query param | Lecture/écriture, scopée à votre app_id |
| MaaS TOMP | SSO : jeton voyageur échangé sur /oauth/token, puis Authorization: Bearer | Lecture/écriture, limitée au voyageur du jeton |
Vos identifiants (clé GBFS, clé MDS, ou couple
api_key/app_idMaaS) vous sont communiqués individuellement à l'onboarding — ils ne sont jamais partagés entre partenaires.
GBFS
Le General Bikeshare Feed Specification est le standard ouvert qui décrit la position, la disponibilité et la tarification d'une flotte de mobilité partagée. Pony expose les feeds GBFS 2.2 et 3.0 pour chaque région opérée, avec une clé d'API key fournie par Pony à l'onboarding.
Auto-discovery
Un seul appel gbfs.json retourne la liste des feeds disponibles pour une région. C'est le point d'entrée à consommer en premier — les URLs des feeds individuels ne doivent pas être codées en dur côté partenaire.
v2.2 — un feed par langue
curl "https://gbfs-dev.getapony.com/v1/nantes/fr/gbfs.json?key=$GBFS_API_KEY"
curl "https://gbfs.getapony.com/v1/nantes/fr/gbfs.json?key=$GBFS_API_KEY"
v3.0 — un feed unique, multilingue
La clé
keyest requise sur tous les feeds : sans elle, l'appel répond401.region_idest l'identifiant de la zone opérée (ex.nantes,bordeaux,orleans) ;languageestfrouenpour le v2.2.
Feeds & versions
| Feed | Contenu |
|---|---|
| system_information | Métadonnées de l'opérateur et de la région (nom, fuseau horaire, langue, contact). |
| vehicle_types | Types de véhicules exploités (vélo, trottinette électrique…) et leurs caractéristiques. |
| free_bike_status (v2.2) / vehicle_status (v3.0) | Position et disponibilité en temps réel de chaque véhicule en free-floating. |
| station_information | Position et attributs statiques des stations. |
| station_status | Occupation et disponibilité en temps réel des stations. |
| system_pricing_plans | Grille tarifaire applicable à la région. |
| geofencing_zones | Zones de restriction (no-parking, no-ride, vitesse réduite…). |
ttl est toujours à 0 : les feeds temps réel doivent être récupérés à chaque besoin, sans mise en cache côté partenaire au-delà de quelques secondes.
Champ véhicule
free_bike_status suit le schéma GBFS standard. Dans les régions opérées avec un partenaire MaaS, Pony l'étend avec les champs préfixés _ ci-dessous ; la liste exposée dépend de la région.
| Champ | Type | Description |
|---|---|---|
| bike_id | string | Identifiant GBFS régénéré à chaque appel — ne pas utiliser pour un suivi persistant, préférer _vehicle_id. |
| _vehicle_id | string | Identifiant stable du véhicule ([A-Za-z0-9_-], ≤ 200 caractères). |
| lat / lon | float | Position GPS courante. |
| current_fuel_percent | float | Niveau de batterie, entre 0.0 et 1.0. |
| is_disabled | boolean | Véhicule hors-service / offline. |
| station_id | string | Présent seulement si le véhicule est actuellement rattaché à une station. |
| _max_speed | float | Vitesse maximale actuellement autorisée sur le véhicule, en km/h. |
| _battery_hatch_locked | boolean | État de verrouillage de la trappe batterie. |
| _online | boolean | Connectivité IoT du véhicule. |
{
"last_updated": 1704793525,
"ttl": 0,
"version": "2.2",
"data": {
"bikes": [
{
"bike_id": "pony_c53dd513d7ad4dad8b9471a8e0fbe45a",
"_vehicle_id": "ec00200b",
"lat": 48.866667,
"lon": 2.333333,
"current_fuel_percent": 0.33,
"is_disabled": false,
"station_id": "s00133a",
"_max_speed": 23.0,
"_battery_hatch_locked": true,
"_online": true
}
]
}
}
Champ station
| Champ | Type | Description |
|---|---|---|
| station_id | string | Identifiant stable de la station. |
| name | string | Nom public affichable de la station. |
| lat / lon | float | Position GPS. |
| is_virtual_station | boolean | Zone logique sans infrastructure physique (toujours true chez Pony). |
| _vehicle_ids | string[] | Véhicules actuellement rattachés à la station (régions où le partenaire opère la logistique). |
| _battery_level | float | Niveau de batterie de la borne. Non mesuré à ce jour : toujours 1.0 (régions où le partenaire opère la logistique). |
{
"last_updated": 1704793525,
"ttl": 0,
"version": "2.2",
"data": {
"stations": [
{
"station_id": "s00133a",
"name": "Bordeaux place du Rocher",
"lat": 44.8519577,
"lon": -0.5947185,
"is_virtual_station": true,
"_vehicle_ids": ["ec00200b", "ec00201b"],
"_battery_level": 1.0
}
]
}
}
MDS
La Mobility Data Specification fournit aux villes les données réglementaires de suivi de flotte : trajets terminés et changements d'état des véhicules. Chaque agence (ex. mairie) reçoit sa propre clé, restreinte à cette API.
Pony provider_id (registre officiel MDS) — identique au gbfs_pony_system_id
f190d330-b49e-4590-871b-0bcbec565a8c
Trips
Retourne tous les trajets terminés jusqu'à l'heure indiquée : « donne-moi tous les trajets qui se sont terminés dans la dernière heure ».
curl "https://mds-dev.getapony.com/v1/paris/trips?end_time=2026-09-11T15&key={api_key}"
curl "https://mds.getapony.com/v1/paris/trips?end_time=2026-09-11T15&key={api_key}"
{
"version": "1.0.0",
"data": {
"trips": [
{
"provider_id": "f190d330-b49e-4590-871b-0bcbec565a8c",
"provider_name": "Pony",
"device_id": "1f035766-9d25-40af-b1e1-e832cd06d10b",
"vehicle_id": "S00789",
"vehicle_type": "scooter",
"propulsion_types": ["electric"],
"trip_id": "85d20361-27f0-400d-a5cb-5edc2b225fbf",
"trip_duration": 180,
"trip_distance": 600,
"start_time": 1606997981,
"end_time": 1606998161,
"publication_time": 1606998161,
"accuracy": 10,
"route": {
"type": "FeatureCollection",
"features": [
{ "type": "Feature", "properties": { "timestamp": 1606997981 },
"geometry": { "type": "Point", "coordinates": [48.853744, 2.3468297] } },
{ "type": "Feature", "properties": { "timestamp": 1606998161 },
"geometry": { "type": "Point", "coordinates": [48.856882, 2.3415603] } }
]
}
}
]
}
}
Détail complet des champs : spec MDS — Trips.
Status Changes
Retourne tous les changements d'état survenus jusqu'à l'heure indiquée (ex. véhicule 1 : available → unavailable suite à un unlock). Un même véhicule peut apparaître plusieurs fois si plusieurs événements se sont produits dans la fenêtre.
curl "https://mds-dev.getapony.com/v1/paris/status_changes?end_time=2026-09-11T15&key={api_key}"
curl "https://mds.getapony.com/v1/paris/status_changes?end_time=2026-09-11T15&key={api_key}"
{
"version": "1.0.0",
"data": {
"status_changes": [
{
"provider_id": "f190d330-b49e-4590-871b-0bcbec565a8c",
"provider_name": "Pony",
"device_id": "1f035766-9d25-40af-b1e1-e832cd06d10b",
"vehicle_id": "S00789",
"vehicle_type": "scooter",
"vehicle_state": "on_trip",
"propulsion_types": ["electric"],
"event_types": ["trip_start"],
"event_time": 1606998064,
"publication_time": 1606998064,
"battery_pct": 0.9,
"trip_id": "85d20361-27f0-400d-a5cb-5edc2b225fbf",
"event_location": {
"type": "Feature",
"properties": { "timestamp": 1606998064 },
"geometry": { "type": "Point", "coordinates": [48.853744, 2.3468297] }
}
}
]
}
}
Machine à états complète (événements ↔ statuts) : diagramme officiel · spec MDS — Status Changes.
NeTEx
NeTEx (Network Timetable Exchange, norme CEN/TS 16614) est le format d'échange XML européen utilisé par les autorités organisatrices de mobilité. Pony expose, par zone opérée, un export NeTEx v6 décrivant son offre de mobilité partagée.
Endpoint
Le fichier est servi sur la même API que MDS, avec la même clé key par agence.
curl "https://mds-dev.getapony.com/v1/netex/v6/regions/paris?key={api_key}" -o pony_netex_paris.xml
curl "https://mds.getapony.com/v1/netex/v6/regions/paris?key={api_key}" -o pony_netex_paris.xml
regionest l'identifiant de zone (ex.paris). La réponse est un fichierapplication/xmlnommépony_netex_{region}.xml.
Structure du flux
Le document suit l'ossature standard NeTEx : un PublicationDelivery racine contenant un dataObjects / CompositeFrame, lui-même composé de frames spécialisées :
- ResourceFrame — opérateur, réseau et types de véhicules (équivalent NeTEx de
system_information/vehicle_typesen GBFS). - SiteFrame — sites et points d'accès, dont les stations de la zone.
- ServiceFrame — l'offre de mobilité partagée elle-même (zones de service, contraintes de dépôt).
<PublicationDelivery xmlns="http://www.netex.org.uk/netex">
<PublicationTimestamp>2026-09-11T15:00:00Z</PublicationTimestamp>
<ParticipantRef>PONY</ParticipantRef>
<dataObjects>
<CompositeFrame id="PONY:CompositeFrame:paris" version="1">
<frames>
<ResourceFrame />
<SiteFrame />
<ServiceFrame />
</frames>
</CompositeFrame>
</dataObjects>
</PublicationDelivery>
Ce squelette suit la norme NeTEx générique ; le détail exact des éléments (identifiants, versions de profil) est confirmé à l'onboarding selon les exigences de votre autorité organisatrice.
MaaS
L'API MaaS permet de vendre le trajet Pony à l'intérieur de votre propre application : localiser les véhicules, envoyer des commandes (déverrouiller, verrouiller, sonner…), suivre les trajets et gérer les coupons de vos utilisateurs.
Pony propose aussi une API MaaS au standard TOMP 1.6.1, avec authentification SSO des voyageurs. L'API décrite sur cette page reste disponible sans changement.
Auth & app_id
api_key
Passé en query param sur chaque appel : api_key=XXXX. Vous transmettez alors votre propre external_user_id.
Pour GBFS, utilisez une clé GBFS dédiée (key=XXXX), distincte de la clé MaaS.
pony auth token (à venir)
Header Authorization: Bearer {auth_token}, lié à un utilisateur Pony précis. Ne pas transmettre d'external_user_id dans ce cas — Pony retrouve l'utilisateur depuis le token.
app_id identifie votre intégration sur tous les appels GBFS et commandes véhicule : app_id=YYYY, en query param systématiquement.
curl "https://partners-dev.getapony.com/v0/users/user_XXXX/ongoing_trip?api_key=XXXX&app_id=YYYY"
curl "https://partners.getapony.com/v0/users/user_XXXX/ongoing_trip?api_key=XXXX&app_id=YYYY"
Seuils de batterie
La batterie du véhicule est exposée en continu via GBFS (current_fuel_percent). Trois seuils gouvernent le comportement du véhicule :
| Seuil | Défaut | Effet |
|---|---|---|
| Refus de déverrouillage | 6% | Sous ce niveau, le véhicule refuse tout nouveau trajet. |
| Fin d'assistance | 6% | L'assistance électrique du véhicule est coupée. |
| Verrouillage forcé | 5% | Le véhicule se verrouille et le trajet en cours se termine automatiquement. |
Format des réponses & erreurs
Succès — http_code 200, corps JSON spécifique à l'appel.
Échec — http_code 400, avec toujours :
{ "status": "failed", "error": "ERROR_CODE" }
| error | Description |
|---|---|
| UNKNOWN_ERROR | Erreur générique non attendue. |
| VALIDATION_ERROR | Payload mal formé — détail dans validation_output. |
| UNKNOWN_APP_ID | app_id non reconnu. |
{
"status": "failed",
"error": "VALIDATION_ERROR",
"validation_output": {
"app_id": ["Missing data for required field."]
}
}
error et error_description sont destinés au debug : ne jamais les afficher tels quels à l'utilisateur final.
Chaque endpoint peut ajouter ses propres codes d'erreur en complément de ce socle commun — voir chaque section ci-dessous.
Coupons
Utiliser un coupon
Historique des coupons
{
"data": [
{
"coupon": "EASTER",
"title": "-50% sur 3 trajet(s)",
"subtitle": "0 / 3 trajet(s) restant(s)",
"effective_end_at": "2030-01-01T00:00:00Z",
"conditions": ["30 min par trajet (max)"],
"expired": false
}
]
}
Trajets
Historique des trajets
{
"has_more": false,
"data": [
{ "_id": "trip_...", "startTime": "2030-01-01T00:00:00Z", "endTime": "2030-01-01T00:00:00Z" }
]
}
Trajet en cours
Renvoie le trajet actuellement en cours pour un utilisateur, c'est-à-dire déverrouillé et non encore verrouillé/terminé (un trajet en pause reste « en cours »).
{
"_id": "trip_...",
"startTime": "2030-01-01T00:00:00+00:00"
}
| error | http_code | Description |
|---|---|---|
| NO_ONGOING_TRIP | 400 | Aucun trajet en cours pour cet utilisateur (y compris si Pony ne le connaît pas). |
| VALIDATION_ERROR | 400 | app_id / api_key manquant ou invalide. |
| — | 401 | app_id inconnu ou api_key invalide — corps {"error": "..."}. |
Véhicules
La liste des véhicules est fournie par le flux GBFS, avec une clé GBFS dédiée qui débloque les champs étendus (voir champ véhicule).
Tous les véhicules
Filtrage possible via
&vehicle_id={id}pour ne récupérer qu'un seul véhicule (correspondance stricte).
Stations
La liste des stations est fournie par le flux GBFS, avec les mêmes champs étendus (voir champ station).
Toutes les stations
Commandes véhicule
Payload commun
| Champ | Requis | Type | Description |
|---|---|---|---|
| command | requis | string | La commande envoyée, ex. "unlock". |
| lat / lon | requis | float | Position de l'appelant. |
| external_trip_id | optionnel | string | Votre identifiant de trajet ([A-Za-z0-9_-], ≤ 200 car.). |
| external_user_id | optionnel | string | Votre identifiant utilisateur — obligatoire si vous n'utilisez pas de pony auth token. |
| external_agent_id | optionnel | string | Identifiant agent/logistique, si l'appelant n'est pas un simple utilisateur. |
| error | Description |
|---|---|
| UNKNOWN_VEHICLE_ID | Identifiant véhicule non reconnu. |
| UNKNOWN_VEHICLE_COMMAND | Commande non reconnue. |
| VEHICLE_OFFLINE | Le véhicule est hors-ligne au moment de l'appel (race condition possible). |
| VEHICLE_NOT_AVAILABLE | Véhicule indisponible pour une autre raison. |
unlock
Déverrouille le véhicule et démarre le trajet associé. Champs additionnels : max_speed (int, requis), nfc_scan_id (optionnel).
curl -X POST https://partners-dev.getapony.com/v1/vehicles/ec00200b/command \
-H 'Content-Type: application/json' \
-d '{"command":"unlock","external_trip_id":"trip_00467","external_user_id":"user_0009","lat":49.866699,"lon":2.333378,"max_speed":23}'
curl -X POST https://partners.getapony.com/v1/vehicles/ec00200b/command \
-H 'Content-Type: application/json' \
-d '{"command":"unlock","external_trip_id":"trip_00467","external_user_id":"user_0009","lat":49.866699,"lon":2.333378,"max_speed":23}'
{"status": "succeeded", "trip_id": "trip_..."}
Erreurs spécifiques :
TRIP_ALREADY_STARTED·VEHICLE_ALREADY_IN_TRIP(un autre utilisateur a déjà démarré un trajet) ·VEHICLE_UNLOCK_FAIL(souvent batterie trop faible).
end_trip_check
Avant de verrouiller réellement, vérifie que la position permet de terminer le trajet.
{ "end_trip_flow": "TAKE_PICTURE", "error_description": "xxx" }
Si
end_trip_flow = TAKE_PICTURE, l'utilisateur peut poursuivre le flux de fin de trajet ; sinon, affichezerror_descriptionet bloquez l'étape suivante.
lock
Verrouille le véhicule et termine le trajet. Appel synchrone : bloque jusqu'au verrouillage effectif ou jusqu'à un timeout de 30 s.
curl -X POST https://partners-dev.getapony.com/v1/vehicles/ec00200b/command \
-H 'Content-Type: application/json' \
-d '{"command":"lock","external_trip_id":"trip_00467","external_user_id":"user_0009"}'
curl -X POST https://partners.getapony.com/v1/vehicles/ec00200b/command \
-H 'Content-Type: application/json' \
-d '{"command":"lock","external_trip_id":"trip_00467","external_user_id":"user_0009"}'
{"status": "succeeded"}
Erreurs :
UNKNOWN_TRIP_ID·TRIP_ALREADY_ENDED·VEHICLE_LOCK_TIMEOUT(timeout 30 s dépassé).
pause
Verrouille le véhicule sans terminer le trajet. Également synchrone (timeout 30 s).
{"status": "succeeded"}
Mêmes erreurs que
lock:UNKNOWN_TRIP_ID·TRIP_ALREADY_ENDED·VEHICLE_LOCK_TIMEOUT.
resume
Redéverrouille un véhicule déjà en trajet (sortie de pause). Champ additionnel : max_speed (int, requis).
{"status": "succeeded"}
Erreur spécifique :
VEHICLE_ALREADY_IN_TRIP.
unlock_battery_hatch
Déverrouille la trappe batterie. Réservé à un agent : external_agent_id requis.
{"status": "succeeded"}
Erreur spécifique :
VEHICLE_ALREADY_IN_TRIP(impossible d'ouvrir la trappe pendant un trajet).
ring
Déclenche le bip sonore du véhicule (typiquement pour un agent logistique). external_agent_id requis.
{"status": "succeeded"}
Erreur spécifique :
VEHICLE_ALREADY_IN_TRIP(ne jamais faire sonner un véhicule occupé par un utilisateur en trajet — risque de surprise/danger).
set_max_speed
Ajuste la vitesse maximale du véhicule, en km/h. Champ additionnel : max_speed (int, requis, entre 1 et 25 — limite légale).
{
"status": "failed",
"error": "VALIDATION_ERROR",
"validation_output": "The max_speed must be between 1 & 25 km/h"
}
Erreurs :
UNKNOWN_TRIP_ID·TRIP_ALREADY_ENDED·VALIDATION_ERROR(vitesse hors bornes).
Webhooks
Pony notifie votre plateforme en HTTPS, avec la même authentification que le reste de l'API MaaS : api_key et app_id en query params.
Mise à jour véhicule
Déclenché à chaque changement d'état d'un véhicule (ex. scan NFC).
curl -X POST https://partner-domain.com/pony_webhook/vehicule?api_key=ZZZZ&app_id=BBBB \
-H 'Content-Type: application/json' \
-d '{"vehicle_id":"ec00200b","locked":true,"position":{"type":"Point","coordinates":[48.866667,2.333333]},"battery_level":0.33,"online":true,"status":"in_trip","station_id":"s00133a","max_speed":23,"battery_hatch_locked":true}'
{"status": "succeeded"}
Relances & timeout
Si l'appel échoue (code ≠ 200), Pony relance avec un backoff exponentiel — 3 tentatives sur 5 minutes, timeout de 10 s par tentative :
| Tentative | Délai depuis l'échec initial |
|---|---|
| Relance 1 | +75 s |
| Relance 2 | +150 s |
| Relance 3 | +300 s (soit 5 min au total) |
MaaS TOMP
La nouvelle API MaaS de Pony suit le standard TOMP-API 1.6.1 : votre application réserve, déverrouille, met en pause et termine un trajet Pony pour un voyageur authentifié par le SSO de votre réseau. Elle coexiste avec l'API MaaS existante, qui reste disponible sans changement.
Spécification v1, en cours d'implémentation. Les routes, corps et codes d'erreur décrits ici sont le contrat que Pony implémente. Pony vous prévient à l'ouverture de l'environnement QA.
Vue d'ensemble
Un trajet complet, dans l'ordre des appels. Toutes les routes sont des routes TOMP 1.6.1 sauf /oauth/token (RFC 7523), l'alias current (voir Compte voyageur) et le dépôt de la photo de stationnement, que TOMP ne prévoit pas.
| Étape | Appel | Effet |
|---|---|---|
| 1. Jeton | POST /oauth/token | Échange le jeton SSO du voyageur contre un jeton Pony, valable 1 h. Crée le compte Pony au premier échange. |
| 2. Profil | GET / PATCH /customers/current | Lit le profil (numéro de téléphone validé ou non) ; modifie le prénom et le nom. |
| 3. Téléphone | PATCH /customers/current | Enregistre le numéro et envoie le code SMS, puis valide le code. Une seule fois par voyageur, obligatoire avant la première réservation. |
| 4. Réservation | POST /bookings/one-stop | Réserve le véhicule scanné pour 5 minutes. Réponse directement CONFIRMED (AUTO_COMMIT). |
| 5. Départ | POST /legs/{id}/events SET_IN_USE | Déverrouille le véhicule et démarre le trajet. |
| 6. Pause / reprise | PAUSE puis SET_IN_USE | Verrouille le véhicule sans terminer le trajet, puis le redéverrouille. |
| 7. Contrôle de fin | START_FINISHING (recommandé) | Indique si le voyageur peut terminer ici et ce qu'il doit faire (photo). Pony ne refait pas ce contrôle à la fin. |
| 8. Photo | POST /legs/{id}/parking-photo (recommandé) | Dépose chez Pony la photo du véhicule garé. |
| 9. Fin | FINISH | Verrouille le véhicule et termine le trajet. |
| 10. Suivi | GET /legs/{id}, GET /bookings | État d'un trajet, trajet en cours, historique. |
Dans l'autre sens, Pony vous notifie par callback quand il termine ou annule un trajet sans votre appel (durée maximale, batterie vide, support, échec du déverrouillage), et, si vous le souhaitez, par webhook véhicule à chaque changement d'état d'un véhicule.
La découverte des véhicules et des stations n'est pas dans cette API : elle passe par le flux GBFS existant, comme le prévoit TOMP.
Conventions
| Environnement | URL de base |
|---|---|
| QA | https://partners-dev.getapony.com/tomp |
| Production | https://partners.getapony.com/tomp |
Tous les chemins de cette page sont relatifs à cette URL de base. Chaque appel porte les en-têtes TOMP suivants (sauf /oauth/token, et Authorization sauf sur /operator/meta) :
| En-tête | Requis | Valeur |
|---|---|---|
| Authorization | requis | Bearer {access_token}, obtenu via /oauth/token. |
| Accept-Language | recommandé | fr ou en, seules langues prises en charge pour l'instant (fr-FR est traité comme fr, toute autre langue donne en, en-tête absent aussi). C'est la langue des messages affichables (detail, text). |
| Api | requis | TOMP |
| Api-Version | requis | 1.6.1 |
| maas-id | requis | Votre identifiant MaaS, communiqué à l'onboarding (ex. tao). |
| addressed-to | optionnel | pony |
| Content-Type | requis sur POST / PATCH | application/json, sauf image/jpeg pour la photo de stationnement |
curl "https://partners-dev.getapony.com/tomp/customers/current" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Accept-Language: fr" \ -H "Api: TOMP" \ -H "Api-Version: 1.6.1" \ -H "maas-id: tao"
curl "https://partners.getapony.com/tomp/customers/current" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Accept-Language: fr" \ -H "Api: TOMP" \ -H "Api-Version: 1.6.1" \ -H "maas-id: tao"
| Cas | Réponse |
|---|---|
Api ou Api-Version absent, ou Api-Version autre que 1.6.1 | 400, UNSUPPORTED_API_VERSION (7008) |
maas-id absent ou inconnu | 400, UNKNOWN_MAAS_ID (7001) |
Jeton absent, expiré, ou émis pour un autre maas-id | 401, INVALID_TOKEN (7501) |
Chaque réponse porte l'en-tête Content-Language.
| Donnée | Format |
|---|---|
| Dates | RFC 3339 en UTC, ex. 2026-10-20T08:15:04Z. Les dates que vous envoyez doivent porter un fuseau (Z ou +02:00). Exception : l'en-tête Expires est une date HTTP. |
| Positions | {"coordinates": {"lat": 47.902964, "lng": 1.909251}}, WGS84. La clé est lng, pas lon. |
| Batterie | asset.stateOfCharge, entier de 0 à 100 (pourcentage). |
| Distance | distance, entier en mètres. |
| Identifiants | Chaînes opaques générées par Pony : ne pas en déduire de sens. |
| Types de véhicule | assetType.id reprend le vehicle_type_id GBFS : ebike et bike ont l'assetClass BICYCLE, scooter l'assetClass STEP. |
Périmètre du jeton. Le jeton identifie un voyageur : réservations et trajets sont limités à ce voyageur. Un identifiant qui appartient à un autre voyageur répond 404.
Fréquence d'appel. Pas de quota, mais n'interrogez pas GET /legs/{id} plus d'une fois toutes les 30 secondes : les callbacks vous préviennent des changements.
Le YAML TOMP 1.6.1 déclare minimum: 0 sur lat et lng, ce qui rejette les longitudes ouest. Désactivez cette contrainte si vous générez un validateur depuis le YAML.
Écarts au standard. Pony ajoute ces conventions au YAML TOMP 1.6.1 (« dialecte ») :
| Élément | Convention Pony |
|---|---|
| /customers/current | Alias de l'identifiant du voyageur du jeton. |
| offset / limit | Pagination de GET /bookings. |
| extraFields.otp | Code SMS, d'après l'exemple de la spec TOMP (extraInfo.otp est aussi accepté). |
| conditions[].id | Sur conditionRequireOffboardingSteps, porte le verdict du contrôle de fin (TAKE_PICTURE…). |
| error.title | Code stable et lisible de l'erreur (CUSTOMER_NOT_ACTIVE). |
| userLocation | Requis sur tous les événements de trajet, y compris START_FINISHING. |
| POST /legs/{id}/parking-photo | Dépôt de la photo de stationnement, absent de TOMP. |
| parkingPhotoUploaded | Sur le leg : indique si Pony a reçu la photo de stationnement. |
| Content-Type | application/json uniquement, y compris sur PATCH /customers (la spec y prévoit application/merge-patch+json). Seule exception : la photo de stationnement, en image/jpeg. |
| Callbacks | comment porte un code de motif ; Pony s'authentifie par app_id / api_key en query string. |
Authentification SSO
Le compte client de votre réseau (Keycloak) est la seule autorité d'identité : Pony ne gère ni mot de passe ni écran de connexion. Votre API échange le jeton Keycloak du voyageur contre un jeton Pony, selon la RFC 7523 (grant jwt-bearer), et s'authentifie en HTTP Basic avec les identifiants que Pony lui fournit.
Échanger le jeton
| Paramètre (form) | Valeur |
|---|---|
| grant_type | urn:ietf:params:oauth:grant-type:jwt-bearer |
| assertion | Le jeton d'accès Keycloak du voyageur. |
| scope | openid email profile |
curl -X POST "https://partners-dev.getapony.com/tomp/oauth/token" \ -u "$CLIENT_ID:$CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \ --data-urlencode "assertion=$KEYCLOAK_ACCESS_TOKEN" \ --data-urlencode "scope=openid email profile"
curl -X POST "https://partners.getapony.com/tomp/oauth/token" \ -u "$CLIENT_ID:$CLIENT_SECRET" \ -H "Content-Type: application/x-www-form-urlencoded" \ --data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer" \ --data-urlencode "assertion=$KEYCLOAK_ACCESS_TOKEN" \ --data-urlencode "scope=openid email profile"
{
"access_token": "pny_at_7f3c9a1e5b8d2f6c4a0e9b7d1c3f5a8e",
"token_type": "Bearer",
"expires_in": 3600
}
Vérification de l'assertion. Pony vérifie la signature RS256 via le JWKS publié par votre realm (jamais une clé copiée), l'émetteur (iss), l'expiration (exp) et le client destinataire (azp ou aud, valeur convenue à l'onboarding). Les claims sub et email (non vide) sont obligatoires. sub ne contient que des lettres, des chiffres, _ et -. scope est facultatif et n'est pas vérifié.
Compte voyageur. Chaque voyageur a un compte Pony propre à votre réseau, identifié par votre maas-id et son sub :
- Création : au premier échange, à partir des claims
sub,given_name,family_nameetbirthdate(tous facultatifs saufsub). - Réutilisation : les échanges suivants utilisent le même compte, sans réécrire le nom ni la date de naissance.
- Jamais rattaché à un compte Pony existant, même avec le même e-mail : le compte n'existe que pour votre réseau.
- E-mail : celui du jeton, renvoyé par
/customers/currentet non modifiable. - Téléphone : il n'est pas dans le jeton. Le compte n'en a pas encore (statut
CREATED) ; il est enregistré ensuite par SMS.
Identifiants Basic. Encodés en application/x-www-form-urlencoded avant le Base64 (RFC 6749 §2.3.1). Les secrets fournis par Pony ne contiennent que des caractères qui n'ont pas besoin d'encodage.
Durée de vie. Le jeton Pony vit 1 heure. Gardez-le en cache, sans refresh token : dès qu'un appel répond 401, refaites un échange avec un jeton Keycloak valide.
Erreurs, au format de la RFC 6749 §5.2 ({"error": "...", "error_description": "..."}) :
| error | http_code | Description |
|---|---|---|
| invalid_client | 401 | Identifiants Basic absents ou invalides. En-tête WWW-Authenticate: Basic. |
| invalid_request | 400 | Paramètre manquant ou en double. |
| unsupported_grant_type | 400 | grant_type différent de jwt-bearer. |
| invalid_grant | 400 | Assertion expirée, signature, émetteur ou client destinataire non reconnus, claim sub ou email absent ou vide, sub avec des caractères non pris en charge. |
| temporarily_unavailable | 503 | Le JWKS de votre realm ne répond pas : l'assertion n'a pas pu être vérifiée. Réessayez après Retry-After secondes, sans redemander de connexion au voyageur. |
| server_error | 500 | Erreur interne Pony. |
La réponse porte Cache-Control: no-store. error_description est en anglais, pour vos logs : ne l'affichez pas au voyageur.
Compte voyageur
Le compte suit la gestion de compte de TOMP (/customers/{id}). L'identifiant current désigne le voyageur du jeton : utilisez toujours /customers/current, sans stocker d'identifiant Pony. Tout autre identifiant que current ou l'id du voyageur répond 404.
| status | Signification |
|---|---|
| CREATED | Compte créé, sans numéro de téléphone validé. |
| OTP_REQUIRED | Code SMS envoyé, en attente de validation. |
| ACTIVE | Numéro validé. |
| BLOCKED | Compte bloqué par Pony : réservation refusée. |
Numéro validé obligatoire pour réserver. Seuls les comptes ACTIVE peuvent réserver : un compte CREATED ou OTP_REQUIRED reçoit CUSTOMER_NOT_ACTIVE (3501). Le numéro se valide une seule fois, par SMS (ci-dessous).
En QA, les SMS sont réellement envoyés : testez avec un vrai mobile français.
« Numéro validé » correspond à
status = ACTIVE. Le YAML TOMP 1.6.1 décritOTP_REQUIREDmais l'a oublié dans l'enumcustomerAccountStatus: acceptez cette valeur.
Lire le profil
{
"id": "Xb7KqP2mT9sW4nLd8RcV",
"externalId": "f3b1c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
"firstName": "Camille",
"lastName": "Martin",
"email": "camille.martin@example.com",
"birthDate": "1990-04-12",
"phones": [],
"status": "CREATED",
"creationDate": "2026-10-20T08:12:03Z"
}
externalIdest votresub.creationDateest la date de création du compte Pony, au premier échange. Les champs inconnus (nom, date de naissance absents du jeton) sont omis.phonesest vide tant qu'aucun numéro n'est enregistré, puis contient exactement un numéro (Pony n'en gère qu'un par compte) : en attente (OTP_REQUIRED) ou validé (ACTIVE).
Modifier le nom
{ "firstName": "Camille", "lastName": "Durand" }
{
"id": "Xb7KqP2mT9sW4nLd8RcV",
"externalId": "f3b1c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
"firstName": "Camille",
"lastName": "Durand",
"email": "camille.martin@example.com",
"birthDate": "1990-04-12",
"phones": [],
"status": "CREATED",
"creationDate": "2026-10-20T08:12:03Z"
}
firstNameetlastNamese modifient ensemble ou séparément (chaîne non vide). Un champ absent ou ànullne change rien. Les autres champs du profil (e-mail, date de naissance…) ne sont pas modifiables et sont ignorés.phonesetextraFields.otpservent à la validation SMS ci-dessous et s'envoient seuls dans leur appel.
Enregistrer le numéro et envoyer le code
Un seul appel enregistre le numéro et envoie le SMS. Pour renvoyer un code, renvoyez le même appel avec le même numéro.
{ "phones": [{ "number": "+33612345678", "kind": "MOBILE" }] }
{
"id": "Xb7KqP2mT9sW4nLd8RcV",
"externalId": "f3b1c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
"firstName": "Camille",
"lastName": "Martin",
"email": "camille.martin@example.com",
"birthDate": "1990-04-12",
"phones": [{ "number": "+33612345678", "kind": "MOBILE" }],
"status": "OTP_REQUIRED",
"creationDate": "2026-10-20T08:12:03Z"
}
| Règle | Valeur |
|---|---|
| Format du numéro | Mobile français uniquement, au format E.164 : +336… ou +337… (12 caractères, ex. +33612345678). Tout autre numéro : INVALID_PHONE_NUMBER. |
| Code | 6 chiffres, valable 10 minutes. |
| Renvoi | Possible 60 secondes après l'envoi précédent. Avant : OTP_ALREADY_ACTIVE. Chaque envoi invalide le code précédent. |
| Changement de numéro | Tant que le statut est OTP_REQUIRED, un autre numéro remplace le numéro en attente et reçoit un nouveau code (mêmes limites). |
| Limite | 5 SMS par heure et par compte. Au-delà : TOO_MANY_REQUESTS. |
| Numéro validé | Ne peut plus être modifié par l'API : PHONE_NUMBER_ALREADY_VALIDATED. |
| Envoi | phones (un seul numéro) ou extraFields.otp (chaîne), seul dans son appel (sinon 400 INVALID_FIELD). |
| Unicité | Un numéro ne valide qu'un seul compte de votre réseau (même maas-id). Vérifié à la validation du code : PHONE_NUMBER_ALREADY_USED. |
| Compte bloqué | Aucun SMS n'est envoyé : CUSTOMER_BLOCKED. |
Valider le code
{ "extraFields": { "otp": "482915" } }
{
"id": "Xb7KqP2mT9sW4nLd8RcV",
"externalId": "f3b1c2a4-5d6e-4f70-8a9b-0c1d2e3f4a5b",
"firstName": "Camille",
"lastName": "Martin",
"email": "camille.martin@example.com",
"birthDate": "1990-04-12",
"phones": [{ "number": "+33612345678", "kind": "MOBILE", "preferred": true }],
"status": "ACTIVE",
"creationDate": "2026-10-20T08:12:03Z"
}
extraFields.otpsuit l'exemple de la spec TOMP ;extraInfo.otp(le champ du schémacustomer) est aussi accepté. Les 4 premiers codes erronés répondentINVALID_OTP; le 5e répondTOO_MANY_ATTEMPTSet invalide le code. Renvoyez alors le numéro pour en recevoir un nouveau. Un code envoyé sur un compte déjàACTIVE(rejeu après un timeout) répond200avec le compte.
Erreurs du compte. Sur /customers, TOMP impose une liste d'erreurs (errors) plutôt qu'un objet seul, y compris pour les erreurs générales (7008, 7001, 7501). Toutes les erreurs ci-dessous, sauf INVALID_FIELD, concernent la validation SMS.
{
"errors": [
{
"errorcode": 8502,
"title": "OTP_ALREADY_ACTIVE",
"status": 409,
"detail": "Un code vient de vous être envoyé. Patientez une minute avant d'en demander un nouveau."
}
]
}
| title | errorcode | http_code | Description |
|---|---|---|---|
| INVALID_FIELD | 8002 | 400 | Champ mal formé (ex. prénom vide, plusieurs numéros dans phones, otp qui n'est pas une chaîne), ou phones / extraFields.otp envoyé avec un autre champ. |
| INVALID_PHONE_NUMBER | 8501 | 400 | Numéro absent, hors format E.164, autre qu'un mobile français, ou refusé par l'opérateur SMS (numéro invalide, désinscrit des SMS) : ne réessayez pas avec ce numéro. |
| OTP_ALREADY_ACTIVE | 8502 | 409 | Un code a été envoyé il y a moins de 60 secondes (« code déjà actif »). |
| TOO_MANY_REQUESTS | 8007 | 429 | Plus de 5 SMS dans l'heure pour ce compte (« trop de demandes »). |
| INVALID_OTP | 8504 | 400 | Code erroné. |
| OTP_EXPIRED | 8505 | 400 | Code expiré (plus de 10 minutes) : renvoyez le numéro. |
| NO_PENDING_OTP | 8506 | 409 | Aucun code en attente pour ce compte (jamais envoyé, ou invalidé). |
| TOO_MANY_ATTEMPTS | 8507 | 429 | 5e code erroné : le code est invalidé. |
| PHONE_NUMBER_ALREADY_USED | 8508 | 409 | Ce numéro valide déjà un autre compte de votre réseau (même maas-id). Répondu à la validation du code, jamais à l'envoi. detail invite le voyageur à contacter le support Pony. |
| PHONE_NUMBER_ALREADY_VALIDATED | 8509 | 409 | Le compte a déjà un numéro validé. |
| CUSTOMER_BLOCKED | 8209 | 403 | Compte bloqué par Pony : aucun SMS n'est envoyé. |
| SMS_UNAVAILABLE | 8510 | 503 | Le SMS n'a pas pu être envoyé pour l'instant : réessayez après Retry-After secondes. L'envoi échoué ne compte pas dans les limites. |
Véhicules & stations
TOMP délègue la découverte de la flotte au flux GBFS (externalType: GBFS dans /operator/meta). Utilisez la clé GBFS fournie à l'onboarding.
curl "https://gbfs-dev.getapony.com/v1/orleans/fr/free_bike_status.json?key=$GBFS_API_KEY"
curl "https://gbfs.getapony.com/v1/orleans/fr/free_bike_status.json?key=$GBFS_API_KEY"
_vehicle_idest l'identifiant stable du véhicule. C'est la valeur à passer dansuseAssetsà la réservation, et la même que celle utilisée par l'API MaaS. Après un scan de QR code, inutile de passer par GBFS : réservez directement avec l'identifiant scanné.bike_idchange à chaque appel (RGPD) : ne l'utilisez pas.- Affichez uniquement les véhicules avec
is_disabled: false. Le flux ne publie pas forcément toute la flotte disponible : un véhicule scanné peut être réservé même s'il n'y figure pas. - Les stations sont dans
station_information.jsonetstation_status.json(voir GBFS).
Batterie. Les seuils sont configurés par Pony, par modèle de véhicule. Sous le seuil de déverrouillage, le véhicule n'est plus proposé (absent du flux ou is_disabled: true) et la réservation est refusée (VEHICLE_NOT_AVAILABLE). Plus bas, l'assistance électrique est coupée. Au seuil de verrouillage forcé (selon le modèle), Pony termine le trajet et vous envoie un callback FINISH (LOW_BATTERY).
Vitesse maximale. Pony la fixe lui-même (réglementation et zones de vitesse réduite) : rien à envoyer.
Réservation
Réserver un véhicule
| Champ | Requis | Type | Description |
|---|---|---|---|
| from | requis | place | Position du voyageur : {"coordinates": {"lat", "lng"}}. |
| useAssets | requis | string[] | Exactement un identifiant de véhicule (_vehicle_id GBFS ou QR code). |
Pony ignore les autres champs TOMP de la requête : le voyageur vient du jeton, le départ est immédiat et l'URL de callback est celle déclarée à l'onboarding.
curl -X POST "https://partners-dev.getapony.com/tomp/bookings/one-stop" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept-Language: fr" -H "Api: TOMP" -H "Api-Version: 1.6.1" -H "maas-id: tao" \
-H "Content-Type: application/json" \
-d '{"from":{"coordinates":{"lat":47.902964,"lng":1.909251}},"useAssets":["E021457"]}'
curl -X POST "https://partners.getapony.com/tomp/bookings/one-stop" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Accept-Language: fr" -H "Api: TOMP" -H "Api-Version: 1.6.1" -H "maas-id: tao" \
-H "Content-Type: application/json" \
-d '{"from":{"coordinates":{"lat":47.902964,"lng":1.909251}},"useAssets":["E021457"]}'
Réponse 201, avec les en-têtes Expires: Tue, 20 Oct 2026 08:20:02 GMT et Content-Language: fr :
{
"id": "bk_7Hc2LqW9xZp4TnRs",
"state": "CONFIRMED",
"legs": [
{
"id": "leg_3Vb8KdQ1mYt6WsPa",
"state": "NOT_STARTED",
"from": { "coordinates": { "lat": 47.902964, "lng": 1.909251 } },
"assetType": { "id": "ebike", "assetClass": "BICYCLE" },
"asset": { "id": "E021457", "stateOfCharge": 78 }
}
]
}
- AUTO_COMMIT : la réservation est directement
CONFIRMED, n'envoyez pas deCOMMIT. - Véhicule bloqué : TOMP sépare la réservation du départ.
one-stopbloque le véhicule pour le voyageur jusqu'àExpires(5 minutes) : personne d'autre ne peut le réserver ni le déverrouiller. SansSET_IN_USEavant cette heure, la réservation passeEXPIRED, son legCANCELLED, et le véhicule est libéré. - Un seul trajet à la fois par compte Pony :
one-stopest refusé si le voyageur a déjà une réservationCONFIRMEDou un trajet en cours, y compris un trajet démarré via l'API MaaS pour le même voyageur (qui, lui, n'apparaît pas dansGET /bookings). - Rejeu sans doublon : si le voyageur a déjà une réservation
CONFIRMEDsur le même véhicule,one-stoprenvoie cette réservation (201, mêmeExpires). Un appel rejoué après un timeout ne crée donc pas de doublon. Un appel rejoué pendant que le premier est encore en cours répondONGOING_BOOKING: réessayez après sa réponse. - Deux identifiants :
id(réservation) etlegs[0].id(trajet), toujours différents. Conservez les deux : tous les appels de trajet utilisent l'identifiant du leg. - Batterie :
asset.stateOfChargefigure dans la réponse àone-stop, pas dansGET /bookings/{id}niGET /bookings. - Pony n'annule jamais une réservation
CONFIRMED, sauf par expiration.
| title | errorcode | http_code | Description |
|---|---|---|---|
| CUSTOMER_NOT_ACTIVE | 3501 | 403 | Numéro de téléphone non validé (status ≠ ACTIVE) : validez-le par SMS. |
| CUSTOMER_BLOCKED | 3209 | 403 | Compte bloqué ou restreint par Pony. |
| ONGOING_BOOKING | 3503 | 403 | Le voyageur a déjà une réservation ou un trajet en cours sur un autre véhicule (via cette API ou l'API MaaS), ou une autre requête de réservation en cours. |
| UNKNOWN_VEHICLE | 3504 | 400 | Identifiant de véhicule inconnu, mal formé (lettres, chiffres, _ et - uniquement, 64 caractères au plus) ou hors de la zone de votre réseau. |
| VEHICLE_NOT_AVAILABLE | 3505 | 406 | Véhicule en trajet, réservé, hors ligne, batterie trop faible ou en maintenance. Choisissez un autre véhicule. |
| TEMPORARILY_UNAVAILABLE | 3506 | 503 | Indisponibilité passagère : véhicule pris par une autre requête au même instant, statut en cours de mise à jour juste après une annulation, erreur temporaire chez Pony. Réessayez le même véhicule après Retry-After secondes. |
Un
useAssetsvide ou avec plusieurs identifiants répond400(3002), unfromabsent400(3001).Juste après la fin d'un trajet, un véhicule peut rester indisponible environ une minute, le temps que Pony confirme son verrouillage : réessayez après ce délai.
Annuler une réservation
Annule la réservation et libère le véhicule si le voyageur renonce avant le départ. Utile aussi s'il scanne un autre véhicule : une réservation CONFIRMED bloque toute nouvelle réservation (ONGOING_BOOKING) jusqu'à son expiration.
{ "operation": "CANCEL", "origin": "END_USER" }
{
"id": "bk_7Hc2LqW9xZp4TnRs",
"state": "CANCELLED",
"legs": [
{
"id": "leg_3Vb8KdQ1mYt6WsPa",
"state": "CANCELLED",
"from": { "coordinates": { "lat": 47.902964, "lng": 1.909251 } },
"assetType": { "id": "ebike", "assetClass": "BICYCLE" },
"asset": { "id": "E021457" }
}
]
}
Possible uniquement avant
SET_IN_USE. Une fois le trajet démarré, terminez-le avecFINISH:CANCELrépond alors403(ILLEGAL_STATE,errorcode3004). Une réservation déjà annulée ou expirée répond200sans changement.COMMITest inutile (AUTO_COMMIT) et répond403(3004), commeDENYetEXPIRE; une autreoperationrépond400(3002). UnCANCELreçu pendant une autre opération sur la même réservation (départ en cours) répond403(3004) : relisez la réservation avant de réessayer.
Trajet
Tous les événements de trajet passent par la même route, avec l'identifiant du leg.
| Champ | Requis | Type | Description |
|---|---|---|---|
| time | requis | date-time | Requis par TOMP, avec fuseau. Pony horodate avec sa propre heure de réception. |
| event | requis | string | SET_IN_USE, PAUSE, START_FINISHING ou FINISH. Toute autre valeur répond 400 (4002). |
| userLocation | requis | place | Position du voyageur. |
| event | Depuis | Vers | Déjà dans l'état cible | Réponse |
|---|---|---|---|---|
| SET_IN_USE | NOT_STARTED | IN_USE | 200, leg inchangé | Immédiate : commande envoyée au véhicule. |
| PAUSE | IN_USE | PAUSED | 200, leg inchangé | Après confirmation du verrouillage. |
| SET_IN_USE (reprise) | PAUSED | IN_USE | 200, leg inchangé | Après confirmation du déverrouillage. |
| START_FINISHING | IN_USE, PAUSED | inchangé | — | Immédiate : contrôle sans effet sur le trajet. |
| FINISH | IN_USE, PAUSED | FINISHED | 200, leg inchangé | Après confirmation du verrouillage. |
- Tout autre état répond
403(ILLEGAL_STATE,errorcode4004), avec une exception :SET_IN_USEsur une réservation expirée répondBOOKING_EXPIRED(4202). - Rejeu : grâce à la colonne « déjà dans l'état cible », vous pouvez rejouer sans risque un appel resté sans réponse (ex.
FINISHaprès un timeout client). - Appels simultanés : un
SET_IN_USEreçu pendant un autre départ sur la même réservation répond4004. UnPAUSE, une reprise ou unFINISHreçu pendant une autre opération sur le trajet répond503(VEHICLE_NOT_RESPONDING). Dans les deux cas, relisez le leg puis réessayez. - Durée :
PAUSE, la reprise etFINISHattendent que le véhicule confirme, en général en quelques secondes.PAUSEet la reprise attendent 30 secondes au plus ;FINISHpeut prendre jusqu'à 90 secondes sur certains vélos électriques. Réglez le timeout de votre client à au moins 100 secondes. Retry-After: les réponses503le portent, aujourd'hui5secondes.- Réponse : toujours
200avec le leg à jour (jamais204).
SET_IN_USE : départ
Déverrouille le véhicule et démarre le trajet.
{
"time": "2026-10-20T08:15:03Z",
"event": "SET_IN_USE",
"userLocation": { "coordinates": { "lat": 47.902964, "lng": 1.909251 } }
}
{
"id": "leg_3Vb8KdQ1mYt6WsPa",
"state": "IN_USE",
"actualDepartureTime": "2026-10-20T08:15:04Z",
"from": { "coordinates": { "lat": 47.902964, "lng": 1.909251 } },
"parkingPhotoUploaded": false,
"assetType": { "id": "ebike", "assetClass": "BICYCLE" },
"asset": { "id": "E021457", "stateOfCharge": 78 }
}
Asynchrone. 200 signifie que le trajet est démarré et la commande envoyée : le véhicule se déverrouille en général dans les secondes qui suivent. S'il ne se déverrouille pas, Pony annule le trajet (réservation et leg CANCELLED) et vous envoie un callback CANCEL (UNLOCK_FAILED). En attendant, le leg est IN_USE et accepte PAUSE et FINISH normalement ; un trajet déjà terminé ne reçoit pas de CANCEL. Le champ _locked du webhook véhicule donne l'état réel du verrou.
Cas rare : si le trajet n'est pas créé chez Pony malgré le
200(incident), les événements du leg répondent503(VEHICLE_NOT_RESPONDING) pendant une minute, puis la réservation et le leg passentCANCELLED.
| title | errorcode | http_code | Description |
|---|---|---|---|
| BOOKING_EXPIRED | 4202 | 403 | Réservation expirée (Expires dépassé). |
| VEHICLE_NO_LONGER_AVAILABLE | 4505 | 403 | Le véhicule n'est plus disponible depuis la réservation (hors ligne, batterie). La réservation et le leg passent CANCELLED : refaites une réservation. |
PAUSE
Verrouille le véhicule sans terminer le trajet. Le véhicule reste attribué au voyageur.
{
"time": "2026-10-20T08:24:10Z",
"event": "PAUSE",
"userLocation": { "coordinates": { "lat": 47.899812, "lng": 1.904577 } }
}
{
"id": "leg_3Vb8KdQ1mYt6WsPa",
"state": "PAUSED",
"actualDepartureTime": "2026-10-20T08:15:04Z",
"from": { "coordinates": { "lat": 47.902964, "lng": 1.909251 } },
"parkingPhotoUploaded": false,
"assetType": { "id": "ebike", "assetClass": "BICYCLE" },
"asset": { "id": "E021457", "stateOfCharge": 74 }
}
Si la région fixe une durée maximale de pause, Pony termine le trajet à son échéance et vous envoie un callback
FINISH(PAUSE_TIMEOUT).
| title | errorcode | http_code | Description |
|---|---|---|---|
| VEHICLE_NOT_RESPONDING | 4504 | 503 | Verrouillage non confirmé, ou autre opération en cours sur le trajet. Le leg reste IN_USE : renvoyez PAUSE après Retry-After secondes. |
SET_IN_USE : reprise
TOMP n'a pas d'événement RESUME : la reprise est un SET_IN_USE sur un leg PAUSED. Contrairement au départ, Pony attend que le véhicule confirme le déverrouillage.
{
"time": "2026-10-20T08:29:45Z",
"event": "SET_IN_USE",
"userLocation": { "coordinates": { "lat": 47.899812, "lng": 1.904577 } }
}
{
"id": "leg_3Vb8KdQ1mYt6WsPa",
"state": "IN_USE",
"actualDepartureTime": "2026-10-20T08:15:04Z",
"from": { "coordinates": { "lat": 47.902964, "lng": 1.909251 } },
"parkingPhotoUploaded": false,
"assetType": { "id": "ebike", "assetClass": "BICYCLE" },
"asset": { "id": "E021457", "stateOfCharge": 74 }
}
actualDepartureTimegarde l'heure du départ initial.
| title | errorcode | http_code | Description |
|---|---|---|---|
| VEHICLE_NOT_RESPONDING | 4504 | 503 | Déverrouillage non confirmé, ou autre opération en cours sur le trajet. Le leg reste PAUSED : renvoyez SET_IN_USE après Retry-After secondes. |
START_FINISHING : contrôle de fin
Recommandé avant de demander au voyageur de se garer et de prendre la photo : c'est le seul contrôle de stationnement, Pony ne le refait pas au FINISH. Pony vérifie la position (zones de stationnement, zones interdites, capacité des parkings) sans toucher au trajet : le leg garde son état et le véhicule n'est pas verrouillé.
{
"time": "2026-10-20T08:34:20Z",
"event": "START_FINISHING",
"userLocation": { "coordinates": { "lat": 47.895390, "lng": 1.900820 } }
}
Si la fin est possible : 200 avec le leg. La condition conditionRequireOffboardingSteps liste les étapes, son id porte le verdict Pony, et userCommunication contient l'avertissement à afficher au voyageur, s'il y en a un :
{
"id": "leg_3Vb8KdQ1mYt6WsPa",
"state": "IN_USE",
"actualDepartureTime": "2026-10-20T08:15:04Z",
"from": { "coordinates": { "lat": 47.902964, "lng": 1.909251 } },
"parkingPhotoUploaded": false,
"assetType": { "id": "ebike", "assetClass": "BICYCLE" },
"asset": { "id": "E021457", "stateOfCharge": 71 },
"conditions": [
{
"conditionType": "conditionRequireOffboardingSteps",
"id": "NOT_IN_PARKING_ZONE",
"steps": [
{ "action": "SEND_EVIDENCE_PARKED", "type": "PLAIN_TEXT", "text": "Prenez une photo du véhicule garé." },
{ "action": "SEND_FINISH", "type": "PLAIN_TEXT", "text": "Terminez le trajet." }
]
}
],
"userCommunication": [
{
"type": "PLAIN_TEXT",
"goal": "INSTRUCTIONS",
"text": "Vous n'êtes pas dans une zone de stationnement. Garez-vous dans une zone dédiée, ou prenez une photo si vous pensez qu'il s'agit d'une erreur."
}
]
}
| Verdict | Fin | Ce que le voyageur doit faire |
|---|---|---|
| TAKE_PICTURE | autorisée | Photo du véhicule garé. Pas d'avertissement. |
| NOT_IN_PARKING_ZONE | autorisée, avec avertissement | Hors zone de stationnement : se déplacer, ou continuer avec une photo. |
| PARKING_NOT_FOR_THIS_VEHICLE_TYPE | autorisée, avec avertissement | Parking non prévu pour ce type de véhicule : se déplacer, ou continuer avec une photo. |
| JAUGE_FAILED | autorisée, avec avertissement | Parking déjà plein : se déplacer, ou continuer avec une photo. |
| FORBIDDEN_PARKING | refusée (403) | Zone interdite ou hors zone d'exploitation : continuer à rouler. |
| STRICT_PARKING | refusée (403) | Stationnement obligatoire sur un emplacement dédié. |
| JAUGE_FAILED_STRICT | refusée (403) | Parking plein et capacité stricte : aller sur un autre parking. |
Pony peut ajouter des verdicts : traitez un
idinconnu comme une fin autorisée, avec photo. Pour un verdict sans avertissement (TAKE_PICTURE),userCommunicationest absent.
Textes affichés au voyageur (Accept-Language: fr) :
| Verdict | Texte (userCommunication ou detail) |
|---|---|
| NOT_IN_PARKING_ZONE | Vous n'êtes pas dans une zone de stationnement. Garez-vous dans une zone dédiée, ou prenez une photo si vous pensez qu'il s'agit d'une erreur. |
| PARKING_NOT_FOR_THIS_VEHICLE_TYPE | Ce stationnement n'est pas prévu pour ce type de véhicule. Garez-vous dans une zone adaptée, ou prenez une photo si vous pensez qu'il s'agit d'une erreur. |
| JAUGE_FAILED | Ce parking est déjà plein. Garez-vous sur un autre parking, ou prenez une photo si vous pensez qu'il s'agit d'une erreur. |
| FORBIDDEN_PARKING | Le stationnement est interdit ici. Continuez votre trajet jusqu'à une zone autorisée. |
| STRICT_PARKING | Le stationnement est obligatoire sur un emplacement dédié. Rejoignez une zone de stationnement pour terminer votre trajet. |
| JAUGE_FAILED_STRICT | Ce parking est plein. Rejoignez un autre parking pour terminer votre trajet. |
Si la fin est refusée : 403, le leg reste dans son état, et detail contient le message à afficher. Bloquez alors la fin du trajet dans votre application, puisque FINISH ne refait pas ce contrôle :
{
"errorcode": 4501,
"title": "FORBIDDEN_PARKING",
"status": 403,
"detail": "Le stationnement est interdit ici. Continuez votre trajet jusqu'à une zone autorisée."
}
Les textes (
text,detail) sont localisés selonAccept-Languageet destinés au voyageur : affichez-les tels quels.
| title | errorcode | http_code | Description |
|---|---|---|---|
| FORBIDDEN_PARKING | 4501 | 403 | Zone interdite ou hors zone d'exploitation. |
| STRICT_PARKING | 4502 | 403 | Stationnement obligatoire sur un emplacement dédié. |
| JAUGE_FAILED_STRICT | 4503 | 403 | Parking plein. |
Photo de stationnement
Extension Pony, absente de TOMP. Dépose la photo du véhicule garé, que Pony conserve comme preuve en cas de stationnement contesté. Envoyez-la après le contrôle de fin et avant FINISH : tous les verdicts qui autorisent la fin la demandent (SEND_EVIDENCE_PARKED).
Le corps est la photo elle-même : une image JPEG de 5 Mo au plus (5 242 880 octets). Les en-têtes sont ceux des autres appels, avec Content-Type: image/jpeg.
curl -X POST "https://partners-dev.getapony.com/tomp/legs/leg_3Vb8KdQ1mYt6WsPa/parking-photo" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Accept-Language: fr" -H "Api: TOMP" -H "Api-Version: 1.6.1" -H "maas-id: tao" \ -H "Content-Type: image/jpeg" \ --data-binary @photo.jpg
curl -X POST "https://partners.getapony.com/tomp/legs/leg_3Vb8KdQ1mYt6WsPa/parking-photo" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Accept-Language: fr" -H "Api: TOMP" -H "Api-Version: 1.6.1" -H "maas-id: tao" \ -H "Content-Type: image/jpeg" \ --data-binary @photo.jpg
Réponse 201 avec le leg à jour :
{
"id": "leg_3Vb8KdQ1mYt6WsPa",
"state": "IN_USE",
"actualDepartureTime": "2026-10-20T08:15:04Z",
"from": { "coordinates": { "lat": 47.902964, "lng": 1.909251 } },
"parkingPhotoUploaded": true,
"assetType": { "id": "ebike", "assetClass": "BICYCLE" },
"asset": { "id": "E021457", "stateOfCharge": 71 }
}
- États : leg
IN_USEouPAUSED. Tout autre état répond403(ILLEGAL_STATE,4004) : une fois le legFINISHED, la photo ne peut plus être déposée. - Une photo par trajet : un nouvel envoi répond
409(4508) et ne remplace pas la première. Si vous rejouez un envoi resté sans réponse,4508signifie que Pony a bien la photo de ce trajet. - Facultative : Pony ne refuse jamais un
FINISHsans photo, pour qu'un échec d'envoi ne bloque pas le voyageur. Après un503, renvoyez la photo aprèsRetry-Aftersecondes, avantFINISH. - Suivi :
parkingPhotoUploadedfigure sur le leg dès que le trajet a démarré, dans toutes les réponses qui le contiennent :falsetant que Pony n'a pas reçu la photo. - Cette route n'est pas déclarée dans
/operator/meta, qui ne liste que les routes TOMP.
| title | errorcode | http_code | Description |
|---|---|---|---|
| PHOTO_TOO_LARGE | 4506 | 413 | Photo de plus de 5 Mo. |
| INVALID_PHOTO | 4507 | 415 | Le corps n'est pas une image JPEG, ou il est vide. |
| PHOTO_ALREADY_UPLOADED | 4508 | 409 | Pony a déjà la photo de ce trajet. |
| PHOTO_STORAGE_UNAVAILABLE | 4509 | 503 | La photo n'a pas pu être enregistrée : renvoyez-la après Retry-After secondes. |
| VEHICLE_NOT_RESPONDING | 4504 | 503 | Trajet pas encore créé chez Pony juste après le départ (voir départ) : renvoyez la photo après Retry-After secondes. |
FINISH : fin du trajet
Termine le trajet : Pony verrouille le véhicule, attend la confirmation, puis termine le trajet. Pony ne refait pas le contrôle de fin : n'envoyez FINISH qu'après un START_FINISHING qui autorise la fin.
{
"time": "2026-10-20T08:34:40Z",
"event": "FINISH",
"userLocation": { "coordinates": { "lat": 47.895390, "lng": 1.900820 } }
}
{
"id": "leg_3Vb8KdQ1mYt6WsPa",
"state": "FINISHED",
"actualDepartureTime": "2026-10-20T08:15:04Z",
"actualArrivalTime": "2026-10-20T08:34:41Z",
"from": { "coordinates": { "lat": 47.902964, "lng": 1.909251 } },
"to": { "coordinates": { "lat": 47.895390, "lng": 1.900820 } },
"parkingPhotoUploaded": true,
"assetType": { "id": "ebike", "assetClass": "BICYCLE" },
"asset": { "id": "E021457", "stateOfCharge": 71 }
}
Photo de stationnement. FINISH ne transmet pas la photo : déposez-la avant, avec sa route dédiée. Pony ne refuse pas un FINISH sans photo, pour qu'un échec d'envoi ne bloque jamais le voyageur.
Distance. La réponse à FINISH contient une distance estimée (distance, en mètres), que TOMP définit comme une estimation. La valeur définitive est calculée après la fin du trajet, en quelques minutes, et la remplace dans GET /legs/{id} et GET /bookings. Elle peut différer : ne conservez pas la valeur de FINISH comme définitive.
| title | errorcode | http_code | Description |
|---|---|---|---|
| VEHICLE_NOT_RESPONDING | 4504 | 503 | Verrouillage non confirmé, ou autre opération en cours sur le trajet. Le trajet continue : renvoyez FINISH après Retry-After secondes. |
Si le véhicule ne confirme toujours pas après 3 tentatives, invitez le voyageur à contacter le support Pony : l'agent termine le trajet à distance et vous recevez un callback
FINISH(SUPPORT).
Suivi des trajets
Un trajet
État courant d'un leg. C'est l'appel de reprise : si l'application redémarre en cours de trajet, il dit où en est le trajet.
{
"id": "leg_3Vb8KdQ1mYt6WsPa",
"state": "FINISHED",
"actualDepartureTime": "2026-10-20T08:15:04Z",
"actualArrivalTime": "2026-10-20T08:34:41Z",
"distance": 2140,
"from": { "coordinates": { "lat": 47.902964, "lng": 1.909251 } },
"to": { "coordinates": { "lat": 47.895390, "lng": 1.900820 } },
"parkingPhotoUploaded": true,
"assetType": { "id": "ebike", "assetClass": "BICYCLE" },
"asset": { "id": "E021457", "stateOfCharge": 71 },
"progressGeometry": {
"type": "LineString",
"coordinates": [[1.909251, 47.902964], [1.905102, 47.899870], [1.900820, 47.895390]]
}
}
| legState | Signification |
|---|---|
| NOT_STARTED | Véhicule réservé, pas encore déverrouillé. |
| IN_USE | Trajet en cours. |
| PAUSED | En pause, véhicule verrouillé. |
| FINISHED | Terminé, par vous ou par Pony. |
| CANCELLED | Réservation annulée ou expirée, ou déverrouillage échoué. |
asset.stateOfCharge: batterie actuelle tant que le trajet n'est pas terminé, batterie de fin une foisFINISHED, absente siCANCELLED.toetactualArrivalTimen'apparaissent qu'une fois le trajet terminé.parkingPhotoUploaded:truesi Pony a reçu la photo de stationnement,falsesinon. Présent dès que le trajet a démarré.progressGeometry: le tracé du trajet, au format GeoJSONLineString(paires[lng, lat], comme dans TOMP). Présent uniquement surGET /legs/{id}et une fois le trajetFINISHED: ni pendant le trajet, ni dans la réponse àFINISH, ni dansGET /bookings. Juste après la fin, les derniers points peuvent manquer : il est complet après quelques minutes, comme la distance. Absent si Pony a moins de 2 positions pour le trajet.
Une réservation
Renvoie la réservation et ses legs, au même format que la réservation, sans stateOfCharge. state vaut CONFIRMED, STARTED, FINISHED, CANCELLED ou EXPIRED.
Trajet en cours et historique
Les réservations du voyageur du jeton, de la plus récente à la plus ancienne. Équivaut à ongoing_trip et trips de l'API MaaS.
| Paramètre | Description |
|---|---|
| state | STARTED pour le trajet en cours, FINISHED pour l'historique, ou CONFIRMED, CANCELLED, EXPIRED. Sans filtre : tous les états. Toute autre valeur répond 400 (3002). |
| min-time / max-time | Bornes RFC 3339 avec fuseau, incluses, sur la date de création de la réservation. |
| offset / limit | Pagination (extension Pony). offset vaut 0 par défaut. limit vaut 20 par défaut, de 1 à 50 ; hors de ces bornes, l'appel répond 400 (3002). Une page incomplète signifie qu'il n'y a pas d'autre résultat. |
curl "https://partners-dev.getapony.com/tomp/bookings?state=FINISHED&limit=20" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Accept-Language: fr" -H "Api: TOMP" -H "Api-Version: 1.6.1" -H "maas-id: tao"
curl "https://partners.getapony.com/tomp/bookings?state=FINISHED&limit=20" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Accept-Language: fr" -H "Api: TOMP" -H "Api-Version: 1.6.1" -H "maas-id: tao"
[
{
"id": "bk_7Hc2LqW9xZp4TnRs",
"state": "FINISHED",
"legs": [
{
"id": "leg_3Vb8KdQ1mYt6WsPa",
"state": "FINISHED",
"actualDepartureTime": "2026-10-20T08:15:04Z",
"actualArrivalTime": "2026-10-20T08:34:41Z",
"distance": 2140,
"from": { "coordinates": { "lat": 47.902964, "lng": 1.909251 } },
"to": { "coordinates": { "lat": 47.895390, "lng": 1.900820 } },
"parkingPhotoUploaded": true,
"assetType": { "id": "ebike", "assetClass": "BICYCLE" },
"asset": { "id": "E021457" }
}
]
}
]
Aucun résultat :
200avec[]. Le véhicule est danslegs[].asset.id. Seules les réservations faites via cette API sont listées.
Callbacks Pony → MaaS
TOMP est bidirectionnel : quand un trajet change d'état sans appel de votre part, Pony envoie l'événement à votre propre route /legs/{id}/events. Sans ces callbacks, votre application afficherait un trajet en cours alors que Pony l'a terminé.
callback_base_url et api_key sont ceux que vous communiquez à l'onboarding ; la même clé sert au webhook véhicule. Comme le callbackUrl de TOMP, callback_base_url est une URL de base : Pony y ajoute /legs/{id}/events (un / final est ignoré). Pony envoie aussi les en-têtes Content-Type: application/json, Accept-Language: fr, Api: TOMP, Api-Version: 1.6.1, maas-id: pony et addressed-to: {votre maas-id}.
| event | Quand | comment |
|---|---|---|
| FINISH | Durée maximale de trajet dépassée (vérifiée toutes les 5 minutes : le callback peut arriver jusqu'à 5 minutes après). | RIDE_TIMEOUT |
| FINISH | Batterie sous le seuil de verrouillage forcé. | LOW_BATTERY |
| FINISH | Durée maximale de pause dépassée. | PAUSE_TIMEOUT |
| FINISH | Trajet terminé par le support Pony. | SUPPORT |
| CANCEL | Le véhicule ne s'est pas déverrouillé après SET_IN_USE. | UNLOCK_FAILED |
curl -X POST "https://api.maas.example/tomp/legs/leg_8Tz4WcN2pLq7XyRd/events?app_id=pony&api_key=ZZZZ" \
-H "Content-Type: application/json" -H "Accept-Language: fr" \
-H "Api: TOMP" -H "Api-Version: 1.6.1" -H "maas-id: pony" -H "addressed-to: tao" \
-d '{"time":"2026-10-20T11:42:10Z","event":"FINISH","comment":"RIDE_TIMEOUT","userLocation":{"coordinates":{"lat":47.90784,"lng":1.91562}},"asset":{"id":"E019802","stateOfCharge":41}}'
timeest l'heure de fin du trajet chez Pony.userLocationest la dernière position connue du véhicule etasset.stateOfChargesa batterie en fin de trajet ; ils sont omis s'ils sont inconnus.commentest toujours l'un des codes du tableau ci-dessus.
Ce qui ne donne pas de callback. Une fin demandée par votre FINISH, et les autres fins décidées par Pony que le tableau ne liste pas. Pour celles-ci, GET /legs/{id} fait foi.
Répondez par un code 2xx (ex. 200 ou 204), en moins de 10 secondes : une réponse plus lente compte comme un échec. Tout autre code, y compris 4xx, ou une absence de réponse déclenche des relances avec backoff exponentiel (de 75 secondes à 6 minutes entre deux tentatives), pendant 7 jours au plus. Pony envoie au plus un événement par trajet, mais il peut arriver deux fois : dédupliquez sur id du leg + event. En cas de doute, GET /legs/{id} fait foi.
Webhook véhicule
Si vous avez déclaré une URL de webhook véhicule, Pony vous envoie l'état de chaque véhicule des régions de votre réseau, en trajet ou non, à chaque changement de position, de niveau de batterie ou de verrouillage.
Ce webhook est propre à Pony (hors TOMP) et complète les callbacks : eux portent sur un trajet, lui sur un véhicule. Hors trajet, il recoupe le GBFS en vous poussant les changements. Pendant un trajet, c'est la seule source de la position du véhicule et de l'état réel de son verrou. Il est facultatif : si votre application n'a pas besoin de ces informations, ne déclarez pas d'URL.
Le corps est un véhicule au format GBFS 2.2 (free_bike_status), enrichi des champs Pony préfixés _ :
| Champ | Type | Description |
|---|---|---|
| _vehicle_id | string | Identifiant stable du véhicule (celui de useAssets). |
| lat / lon | float | Position courante. |
| current_fuel_percent | float | Batterie, de 0.0 à 1.0. |
| current_range_meters | int | Autonomie restante estimée, en mètres. |
| is_disabled | boolean | true si le véhicule ne peut pas être réservé (en trajet, batterie, hors ligne, maintenance). |
| _locked | boolean | Verrou fermé. |
| _online | boolean | Véhicule connecté. |
| _max_speed | float | Vitesse maximale appliquée, en km/h. |
| _battery_hatch_locked | boolean | Trappe batterie verrouillée. |
| station_id | string | Présent si le véhicule est dans une zone de stationnement. |
| vehicle_type_id | string | ebike, scooter ou bike. |
| last_reported | int | Horodatage Unix de l'envoi. |
| _event_uuid | string | Identifiant de la notification, identique d'une relance à l'autre. |
{
"system_id": "f190d330-b49e-4590-871b-0bcbec565a8c",
"last_reported": 1792485300,
"lat": 47.89539,
"lon": 1.90082,
"is_reserved": false,
"is_disabled": false,
"vehicle_type_id": "ebike",
"pricing_plan_id": "Orleans_ebike",
"rental_uris": { "android": "", "ios": "" },
"bike_id": "pony_c53dd513d7ad4dad8b9471a8e0fbe45a",
"current_fuel_percent": 0.71,
"current_range_meters": 41200,
"_vehicle_id": "E021457",
"_max_speed": 25.0,
"_battery_hatch_locked": true,
"_locked": true,
"_online": true,
"_event_uuid": "8c1e2f0a-5b7d-4e3a-9f61-2d4c8b0e7a13"
}
- État, pas événement : le corps reflète l'état du véhicule au moment de l'envoi, et le type de changement n'est pas indiqué. Tant qu'une notification est en cours de livraison pour un véhicule, les changements suivants sont regroupés dans la prochaine : vous pouvez ne pas voir chaque état intermédiaire.
- Relances : comme les callbacks (réponse 2xx attendue en moins de 10 secondes, backoff de 75 secondes à 6 minutes, 7 jours au plus). Chaque relance renvoie l'état à jour, avec le même
_event_uuid. - Ordre par véhicule : les notifications d'un même véhicule sont livrées dans l'ordre. Tant que l'une échoue, les suivantes de ce véhicule attendent.
- Doublons : une notification déjà acquittée peut revenir avec le même
_event_uuid. Dédupliquez sur_event_uuid. - Réservation :
is_reservedvaut toujoursfalse. Un véhicule réservé (via cette API ou une autre application) apparaîtis_disabled: true. - Pony n'envoie rien pour un véhicule dont l'état ne peut pas être lu (véhicule retiré de la flotte, par exemple).
bike_idchange à chaque envoi : utilisez_vehicle_id.
Erreurs
| http_code | Signification |
|---|---|
400 | Requête invalide : champ manquant (x001, MISSING_FIELD) ou mal formé (x002, INVALID_FIELD) ; detail nomme le champ, ex. Champ manquant : userLocation. Un corps qui n'est pas un objet JSON donne x002 sur le champ body. En-têtes TOMP : voir Conventions. |
401 | Jeton absent, invalide, expiré ou émis pour un autre maas-id : refaites l'échange. Corps : INVALID_TOKEN (7501). |
403 | Opération refusée : état du trajet, compte, stationnement. |
404 | Ressource inconnue ou appartenant à un autre voyageur (sans corps). |
406 | Réservation impossible : véhicule indisponible. |
409 | Conflit sur le compte (SMS, numéro), ou photo de stationnement déjà reçue (4508). |
413 | Photo de stationnement de plus de 5 Mo (4506). |
415 | Photo de stationnement qui n'est pas une image JPEG (4507). |
429 | Trop de demandes (SMS). |
503 | Le véhicule n'a pas confirmé la commande, une autre opération est en cours (4504), indisponibilité passagère à la réservation (3506), SMS impossible à envoyer (8510), ou photo de stationnement impossible à enregistrer (4509). En-tête Retry-After en secondes. |
500 | Erreur interne Pony : INTERNAL_ERROR (7500), au format TOMP. |
Le corps suit l'objet error de TOMP. Sur /customers, il est enveloppé dans une liste errors (voir Compte voyageur).
{
"errorcode": 3501,
"title": "CUSTOMER_NOT_ACTIVE",
"status": 403,
"detail": "Validez votre numéro de téléphone pour réserver un vélo."
}
| Champ | Description |
|---|---|
| errorcode | Code numérique, selon la convention TOMP : le premier chiffre donne le module (3 réservation, 4 trajet, 8 compte). Les codes x001 à x009 (techniques) et x201 à x211 (fonctionnels) sont ceux du standard, ex. 4001 champ manquant, 4002 champ invalide, 4004 opération illégale dans l'état courant, 4202 réservation expirée, 3209 compte bloqué. Les codes à partir de x500 sont propres à Pony. Le module 7 regroupe les erreurs générales (en-têtes, jeton). |
| title | Code stable et lisible (CUSTOMER_NOT_ACTIVE) : c'est lui qu'il faut tester dans votre code. |
| status | Code HTTP. |
| detail | Message localisé selon Accept-Language, affichable au voyageur. |
Contrairement à l'API MaaS, detail (et le text des conditions de fin) est destiné au voyageur : vous pouvez l'afficher tel quel.
Les codes propres à chaque appel sont listés dans leur section : compte, réservation, départ, pause, reprise, contrôle de fin, photo, fin. Les erreurs d'état (ILLEGAL_STATE) suivent le tableau des transitions.
Métadonnées
GET /operator/meta décrit, au format TOMP, ce que Pony implémente : endpoints, délégation à GBFS et process identifiers (AUTO_COMMIT). Contenu statique, sans en-tête Authorization ; les autres en-têtes TOMP restent requis. Un chemin GBFS est déclaré par région de votre réseau ; ajoutez-y votre clé key. baseUrl est celle de l'environnement appelé.
[
{
"version": "1.6.1",
"baseUrl": "https://partners.getapony.com/tomp",
"endpoints": [
{ "method": "GET", "externalType": "GBFS", "path": "https://gbfs.getapony.com/v1/orleans/fr/free_bike_status.json", "useAssets": ["_vehicle_id"], "status": "IMPLEMENTED" },
{ "method": "POST", "path": "/bookings/one-stop", "status": "IMPLEMENTED" },
{ "method": "POST", "path": "/bookings/{id}/events", "eventType": "CANCEL", "status": "IMPLEMENTED" },
{ "method": "GET", "path": "/bookings/{id}", "status": "IMPLEMENTED" },
{ "method": "GET", "path": "/bookings", "status": "DIALECT", "supportsPaging": true, "maxPageSize": 50 },
{ "method": "POST", "path": "/legs/{id}/events", "eventType": "SET_IN_USE", "status": "IMPLEMENTED" },
{ "method": "POST", "path": "/legs/{id}/events", "eventType": "PAUSE", "status": "IMPLEMENTED" },
{ "method": "POST", "path": "/legs/{id}/events", "eventType": "START_FINISHING", "status": "IMPLEMENTED" },
{ "method": "POST", "path": "/legs/{id}/events", "eventType": "FINISH", "status": "IMPLEMENTED" },
{ "method": "GET", "path": "/legs/{id}", "status": "IMPLEMENTED" },
{ "method": "GET", "path": "/customers/{id}", "status": "IMPLEMENTED" },
{ "method": "PATCH", "path": "/customers/{id}", "status": "IMPLEMENTED" }
],
"scenarios": [],
"processIdentifiers": {
"operatorInformation": [],
"planning": [],
"booking": ["ONE_STOP", "AUTO_COMMIT"],
"tripExecution": [
"LOCK_UNLOCK_REMOTELY",
"LEG_EVENT_LOCATION_REQUIRED_SET_IN_USE",
"LEG_EVENT_LOCATION_REQUIRED_PAUSE",
"LEG_EVENT_LOCATION_REQUIRED_FINISH"
],
"support": [],
"payment": [],
"general": []
}
}
]
Onboarding & périmètre
Ce que chacun fournit
| De | Vers | Élément |
|---|---|---|
| Pony | MaaS | client_id / client_secret pour /oauth/token, un couple par environnement (QA, production). |
| Pony | MaaS | Votre maas-id et la clé GBFS. |
| Pony | MaaS | L'URL de la passerelle Pony (https://partners.getapony.com/tomp, et son équivalent QA), pour déclarer le client Pony dans votre realm. |
| MaaS | Pony | L'émetteur (iss), l'URL JWKS de votre realm Keycloak et la valeur attendue de azp (ou aud), par environnement. |
| MaaS | Pony | callback_base_url, l'URL du webhook véhicule (facultatif) et une clé (api_key) que Pony présente sur les deux, par environnement. |
Pas dans cette version
- Tarification, paiement et pénalités : pas de
pricingdans les réponses ni de route de paiement ; les pénalités de stationnement seront traitées avec le paiement. Le modèle est en cours de définition avec le réseau. - Coupons et avantages.
- Signalement de problème (
POST /support/), évaluation du trajet, suivi en temps réel (GET /legs/{id}/progress) et suppression de compte (DELETE /customers/{id}). - Commandes de flotte (
ring,unlock_battery_hatch,set_max_speed) : propres à l'API MaaS et à ses trajets.