Pony for partners
Support technique ↗
Pony · API partenaires

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).

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
GBFSgbfs-dev.getapony.comgbfs.getapony.com
MDS / NeTExmds-dev.getapony.commds.getapony.com
MaaSpartners-dev.getapony.compartners.getapony.com
MaaS TOMPpartners-dev.getapony.com/tomppartners.getapony.com/tomp
i

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
GBFSClé key en query param, sur tous les feedsLecture seule
MDS / NeTExClé key en query param, une clé par ville/agenceLecture seule, restreinte GCP
MaaSapi_key + app_id en query paramLecture/écriture, scopée à votre app_id
MaaS TOMPSSO : jeton voyageur échangé sur /oauth/token, puis Authorization: BearerLecture/écriture, limitée au voyageur du jeton

Vos identifiants (clé GBFS, clé MDS, ou couple api_key/app_id MaaS) vous sont communiqués individuellement à l'onboarding — ils ne sont jamais partagés entre partenaires.

Pony Partner APIs Une question d'intégration ? dev@getapony.com
Lecture seule · clé d'API

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

GET https://gbfs-dev.getapony.com/v1/{region_id}/{language}/gbfs.json?key={gbfs_api_key}https://gbfs.getapony.com/v1/{region_id}/{language}/gbfs.json?key={gbfs_api_key}
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

GET https://gbfs-dev.getapony.com/v3/{region_id}/gbfs.json?key={gbfs_api_key}https://gbfs.getapony.com/v3/{region_id}/gbfs.json?key={gbfs_api_key}

La clé key est requise sur tous les feeds : sans elle, l'appel répond 401. region_id est l'identifiant de la zone opérée (ex. nantes, bordeaux, orleans) ; language est fr ou en pour le v2.2.

Feeds & versions

Feed Contenu
system_informationMétadonnées de l'opérateur et de la région (nom, fuseau horaire, langue, contact).
vehicle_typesTypes 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_informationPosition et attributs statiques des stations.
station_statusOccupation et disponibilité en temps réel des stations.
system_pricing_plansGrille tarifaire applicable à la région.
geofencing_zonesZones de restriction (no-parking, no-ride, vitesse réduite…).
i

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_idstringIdentifiant GBFS régénéré à chaque appel — ne pas utiliser pour un suivi persistant, préférer _vehicle_id.
_vehicle_idstringIdentifiant stable du véhicule ([A-Za-z0-9_-], ≤ 200 caractères).
lat / lonfloatPosition GPS courante.
current_fuel_percentfloatNiveau de batterie, entre 0.0 et 1.0.
is_disabledbooleanVéhicule hors-service / offline.
station_idstringPrésent seulement si le véhicule est actuellement rattaché à une station.
_max_speedfloatVitesse maximale actuellement autorisée sur le véhicule, en km/h.
_battery_hatch_lockedbooleanÉtat de verrouillage de la trappe batterie.
_onlinebooleanConnectivité 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_idstringIdentifiant stable de la station.
namestringNom public affichable de la station.
lat / lonfloatPosition GPS.
is_virtual_stationbooleanZone logique sans infrastructure physique (toujours true chez Pony).
_vehicle_idsstring[]Véhicules actuellement rattachés à la station (régions où le partenaire opère la logistique).
_battery_levelfloatNiveau 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
      }
    ]
  }
}
GBFS · Pony Partner APIs Standard officiel : github.com/MobilityData/gbfs
Réservé aux villes & agences

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 ».

GET https://mds-dev.getapony.com/v1/{city}/trips?end_time={end_time}&key={api_key}https://mds.getapony.com/v1/{city}/trips?end_time={end_time}&key={api_key}
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.

GET https://mds-dev.getapony.com/v1/{city}/status_changes?end_time={end_time}&key={api_key}https://mds.getapony.com/v1/{city}/status_changes?end_time={end_time}&key={api_key}
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.

MDS · Pony Partner APIs Standard officiel : Open Mobility Foundation
Réservé aux villes & agences

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.

GET https://mds-dev.getapony.com/{version}/netex/v6/regions/{region}?key={api_key}https://mds.getapony.com/{version}/netex/v6/regions/{region}?key={api_key}
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

region est l'identifiant de zone (ex. paris). La réponse est un fichier application/xml nommé 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_types en 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>
i

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.

NeTEx · Pony Partner APIs Norme officielle : netex-cen.eu
Intégration poussée

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.

i

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éverrouillage6%Sous ce niveau, le véhicule refuse tout nouveau trajet.
Fin d'assistance6%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_ERRORErreur générique non attendue.
VALIDATION_ERRORPayload mal formé — détail dans validation_output.
UNKNOWN_APP_IDapp_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

POST https://partners-dev.getapony.com/v1/coupons/{coupon}/redeemhttps://partners.getapony.com/v1/coupons/{coupon}/redeem

Historique des coupons

GET https://partners-dev.getapony.com/v0/users/{user_id}/benefitshttps://partners.getapony.com/v0/users/{user_id}/benefits
{
  "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

GET https://partners-dev.getapony.com/v0/users/{user_id}/trips?starting_after={xxx}https://partners.getapony.com/v0/users/{user_id}/trips?starting_after={xxx}
{
  "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 »).

GET https://partners-dev.getapony.com/v0/users/{external_user_id}/ongoing_trip?api_key=XXXX&app_id=YYYYhttps://partners.getapony.com/v0/users/{external_user_id}/ongoing_trip?api_key=XXXX&app_id=YYYY
{
  "_id": "trip_...",
  "startTime": "2030-01-01T00:00:00+00:00"
}
error http_code Description
NO_ONGOING_TRIP400Aucun trajet en cours pour cet utilisateur (y compris si Pony ne le connaît pas).
VALIDATION_ERROR400app_id / api_key manquant ou invalide.
—401app_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

GET https://gbfs-dev.getapony.com/v1/{region}/{language}/free_bike_status.json?key={gbfs_api_key}https://gbfs.getapony.com/v1/{region}/{language}/free_bike_status.json?key={gbfs_api_key}

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

GET https://gbfs-dev.getapony.com/v1/{region}/{language}/station_information.jsonhttps://gbfs.getapony.com/v1/{region}/{language}/station_information.json

Commandes véhicule

POST https://partners-dev.getapony.com/v1/vehicles/{vehicle_id}/commandhttps://partners.getapony.com/v1/vehicles/{vehicle_id}/command

Payload commun

Champ Requis Type Description
commandrequisstringLa commande envoyée, ex. "unlock".
lat / lonrequisfloatPosition de l'appelant.
external_trip_idoptionnelstringVotre identifiant de trajet ([A-Za-z0-9_-], ≤ 200 car.).
external_user_idoptionnelstringVotre identifiant utilisateur — obligatoire si vous n'utilisez pas de pony auth token.
external_agent_idoptionnelstringIdentifiant agent/logistique, si l'appelant n'est pas un simple utilisateur.
error Description
UNKNOWN_VEHICLE_IDIdentifiant véhicule non reconnu.
UNKNOWN_VEHICLE_COMMANDCommande non reconnue.
VEHICLE_OFFLINELe véhicule est hors-ligne au moment de l'appel (race condition possible).
VEHICLE_NOT_AVAILABLEVé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, affichez error_description et 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.

POST https://{SPECIFIC_PARTNER_DOMAIN}/{WEBHOOK_SERVICE_URL}/

Mise à jour véhicule

Déclenché à chaque changement d'état d'un véhicule (ex. scan NFC).

POST https://{SPECIFIC_PARTNER_DOMAIN}/{WEBHOOK_SERVICE_URL}/vehicule?api_key={ZZZZ}&app_id={BBBB}
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 · Pony Partner APIs Une question d'intégration ? dev@getapony.com
TOMP 1.6.1 · SSO

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.

i

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. JetonPOST /oauth/tokenÉchange le jeton SSO du voyageur contre un jeton Pony, valable 1 h. Crée le compte Pony au premier échange.
2. ProfilGET / PATCH /customers/currentLit le profil (numéro de téléphone validé ou non) ; modifie le prénom et le nom.
3. TéléphonePATCH /customers/currentEnregistre 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éservationPOST /bookings/one-stopRéserve le véhicule scanné pour 5 minutes. Réponse directement CONFIRMED (AUTO_COMMIT).
5. DépartPOST /legs/{id}/events SET_IN_USEDéverrouille le véhicule et démarre le trajet.
6. Pause / reprisePAUSE puis SET_IN_USEVerrouille le véhicule sans terminer le trajet, puis le redéverrouille.
7. Contrôle de finSTART_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. PhotoPOST /legs/{id}/parking-photo (recommandé)Dépose chez Pony la photo du véhicule garé.
9. FinFINISHVerrouille le véhicule et termine le trajet.
10. SuiviGET /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
QAhttps://partners-dev.getapony.com/tomp
Productionhttps://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
AuthorizationrequisBearer {access_token}, obtenu via /oauth/token.
Accept-Languagerecommandé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).
ApirequisTOMP
Api-Versionrequis1.6.1
maas-idrequisVotre identifiant MaaS, communiqué à l'onboarding (ex. tao).
addressed-tooptionnelpony
Content-Typerequis sur POST / PATCHapplication/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.1400, UNSUPPORTED_API_VERSION (7008)
maas-id absent ou inconnu400, UNKNOWN_MAAS_ID (7001)
Jeton absent, expiré, ou émis pour un autre maas-id401, INVALID_TOKEN (7501)

Chaque réponse porte l'en-tête Content-Language.

Donnée Format
DatesRFC 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.
Batterieasset.stateOfCharge, entier de 0 à 100 (pourcentage).
Distancedistance, entier en mètres.
IdentifiantsChaînes opaques générées par Pony : ne pas en déduire de sens.
Types de véhiculeassetType.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/currentAlias de l'identifiant du voyageur du jeton.
offset / limitPagination de GET /bookings.
extraFields.otpCode SMS, d'après l'exemple de la spec TOMP (extraInfo.otp est aussi accepté).
conditions[].idSur conditionRequireOffboardingSteps, porte le verdict du contrôle de fin (TAKE_PICTURE…).
error.titleCode stable et lisible de l'erreur (CUSTOMER_NOT_ACTIVE).
userLocationRequis sur tous les événements de trajet, y compris START_FINISHING.
POST /legs/{id}/parking-photoDépôt de la photo de stationnement, absent de TOMP.
parkingPhotoUploadedSur le leg : indique si Pony a reçu la photo de stationnement.
Content-Typeapplication/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.
Callbackscomment 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

POST https://partners-dev.getapony.com/tomp/oauth/tokenhttps://partners.getapony.com/tomp/oauth/token
Paramètre (form) Valeur
grant_typeurn:ietf:params:oauth:grant-type:jwt-bearer
assertionLe jeton d'accès Keycloak du voyageur.
scopeopenid 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_name et birthdate (tous facultatifs sauf sub).
  • 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/current et 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_client401Identifiants Basic absents ou invalides. En-tête WWW-Authenticate: Basic.
invalid_request400Paramètre manquant ou en double.
unsupported_grant_type400grant_type différent de jwt-bearer.
invalid_grant400Assertion 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_unavailable503Le 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_error500Erreur 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
CREATEDCompte créé, sans numéro de téléphone validé.
OTP_REQUIREDCode SMS envoyé, en attente de validation.
ACTIVENuméro validé.
BLOCKEDCompte bloqué par Pony : réservation refusée.
i

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écrit OTP_REQUIRED mais l'a oublié dans l'enum customerAccountStatus : acceptez cette valeur.

Lire le profil

GET https://partners-dev.getapony.com/tomp/customers/currenthttps://partners.getapony.com/tomp/customers/current
{
  "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"
}

externalId est votre sub. email vient du jeton SSO échangé pour obtenir le jeton Pony de l'appel. creationDate est la date de création du compte Pony, au premier échange. Les champs inconnus (nom, date de naissance absents du jeton) sont omis. phones est 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

PATCH https://partners-dev.getapony.com/tomp/customers/currenthttps://partners.getapony.com/tomp/customers/current
{ "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"
}

firstName et lastName se modifient ensemble ou séparément (chaîne non vide). Un champ absent ou à null ne change rien. Les autres champs du profil (e-mail, date de naissance…) ne sont pas modifiables et sont ignorés. phones et extraFields.otp servent à 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.

PATCH https://partners-dev.getapony.com/tomp/customers/currenthttps://partners.getapony.com/tomp/customers/current
{ "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éroMobile français uniquement, au format E.164 : +336… ou +337… (12 caractères, ex. +33612345678). Tout autre numéro : INVALID_PHONE_NUMBER.
Code6 chiffres, valable 10 minutes.
RenvoiPossible 60 secondes après l'envoi précédent. Avant : OTP_ALREADY_ACTIVE. Chaque envoi invalide le code précédent.
Changement de numéroTant 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).
Limite5 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.
Envoiphones (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

PATCH https://partners-dev.getapony.com/tomp/customers/currenthttps://partners.getapony.com/tomp/customers/current
{ "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.otp suit l'exemple de la spec TOMP ; extraInfo.otp (le champ du schéma customer) est aussi accepté. Les 4 premiers codes erronés répondent INVALID_OTP ; le 5e répond TOO_MANY_ATTEMPTS et 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épond 200 avec 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_FIELD8002400Champ 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_NUMBER8501400Numé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_ACTIVE8502409Un code a été envoyé il y a moins de 60 secondes (« code déjà actif »).
TOO_MANY_REQUESTS8007429Plus de 5 SMS dans l'heure pour ce compte (« trop de demandes »).
INVALID_OTP8504400Code erroné.
OTP_EXPIRED8505400Code expiré (plus de 10 minutes) : renvoyez le numéro.
NO_PENDING_OTP8506409Aucun code en attente pour ce compte (jamais envoyé, ou invalidé).
TOO_MANY_ATTEMPTS85074295e code erroné : le code est invalidé.
PHONE_NUMBER_ALREADY_USED8508409Ce 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_VALIDATED8509409Le compte a déjà un numéro validé.
CUSTOMER_BLOCKED8209403Compte bloqué par Pony : aucun SMS n'est envoyé.
SMS_UNAVAILABLE8510503Le 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.

GET https://gbfs-dev.getapony.com/v1/{region_id}/{language}/free_bike_status.json?key={gbfs_api_key}https://gbfs.getapony.com/v1/{region_id}/{language}/free_bike_status.json?key={gbfs_api_key}
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_id est l'identifiant stable du véhicule. C'est la valeur à passer dans useAssets à 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_id change à 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.json et station_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

POST https://partners-dev.getapony.com/tomp/bookings/one-stophttps://partners.getapony.com/tomp/bookings/one-stop
Champ Requis Type Description
fromrequisplacePosition du voyageur : {"coordinates": {"lat", "lng"}}.
useAssetsrequisstring[]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 de COMMIT.
  • Véhicule bloqué : TOMP sépare la réservation du départ. one-stop bloque le véhicule pour le voyageur jusqu'à Expires (5 minutes) : personne d'autre ne peut le réserver ni le déverrouiller. Sans SET_IN_USE avant cette heure, la réservation passe EXPIRED, son leg CANCELLED, et le véhicule est libéré.
  • Un seul trajet à la fois par compte Pony : one-stop est refusé si le voyageur a déjà une réservation CONFIRMED ou 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 dans GET /bookings).
  • Rejeu sans doublon : si le voyageur a déjà une réservation CONFIRMED sur le même véhicule, one-stop renvoie cette réservation (201, même Expires). 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épond ONGOING_BOOKING : réessayez après sa réponse.
  • Deux identifiants : id (réservation) et legs[0].id (trajet), toujours différents. Conservez les deux : tous les appels de trajet utilisent l'identifiant du leg.
  • Batterie : asset.stateOfCharge figure dans la réponse à one-stop, pas dans GET /bookings/{id} ni GET /bookings.
  • Pony n'annule jamais une réservation CONFIRMED, sauf par expiration.
title errorcode http_code Description
CUSTOMER_NOT_ACTIVE3501403Numéro de téléphone non validé (status ≠ ACTIVE) : validez-le par SMS.
CUSTOMER_BLOCKED3209403Compte bloqué ou restreint par Pony.
ONGOING_BOOKING3503403Le 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_VEHICLE3504400Identifiant 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_AVAILABLE3505406Véhicule en trajet, réservé, hors ligne, batterie trop faible ou en maintenance. Choisissez un autre véhicule.
TEMPORARILY_UNAVAILABLE3506503Indisponibilité 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 useAssets vide ou avec plusieurs identifiants répond 400 (3002), un from absent 400 (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.

POST https://partners-dev.getapony.com/tomp/bookings/{id}/eventshttps://partners.getapony.com/tomp/bookings/{id}/events
{ "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 avec FINISH : CANCEL répond alors 403 (ILLEGAL_STATE, errorcode 3004). Une réservation déjà annulée ou expirée répond 200 sans changement. COMMIT est inutile (AUTO_COMMIT) et répond 403 (3004), comme DENY et EXPIRE ; une autre operation répond 400 (3002). Un CANCEL reçu pendant une autre opération sur la même réservation (départ en cours) répond 403 (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.

POST https://partners-dev.getapony.com/tomp/legs/{id}/eventshttps://partners.getapony.com/tomp/legs/{id}/events
Champ Requis Type Description
timerequisdate-timeRequis par TOMP, avec fuseau. Pony horodate avec sa propre heure de réception.
eventrequisstringSET_IN_USE, PAUSE, START_FINISHING ou FINISH. Toute autre valeur répond 400 (4002).
userLocationrequisplacePosition du voyageur.
event Depuis Vers Déjà dans l'état cible Réponse
SET_IN_USENOT_STARTEDIN_USE200, leg inchangéImmédiate : commande envoyée au véhicule.
PAUSEIN_USEPAUSED200, leg inchangéAprès confirmation du verrouillage.
SET_IN_USE (reprise)PAUSEDIN_USE200, leg inchangéAprès confirmation du déverrouillage.
START_FINISHINGIN_USE, PAUSEDinchangé—Immédiate : contrôle sans effet sur le trajet.
FINISHIN_USE, PAUSEDFINISHED200, leg inchangéAprès confirmation du verrouillage.
  • Tout autre état répond 403 (ILLEGAL_STATE, errorcode 4004), avec une exception : SET_IN_USE sur une réservation expirée répond BOOKING_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. FINISH après un timeout client).
  • Appels simultanés : un SET_IN_USE reçu pendant un autre départ sur la même réservation répond 4004. Un PAUSE, une reprise ou un FINISH reçu pendant une autre opération sur le trajet répond 503 (VEHICLE_NOT_RESPONDING). Dans les deux cas, relisez le leg puis réessayez.
  • Durée : PAUSE, la reprise et FINISH attendent que le véhicule confirme, en général en quelques secondes. PAUSE et la reprise attendent 30 secondes au plus ; FINISH peut prendre jusqu'à 90 secondes sur certains vélos électriques. Réglez le timeout de votre client à au moins 100 secondes.
  • Retry-After : les réponses 503 le portent, aujourd'hui 5 secondes.
  • Réponse : toujours 200 avec le leg à jour (jamais 204).

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 }
}
i

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épondent 503 (VEHICLE_NOT_RESPONDING) pendant une minute, puis la réservation et le leg passent CANCELLED.

title errorcode http_code Description
BOOKING_EXPIRED4202403Réservation expirée (Expires dépassé).
VEHICLE_NO_LONGER_AVAILABLE4505403Le 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_RESPONDING4504503Verrouillage 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 }
}

actualDepartureTime garde l'heure du départ initial.

title errorcode http_code Description
VEHICLE_NOT_RESPONDING4504503Dé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_PICTUREautoriséePhoto du véhicule garé. Pas d'avertissement.
NOT_IN_PARKING_ZONEautorisée, avec avertissementHors zone de stationnement : se déplacer, ou continuer avec une photo.
PARKING_NOT_FOR_THIS_VEHICLE_TYPEautorisée, avec avertissementParking non prévu pour ce type de véhicule : se déplacer, ou continuer avec une photo.
JAUGE_FAILEDautorisée, avec avertissementParking déjà plein : se déplacer, ou continuer avec une photo.
FORBIDDEN_PARKINGrefusée (403)Zone interdite ou hors zone d'exploitation : continuer à rouler.
STRICT_PARKINGrefusée (403)Stationnement obligatoire sur un emplacement dédié.
JAUGE_FAILED_STRICTrefusée (403)Parking plein et capacité stricte : aller sur un autre parking.

Pony peut ajouter des verdicts : traitez un id inconnu comme une fin autorisée, avec photo. Pour un verdict sans avertissement (TAKE_PICTURE), userCommunication est absent.

Textes affichés au voyageur (Accept-Language: fr) :

Verdict Texte (userCommunication ou detail)
NOT_IN_PARKING_ZONEVous 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_TYPECe 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_FAILEDCe 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_PARKINGLe stationnement est interdit ici. Continuez votre trajet jusqu'à une zone autorisée.
STRICT_PARKINGLe stationnement est obligatoire sur un emplacement dédié. Rejoignez une zone de stationnement pour terminer votre trajet.
JAUGE_FAILED_STRICTCe 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 selon Accept-Language et destinés au voyageur : affichez-les tels quels.

title errorcode http_code Description
FORBIDDEN_PARKING4501403Zone interdite ou hors zone d'exploitation.
STRICT_PARKING4502403Stationnement obligatoire sur un emplacement dédié.
JAUGE_FAILED_STRICT4503403Parking 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).

POST https://partners-dev.getapony.com/tomp/legs/{id}/parking-photohttps://partners.getapony.com/tomp/legs/{id}/parking-photo

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_USE ou PAUSED. Tout autre état répond 403 (ILLEGAL_STATE, 4004) : une fois le leg FINISHED, 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, 4508 signifie que Pony a bien la photo de ce trajet.
  • Facultative : Pony ne refuse jamais un FINISH sans photo, pour qu'un échec d'envoi ne bloque pas le voyageur. Après un 503, renvoyez la photo après Retry-After secondes, avant FINISH.
  • Suivi : parkingPhotoUploaded figure sur le leg dès que le trajet a démarré, dans toutes les réponses qui le contiennent : false tant 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_LARGE4506413Photo de plus de 5 Mo.
INVALID_PHOTO4507415Le corps n'est pas une image JPEG, ou il est vide.
PHOTO_ALREADY_UPLOADED4508409Pony a déjà la photo de ce trajet.
PHOTO_STORAGE_UNAVAILABLE4509503La photo n'a pas pu être enregistrée : renvoyez-la après Retry-After secondes.
VEHICLE_NOT_RESPONDING4504503Trajet 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_RESPONDING4504503Verrouillage 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.

GET https://partners-dev.getapony.com/tomp/legs/{id}https://partners.getapony.com/tomp/legs/{id}
{
  "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_STARTEDVéhicule réservé, pas encore déverrouillé.
IN_USETrajet en cours.
PAUSEDEn pause, véhicule verrouillé.
FINISHEDTerminé, par vous ou par Pony.
CANCELLEDRé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 fois FINISHED, absente si CANCELLED.
  • to et actualArrivalTime n'apparaissent qu'une fois le trajet terminé.
  • parkingPhotoUploaded : true si Pony a reçu la photo de stationnement, false sinon. Présent dès que le trajet a démarré.
  • progressGeometry : le tracé du trajet, au format GeoJSON LineString (paires [lng, lat], comme dans TOMP). Présent uniquement sur GET /legs/{id} et une fois le trajet FINISHED : ni pendant le trajet, ni dans la réponse à FINISH, ni dans GET /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

GET https://partners-dev.getapony.com/tomp/bookings/{id}https://partners.getapony.com/tomp/bookings/{id}

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

GET https://partners-dev.getapony.com/tomp/bookings?state={state}&min-time={min}&max-time={max}&offset={offset}&limit={limit}https://partners.getapony.com/tomp/bookings?state={state}&min-time={min}&max-time={max}&offset={offset}&limit={limit}

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
stateSTARTED 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-timeBornes RFC 3339 avec fuseau, incluses, sur la date de création de la réservation.
offset / limitPagination (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 : 200 avec []. Le véhicule est dans legs[].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é.

POST {callback_base_url}/legs/{id}/events?app_id=pony&api_key={votre_cle}

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
FINISHDuré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
FINISHBatterie sous le seuil de verrouillage forcé.LOW_BATTERY
FINISHDurée maximale de pause dépassée.PAUSE_TIMEOUT
FINISHTrajet terminé par le support Pony.SUPPORT
CANCELLe 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}}'

time est l'heure de fin du trajet chez Pony. userLocation est la dernière position connue du véhicule et asset.stateOfCharge sa batterie en fin de trajet ; ils sont omis s'ils sont inconnus. comment est 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.

POST {vehicle_webhook_url}?app_id=pony&api_key={votre_cle}

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_idstringIdentifiant stable du véhicule (celui de useAssets).
lat / lonfloatPosition courante.
current_fuel_percentfloatBatterie, de 0.0 à 1.0.
current_range_metersintAutonomie restante estimée, en mètres.
is_disabledbooleantrue si le véhicule ne peut pas être réservé (en trajet, batterie, hors ligne, maintenance).
_lockedbooleanVerrou fermé.
_onlinebooleanVéhicule connecté.
_max_speedfloatVitesse maximale appliquée, en km/h.
_battery_hatch_lockedbooleanTrappe batterie verrouillée.
station_idstringPrésent si le véhicule est dans une zone de stationnement.
vehicle_type_idstringebike, scooter ou bike.
last_reportedintHorodatage Unix de l'envoi.
_event_uuidstringIdentifiant 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_reserved vaut toujours false. Un véhicule réservé (via cette API ou une autre application) apparaît is_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_id change à chaque envoi : utilisez _vehicle_id.

Erreurs

http_code Signification
400Requê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.
401Jeton absent, invalide, expiré ou émis pour un autre maas-id : refaites l'échange. Corps : INVALID_TOKEN (7501).
403Opération refusée : état du trajet, compte, stationnement.
404Ressource inconnue ou appartenant à un autre voyageur (sans corps).
406Réservation impossible : véhicule indisponible.
409Conflit sur le compte (SMS, numéro), ou photo de stationnement déjà reçue (4508).
413Photo de stationnement de plus de 5 Mo (4506).
415Photo de stationnement qui n'est pas une image JPEG (4507).
429Trop de demandes (SMS).
503Le 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.
500Erreur 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
errorcodeCode 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).
titleCode stable et lisible (CUSTOMER_NOT_ACTIVE) : c'est lui qu'il faut tester dans votre code.
statusCode HTTP.
detailMessage localisé selon Accept-Language, affichable au voyageur.
i

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é.

GET https://partners-dev.getapony.com/tomp/operator/metahttps://partners.getapony.com/tomp/operator/meta
[
  {
    "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
PonyMaaSclient_id / client_secret pour /oauth/token, un couple par environnement (QA, production).
PonyMaaSVotre maas-id et la clé GBFS.
PonyMaaSL'URL de la passerelle Pony (https://partners.getapony.com/tomp, et son équivalent QA), pour déclarer le client Pony dans votre realm.
MaaSPonyL'émetteur (iss), l'URL JWKS de votre realm Keycloak et la valeur attendue de azp (ou aud), par environnement.
MaaSPonycallback_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 pricing dans 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.
MaaS TOMP · Pony Partner APIs Standard officiel : TOMP-API v1.6.1