De la recherche d'un véhicule à l'annulation, en cinq appels. Vous avez besoin d'une clé API (voir « Authentification et scopes »), d'une organisation au plan Business, d'un véhicule actif rattaché à une catégorie tarifaire avec prix, et des scopes offers.read, quotes.write, rentals.read et rentals.write.
Les dates sont calculées dans le shell (dans 30 jours, pour 5 jours) : elles restent futures. Remplacez <vehicle-uuid> par le data[].vehicle.id lu à l'étape 1, <quote-uuid> par le data.id lu à l'étape 2 et <rental-uuid> par le data.id lu à l'étape 3.
# 0. Clé API (Paramètres → Développeur) et adresse de base.
export LEXIO_KEY="lck_xxxxxxxxxxxxxxxxxxxxxxxx"
export LEXIO_API="https://api.lexio.rent/api/external/v1"
# Un numéro de commande unique par essai : une partnerReference déjà utilisée répond 409 ALREADY_EXISTS.
export REF="quickstart-$(date +%s)"
# Dates futures, date GNU puis BSD/macOS.
export START=$(date -d '+30 days' +%F 2>/dev/null || date -v+30d +%F)
export END=$(date -d '+35 days' +%F 2>/dev/null || date -v+35d +%F)
# 1. Véhicules réservables et prix (offers.read) ; lisez data[].vehicle.id
curl "$LEXIO_API/availability?startAt=${START}T10:00&endAt=${END}T10:00&isAvailable=true" \
-H "Authorization: Bearer $LEXIO_KEY"
# 2. Devis au prix garanti 24 h (quotes.write) ; lisez data.id et data.totalTTC
curl -X POST "$LEXIO_API/quotes" \
-H "Authorization: Bearer $LEXIO_KEY" \
-H "Content-Type: application/json" \
-d '{
"vehicleId": "<vehicle-uuid>",
"startAt": "'"$START"'T10:00",
"endAt": "'"$END"'T10:00"
}'
# 3. Créer la location au prix du devis (rentals.write) ; lisez data.id
curl -X POST "$LEXIO_API/rentals" \
-H "Authorization: Bearer $LEXIO_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"quoteId": "<quote-uuid>",
"partnerReference": "'"$REF"'",
"customer": {
"firstName": "Marie",
"lastName": "Durand",
"email": "marie@example.com",
"phone": "+33612345678"
}
}'
# 4. Relire la location (rentals.read) ; lisez data.totalTTC et data.balance
curl "$LEXIO_API/rentals/<rental-uuid>" -H "Authorization: Bearer $LEXIO_KEY"
# 5. Annuler avec un motif (rentals.write) ; lisez effects.cancellation
curl -X POST "$LEXIO_API/rentals/<rental-uuid>/cancel" \
-H "Authorization: Bearer $LEXIO_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "reason": "Annulation du démarrage rapide" }'
Ce qu'il faut retenir :
409 VEHICLE_UNAVAILABLE) et un devis ne se consomme qu'une fois ;409 PRICE_CHANGED et n'écrit rien : demandez un nouveau devis ;Idempotency-Key et le même corps renvoie la réponse d'origine (voir « Idempotence et reprise ») ;?dryRun=true à une action : la réponse est celle de l'appel réel, rien n'est enregistré ;L'API s'authentifie par clé API uniquement, sans connexion d'utilisateur. La clé commence par lck_ ; le loueur la crée dans Lexio (Paramètres → Développeur) et vous la transmet. Seul le propriétaire de l'organisation, ou un membre autorisé par le propriétaire à gérer les clés API, peut la créer.
La clé s'envoie dans un seul en-tête, jamais dans l'adresse :
Authorization: Bearer lck_xxxxxxxxxxxxx
L'API se consomme depuis votre serveur : elle n'émet aucun en-tête CORS et une clé ne doit jamais être embarquée dans une page web ni dans une application installée. Une clé envoyée à une autre adresse que l'API externe est refusée (403) : vérifiez que vous appelez bien la base de l'API externe indiquée dans la référence.
La clé appartient à l'organisation, pas à la personne qui l'a créée : elle continue de fonctionner si ce membre perd des droits ou quitte l'équipe. Les écritures faites avec la clé sont signées « API : nom de la clé » (suivi de l'identifiant partenaire entre parenthèses s'il existe) dans les notes, les paiements et le journal d'activité du loueur.
| Situation | Statut | code | Qui agit |
|---|---|---|---|
| Clé absente de la requête | 401 | API_KEY_MISSING | vous |
| En-tête mal formé ou clé inconnue | 401 | API_KEY_INVALID | vous |
| Clé désactivée | 401 | API_KEY_DISABLED | le loueur la réactive, la même clé refonctionne |
| Clé révoquée | 401 | API_KEY_REVOKED | le loueur en crée une nouvelle |
| Clé expirée | 401 | API_KEY_EXPIRED | le loueur en crée une nouvelle |
| Scope manquant | 403 | INSUFFICIENT_SCOPE | le loueur crée une clé qui porte le scope |
| Organisation suspendue | 403 | ORGANIZATION_SUSPENDED | le loueur régularise son abonnement |
| Abonnement sans la fonctionnalité de la route | 403 | FEATURE_NOT_AVAILABLE | le loueur change d'abonnement |
Toute réponse 401 porte WWW-Authenticate: Bearer realm="Lexio API", complété de error="invalid_token" dès qu'un en-tête Authorization a été envoyé.
Une clé expire par défaut au bout de 365 jours. Pour la remplacer sans coupure : créez une seconde clé avec les mêmes scopes (et le même identifiant partenaire), basculez vos appels, attendez 24 h (durée de vie des Idempotency-Key, propres à chaque clé : un rejeu ne passe pas d'une clé à l'autre), puis faites révoquer l'ancienne.
Chaque réponse porte X-Request-Id, repris dans le champ requestId des erreurs : communiquez-le au support. Vous pouvez fournir le vôtre dans X-Request-Id (8 à 128 caractères parmi A-Za-z0-9._:-) ; sinon le serveur en génère un.
JSON UTF-8 de 1 Mio au plus, en application/json (application/merge-patch+json aussi sur PATCH). Au-delà, 413 ; autre type de contenu, 415 ; JSON illisible, 400. Les champs inconnus sont refusés (400).
Un scope s'écrit <ressource>.<accès> : read couvre les lectures de la ressource et de ses sous-ressources, write les créations, modifications et actions, les droits sensibles ont leur scope propre. Une opération accepte l'un des scopes listés (OU logique). GET /organization s'ouvre à toute clé valide. Un scope se choisit à la création de la clé et ne se modifie pas.
| Scope | Libellé | Opérations |
|---|---|---|
| (aucun) | Toute clé valide | GET /organization |
offers.read | Consulter les disponibilités et les devis | GET /availability, GET /quotes/{quoteId} |
quotes.write | Créer des devis | POST /quotes |
rentals.read | Voir les locations | GET /events, GET /rentals, GET /rentals/{rentalId}, GET /rentals/{rentalId}/amendments, GET /rentals/{rentalId}/contract, GET /rentals/{rentalId}/inspections, GET /rentals/{rentalId}/invoices, GET /rentals/{rentalId}/payments, GET /rentals/{rentalId}/payments/{paymentId}, GET /rentals/{rentalId}/photos, GET /rentals/{rentalId}/photos/{photoId} |
rentals.write | Créer et modifier les locations | POST /rentals, PATCH /rentals/{rentalId}, POST /rentals/{rentalId}/cancel, POST /rentals/{rentalId}/change-coverage, POST /rentals/{rentalId}/change-customer, POST /rentals/{rentalId}/change-vehicle, DELETE /rentals/{rentalId}/options/{optionId}, PUT /rentals/{rentalId}/options/{optionId}, POST /rentals/{rentalId}/reschedule |
rentals.operate | Départs, retours et photos | POST /rentals/{rentalId}/photo-uploads, POST /rentals/{rentalId}/photos, DELETE /rentals/{rentalId}/photos/{photoId}, POST /rentals/{rentalId}/pickup, POST /rentals/{rentalId}/return |
rentals.delete | Supprimer les locations | DELETE /rentals/{rentalId} |
payments.write | Enregistrer des paiements | POST /rentals/{rentalId}/payments |
payments.refund | Rembourser | POST /rentals/{rentalId}/payments, DELETE /rentals/{rentalId}/payments/{paymentId} |
customers.read | Voir les clients | GET /customers, GET /customers/{customerId} |
customers.write | Créer et modifier les clients | POST /customers, PATCH /customers/{customerId} |
customers.delete | Supprimer les clients | DELETE /customers/{customerId} |
vehicles.read | Voir les véhicules et leurs blocages | GET /vehicle-blocks, GET /vehicles, GET /vehicles/{vehicleId} |
vehicles.write | Créer et modifier les véhicules et leurs blocages | POST /vehicles, PATCH /vehicles/{vehicleId}, POST /vehicles/{vehicleId}/archive, POST /vehicles/{vehicleId}/unarchive |
vehicles.delete | Supprimer les véhicules | DELETE /vehicles/{vehicleId} |
catalog.read | Voir le catalogue (catégories, options, franchises, cautions, forfaits) | GET /categories, GET /categories/{categoryId}, GET /deductibles, GET /deductibles/{deductibleId}, GET /deposits, GET /deposits/{depositId}, GET /locations, GET /mileage-packages, GET /mileage-packages/{mileagePackageId}, GET /options, GET /options/{optionId} |
catalog.write | Modifier le catalogue | POST /categories, DELETE /categories/{categoryId}, PATCH /categories/{categoryId}, POST /deductibles, DELETE /deductibles/{deductibleId}, PATCH /deductibles/{deductibleId}, POST /deposits, DELETE /deposits/{depositId}, PATCH /deposits/{depositId}, POST /mileage-packages, DELETE /mileage-packages/{mileagePackageId}, PATCH /mileage-packages/{mileagePackageId}, POST /options, DELETE /options/{optionId}, PATCH /options/{optionId} |
pricing.read | Voir la tarification (saisons, remises, délais) | GET /duration-discounts, GET /duration-discounts/{durationDiscountId}, GET /lead-time-tiers, GET /lead-time-tiers/{leadTimeTierId}, GET /pricing-settings, GET /season-calendar, GET /season-calendar/{month}, GET /seasons, GET /seasons/{slug} |
pricing.write | Modifier la tarification | POST /duration-discounts, DELETE /duration-discounts/{durationDiscountId}, PATCH /duration-discounts/{durationDiscountId}, PATCH /lead-time-tiers/{leadTimeTierId}, PUT /season-calendar/{month}, PATCH /seasons/{slug} |
maintenance.read | Voir les entretiens | GET /maintenance-operations, GET /maintenance-records, GET /maintenance-records/{maintenanceRecordId}, GET /vehicles/{vehicleId}/maintenance-alerts |
maintenance.write | Gérer les entretiens | POST /maintenance-records, DELETE /maintenance-records/{maintenanceRecordId}, PATCH /maintenance-records/{maintenanceRecordId} |
offers.read et quotes.write permettent de chiffrer sans ouvrir le registre des locations.
Quatre seaux protègent l'API. Une requête refusée répond 429 RATE_LIMITED, avec Retry-After (secondes) ; le corps indique le seau (limit), sa fenêtre (windowSeconds) et le délai (retryAfter).
Seau (limit) | Plafond | Compté par | S’applique à |
|---|---|---|---|
PRE_AUTH | 30 requêtes / 1 min | adresse IP | les échecs d’authentification qu’aucune clé ne peut se voir attribuer (clé absente ou inconnue) |
PER_KEY | 120 requêtes / 1 min | clé API | toute requête d’une clé authentifiée, erreurs comprises |
WRITE | 30 requêtes / 5 min | clé API | POST /rentals, POST /rentals/{rentalId}/photo-uploads, POST /rentals/{rentalId}/photos |
WRITE | 60 requêtes / 1 min | clé API | toute autre écriture (POST, PUT, PATCH, DELETE) |
QUOTE | 120 requêtes / 1 min | clé API | POST /quotes et toute requête avec ?dryRun=true |
RateLimit-Limit, RateLimit-Remaining et RateLimit-Reset décrivent le seul seau PER_KEY ; les autres seaux se signalent par le 429.Idempotency-Key ne consomme aucun seau d'écriture ni de devis, sauf sur la préparation et l'enregistrement de photos, rejoués par uploadId : chaque renvoi compte dans le seau WRITE. Une écriture refusée (4xx ou 5xx) rend son jeton du seau WRITE ; le seau QUOTE compte aussi les refus.?dryRun=true compte dans le seau QUOTE, pas dans le seau d'écriture.429, attendez Retry-After puis rejouez la même requête avec la même Idempotency-Key.Toute liste utilise un curseur, partout :
{ "data": [ … ], "nextCursor": "eyJ…" }
limit : 1 à 100, 50 par défaut. cursor : valeur opaque, à renvoyer telle quelle.nextCursor nul signifie la fin de la liste. Il n'y a ni page, ni offset, ni total.nextCursor toujours nul ; elles n'acceptent ni limit ni cursor.limit, voire aucun : suivez nextCursor tant qu'il n'est pas nul (c'est le cas de GET /availability, qui tarife au plus 3 100 jours-véhicule par page).id) ; il n'y a pas de paramètre de tri.400 sur /cursor). Seul le curseur de GET /events expire, après 30 jours (410 CURSOR_EXPIRED) : voir « Synchronisation par événements ».status, vehicleId, customerId, categoryId, partnerReference, isActive, vehicleType.from et to (YYYY-MM-DD, inclusifs, fuseau de l'agence) bornent une fenêtre de jours ; to antérieur à from répond 400.true ou false ; filtre omis, tous les éléments.400.Une écriture réseau peut échouer après avoir réussi. Envoyez une Idempotency-Key pour la rejouer sans risque de doublon.
400. Acceptée sur toute autre écriture. Non exigée avec ?dryRun=true.Idempotent-Replayed: true. Exception : la préparation (POST /rentals/{rentalId}/photo-uploads) et l'enregistrement de photos (POST /rentals/{rentalId}/photos) ne sont jamais rejoués depuis la clé : un renvoi réexécute l'opération, et l'enregistrement se rejoue par uploadId.422 IDEMPOTENCY_KEY_REUSED.409 IDEMPOTENCY_IN_FLIGHT avec Retry-After ; rejouez après le délai.IDEMPOTENCY_ALREADY_APPLIED (création de location et de devis, actions sur une location, paiements, enregistrement d'un entretien, suppression d'une photo), une écriture faite dont la réponse a échoué (5xx) garde sa clé : un rejeu répond 409 IDEMPOTENCY_ALREADY_APPLIED avec resourceId, et l'en-tête Location quand la ressource se relit par un GET. Ne rejouez pas : relisez la ressource. Sur les autres écritures, la même clé réexécute l'opération après un 5xx : relisez la ressource avant de rejouer.| Réponse | Que faire |
|---|---|
Erreur réseau, délai dépassé, 500, 503 | rejouer à l'identique avec la même Idempotency-Key (après Retry-After s'il existe) ; sur une écriture qui ne déclare pas IDEMPOTENCY_ALREADY_APPLIED, relire d'abord la ressource |
409 IDEMPOTENCY_IN_FLIGHT, 409 RENTAL_BUSY | attendre Retry-After, puis rejouer à l'identique |
409 IDEMPOTENCY_ALREADY_APPLIED | ne pas rejouer : relire la ressource désignée |
429 | attendre Retry-After, puis rejouer à l'identique |
409 RENTAL_CHANGED, PRICE_CHANGED | relire la ressource, puis décider : ne rejouez pas à l'aveugle |
Autres 4xx | corriger la requête ; une nouvelle tentative utilise une nouvelle clé |
partnerReference est votre numéro de commande : unique par identifiant partenaire, il n'est pas un mécanisme de rejeu. Un doublon répond 409 ALREADY_EXISTS (field, conflictingId). Ne l'alimentez jamais avec un identifiant de paiement : une réservation a sa propre identité, indépendante du cycle de vie du paiement. Le rejeu sûr passe par Idempotency-Key.
Une action sur une location déjà dans l'état visé répond 409 RENTAL_STATUS_NOT_ALLOWED. L'annulation d'une location déjà annulée fait exception : elle répond 200 sans rien écrire, avec effects.cancellation.alreadyCancelled: true.
?dryRun=true exécute l'action jusqu'au bout, puis l'annule : la réponse est identique à l'appel réel, y compris effects.priceChange et effects.overpayment, et rien n'est enregistré.
expectedTotalTTC : si le total recalculé s'en écarte d'une unité mineure de la devise ou plus, elle répond 409 PRICE_CHANGED sans rien écrire. Sans expectedTotalTTC, l'action s'applique au prix courant.overpaymentResolution (REFUND_PENDING ou NO_REFUND, scope payments.refund) ; sinon 409 OVERPAYMENT_RESOLUTION_REQUIRED. Une clé partenaire reçoit resolvableByKey: false : le loueur traite la baisse dans Lexio.GET /events est le flux ordonné des changements de locations. Il remplace l'interrogation périodique des listes et ne demande aucun webhook.
GET /api/external/v1/events?cursor=…&limit=100&types=rental.created,rental.cancelled
{
"data": [
{
"id": "4e1a…",
"type": "rental.cancelled",
"occurredAt": "2027-06-01T09:12:44+02:00",
"resource": { "type": "RENTAL", "id": "9c2d…", "partnerReference": "LR-884213" }
}
],
"nextCursor": "…"
}
rental.created, rental.updated, rental.cancelled, rental.deleted. D'autres types pourront s'ajouter : ignorez ceux que vous ne connaissez pas.GET /rentals/{rentalId} pour obtenir son état. Une location supprimée ne se relit plus (404).rental.created. L'ordre est total : un événement validé tard n'est jamais sauté.rentals.read. Une clé partenaire ne voit que les événements de ses propres locations.start=EARLIEST), le flux repart des événements des 29 derniers jours ; start=LATEST ne renvoie aucun événement mais le curseur d'aujourd'hui, pour ne suivre que ce qui suit. start et cursor s'excluent.nextCursor. Le curseur est toujours renvoyé, même sur une page vide : rejouez-le tel quel.Les événements sont conservés 30 jours. Un curseur plus ancien répond 410 CURSOR_EXPIRED : resynchronisez en lisant d'abord GET /events?start=LATEST (enregistrez son curseur), puis parcourez GET /rentals page par page pour reconstruire l'état, et reprenez le flux au curseur enregistré. Rien de ce qui a changé pendant le parcours n'est perdu.
La version majeure est dans l'adresse : /api/external/v1. La documentation décrit le contrat en vigueur ; son numéro (info.version) suit le versionnage sémantique, par exemple 1.0.0. La date de chaque changement figure dans le changelog publié de l'API.
Elles ne cassent aucun client qui respecte ces règles : ignorez les champs de réponse que vous ne connaissez pas.
Elle est annoncée 30 jours avant. Tolérez une valeur inconnue : ne basez pas un switch sans branche par défaut sur une énumération de sortie.
Un correctif de sécurité, d'intégrité des données ou de bug, ou l'alignement de la référence sur le comportement réel, s'applique à la v1 sans préavis. Il est daté dans le changelog, avec la marche à suivre.
Hors correctif de contrat, elles ne paraissent que dans une nouvelle version majeure (v2), servie en parallèle. La v1 reste disponible 6 mois après l'annonce, et ses réponses portent les en-têtes Deprecation et Sunset.
Toute réponse 4xx ou 5xx est un objet RFC 9457 (application/problem+json). Le code est la référence stable, lisible par une machine ; title et detail sont rédigés en français et peuvent évoluer : ne les analysez pas.
{
"type": "https://api.lexio.rent/errors/rental-busy",
"title": "Location occupée",
"status": 409,
"code": "RENTAL_BUSY",
"detail": "Une autre opération est en cours sur cette location.",
"instance": "/api/external/v1/rentals/9c2d…/cancel",
"requestId": "9b2d6c1e-4f3a-4e8b-a1d7-3c5e9f0a2b46"
}
code : valeur d'une énumération fermée, la liste ci-dessous. Une opération ne répond que par les codes qu'elle déclare, plus un socle commun (clé, scope, abonnement, débit, erreur interne, validation).type : adresse stable du code, la même partout.reasons, conflictingId, expectedTotalTTC…) sont typés et propres à ce code ; la colonne « Champs supplémentaires » les liste.errors[] accompagne les refus de validation : chaque élément porte un pointer (RFC 6901, /startAt, /options/0/optionId), un code stable et un detail.requestId : à communiquer au support. Il est aussi dans l'en-tête X-Request-Id.Retry-After (secondes) accompagne les codes rejouables (colonne « Retry-After » du catalogue). Un 500 n'en porte pas mais se rejoue aussi (voir le tableau) ; les autres codes ne se rejouent pas à l'identique.| Statut | Signification | Votre réaction |
|---|---|---|
400 | la forme de la requête est invalide (JSON, type, format, champ inconnu ou manquant) | corriger la requête |
401 | clé absente, invalide, désactivée, révoquée ou expirée | changer de clé |
403 | scope manquant, organisation suspendue, fonctionnalité hors abonnement | demander l'accès au loueur |
404 | cible inexistante ou hors du périmètre de la clé | ne pas réessayer |
405, 410, 413, 415 | méthode, curseur expiré, taille, type de contenu | corriger |
409 | l'état d'une ressource (location, véhicule, client, devis) ou une écriture concurrente empêche l'opération : la même requête peut réussir plus tard sans modification | relire l'état ; ne réessayer que si le code est rejouable |
422 | requête bien formée mais refusée par une règle de configuration ou de données (référence, règle de réservation, plafond, montant) | changer les données |
429 | débit | attendre Retry-After |
500, 503 | incident ou dépendance indisponible | rejouer avec la même Idempotency-Key, après Retry-After si présent (un 500 n'en porte pas : patienter quelques secondes) ; voir « Idempotence et reprise » avant de rejouer une écriture |
Un même refus a le même statut et le même code sur toutes les routes.
| Code | Statut | Titre | Champs supplémentaires | Retry-After |
|---|---|---|---|---|
MALFORMED_REQUEST | 400 | Corps de requête illisible | — | — |
VALIDATION_FAILED | 400 | Requête invalide | errors[] | — |
API_KEY_DISABLED | 401 | Clé API désactivée | — | — |
API_KEY_EXPIRED | 401 | Clé API expirée | — | — |
API_KEY_INVALID | 401 | Clé API invalide | — | — |
API_KEY_MISSING | 401 | Clé API absente | — | — |
API_KEY_REVOKED | 401 | Clé API révoquée | — | — |
FEATURE_NOT_AVAILABLE | 403 | Fonctionnalité non incluse | feature | — |
INSUFFICIENT_SCOPE | 403 | Scope insuffisant | requiredScopes | — |
ORGANIZATION_SUSPENDED | 403 | Organisation suspendue | — | — |
NOT_FOUND | 404 | Ressource introuvable | resource | — |
ROUTE_NOT_FOUND | 404 | Route inconnue | — | — |
METHOD_NOT_ALLOWED | 405 | Méthode non autorisée | allowed | — |
ALREADY_EXISTS | 409 | Doublon | field, conflictingId | — |
BOOKING_REFUSED | 409 | Réservation refusée | — | — |
CREDIT_NOTE_NOT_ISSUABLE | 409 | Avoir impossible à émettre | — | — |
CUSTOMER_ANONYMIZED | 409 | Client anonymisé | — | — |
CUSTOMER_BLOCKED | 409 | Client bloqué | — | — |
DEFAULT_ITEM_REQUIRED | 409 | Élément par défaut requis | — | — |
IDEMPOTENCY_ALREADY_APPLIED | 409 | Écriture déjà appliquée | resourceId | — |
IDEMPOTENCY_IN_FLIGHT | 409 | Requête identique en cours | — | oui |
INVOICE_FINALIZED | 409 | Facture finalisée | — | — |
MAINTENANCE_TRACKING_ALREADY_STARTED | 409 | Suivi d'entretien déjà démarré | maintenanceTrackingMode | — |
MAINTENANCE_TRACKING_NOT_STARTED | 409 | Suivi d'entretien non démarré | — | — |
OPTION_UNAVAILABLE | 409 | Option en rupture | optionId, availableQuantity | — |
OVERPAYMENT_RESOLUTION_REQUIRED | 409 | Trop-perçu à résoudre | overpaidAmount, resolvableByKey | — |
PAYMENT_PROVIDER_MANAGED | 409 | Paiement géré par le prestataire | — | — |
PRICE_CHANGED | 409 | Prix modifié | expectedTotalTTC, currentTotalTTC | — |
QUOTE_ALREADY_USED | 409 | Devis déjà utilisé | rentalId | — |
QUOTE_EXPIRED | 409 | Devis expiré | — | — |
RANGE_OVERLAP | 409 | Plages qui se chevauchent | errors[], conflictingId, minRentalDays, maxRentalDays, minLeadDays, maxLeadDays | — |
RENTAL_BUSY | 409 | Location occupée | — | oui |
RENTAL_CHANGED | 409 | Location modifiée entre-temps | — | — |
RENTAL_STATUS_NOT_ALLOWED | 409 | Statut de la location incompatible | errors[], rentalStatus, allowedStatuses, action | — |
RESOURCE_IN_USE | 409 | Ressource utilisée | blockers | — |
SEPA_DEBIT_PENDING | 409 | Prélèvement SEPA en cours | pendingDebitAmount | — |
VEHICLE_ARCHIVED | 409 | Véhicule archivé | — | — |
VEHICLE_UNAVAILABLE | 409 | Véhicule indisponible | reasons, conflicts, overdueOperations | — |
CURSOR_EXPIRED | 410 | Curseur expiré | — | — |
PAYLOAD_TOO_LARGE | 413 | Corps trop volumineux | maxBytes | — |
UNSUPPORTED_MEDIA_TYPE | 415 | Type de contenu non pris en charge | accepted | — |
BOOKING_RULE_VIOLATION | 422 | Règle de réservation non respectée | errors[], rule | — |
DISCOUNT_CAP_EXCEEDED | 422 | Plafond de remise dépassé | errors[], maxDiscountPercent | — |
DRIVER_AGE_MISMATCH | 422 | Âge du conducteur différent du devis | errors[] | — |
IDEMPOTENCY_KEY_REUSED | 422 | Clé d'idempotence réutilisée | — | — |
LIMIT_EXCEEDED | 422 | Limite atteinte | resource, max | — |
OPTION_OUT_OF_SEASON | 422 | Option hors saison | errors[] | — |
OPTION_QUANTITY_EXCEEDED | 422 | Quantité d'option dépassée | errors[], maxQuantity | — |
PAYMENT_EXCEEDS_DUE | 422 | Encaissement supérieur au dû | errors[], dueAmount | — |
PAYMENT_NOT_VERIFIED | 422 | Paiement non vérifié | errors[], reason | — |
PRICING_CHOICE_NOT_AVAILABLE | 422 | Choix tarifaire indisponible | errors[], availablePricingChoices | — |
PROMO_CODE_REJECTED | 422 | Code promo refusé | errors[], reason, minRentalDays | — |
REFERENCE_INACTIVE | 422 | Référence inactive | errors[] | — |
REFERENCE_NOT_FOUND | 422 | Référence introuvable | errors[] | — |
REFUND_EXCEEDS_PAID | 422 | Remboursement supérieur à l'encaissé | errors[], maxRefundableAmount | — |
UPLOAD_INVALID | 422 | Téléversement invalide | errors[] | — |
VAT_REVERSE_CHARGE_NOT_ALLOWED | 422 | Autoliquidation impossible | reason | — |
RATE_LIMITED | 429 | Trop de requêtes | limit, windowSeconds, retryAfter | oui |
INTERNAL_ERROR | 500 | Erreur interne | — | — |
SERVICE_UNAVAILABLE | 503 | Service momentanément indisponible | dependency | oui |
Parcours d'une plateforme de réservation qui vend des véhicules de plusieurs loueurs Lexio.
Une clé appartient à une seule organisation : il n'existe pas de clé multi-organisation. Chaque loueur crée la sienne (Paramètres → Développeur) et vous la transmet. Contrôlez-la avec GET /organization : devise, fuseau, TVA, horaires d'ouverture, règles de réservation et scopes de la clé.
Une clé peut porter un identifiant partenaire (partnerCode), fixé à sa création et non modifiable. Il cloisonne votre espace :
404, comme un identifiant inconnu ;partnerReference est unique par identifiant partenaire : deux partenaires peuvent utiliser la même référence sans collision ;customerId n'est pas accepté en écriture. Un client bloqué chez le loueur fait répondre 409 BOOKING_REFUSED, sans cause ;Scopes accordables à une clé partenaire : catalog.read, offers.read, quotes.write, rentals.read, rentals.write et payments.write (limité aux paiements Stripe). Les autres sont refusés à la création de la clé.
GET /availability : véhicules réservables et prix pour la période, selon les règles de réservation du loueur (422 BOOKING_RULE_VIOLATION). Prix et disponibilité ne sont pas garantis.POST /quotes : prix garanti 24 h (expiresAt), consommable une seule fois. Le devis ne réserve rien.POST /rentals avec quoteId, votre numéro de commande en partnerReference et une Idempotency-Key. La disponibilité est revérifiée (409 VEHICLE_UNAVAILABLE) ; si le prix recalculé s'écarte du devis, 409 PRICE_CHANGED et rien n'est créé.totalTTC et le reste à payer dans balance de la location.Votre numéro de commande est la référence de la réservation : n'y mettez pas un identifiant de paiement. Pour retrouver une location : GET /rentals?partnerReference=… ou GET /rentals?number=… (numéro Lexio).
Enregistrez un paiement par POST /rentals/{rentalId}/payments. Une clé partenaire n'enregistre que des paiements Stripe, par leur identifiant de paiement ; les remboursements sont réservés au loueur. Si une modification fait passer le total sous le montant encaissé, la réponse est 409 OVERPAYMENT_RESOLUTION_REQUIRED avec resolvableByKey: false : le loueur traite la baisse dans Lexio.
GET /events puis relisez la location (voir « Synchronisation par événements »). Il n'y a pas de webhook.POST /rentals/{rentalId}/cancel avec un reason, pas par DELETE. Une location déjà annulée répond 200 sans effet. effects.cancellation donne les avoirs émis et le remboursement dû.reschedule et le véhicule par change-vehicle : aperçu avec ?dryRun=true, puis appel réel avec expectedTotalTTC égal au total de l'aperçu (voir « Idempotence et reprise »).PATCH /rentals/{rentalId} ne modifie que les notes et les lieux.Une clé partenaire n'envoie jamais d'email de confirmation au client final. Le loueur est alerté de chaque création.
Testez sur une organisation de test du loueur, avec des dates futures, puis annulez.
Voir « Authentification et scopes » : deux clés actives avec le même identifiant partenaire pendant la bascule. Vos références, devis, clients et événements sont liés à l'identifiant partenaire, pas à la clé.
Principe : tout ce qui bloque une action de l'API est lisible par l'API (la cause figure dans l'erreur et la ressource est consultable), et modifiable par l'API, sauf les exceptions ci-dessous. Elles se gèrent dans l'application Lexio, par le loueur.
| Sujet | Ce qui bloque | Lecture par l'API | Pourquoi dans Lexio |
|---|---|---|---|
| Blocages de planning | VEHICLE_UNAVAILABLE (raison BLOCKED) | GET /vehicle-blocks | outil de planning du loueur ; les blocages d'entretien se créent et se suppriment avec /maintenance-records |
| Contrôle technique | VEHICLE_UNAVAILABLE (raison MAINTENANCE_OVERDUE) | GET /vehicles/{vehicleId}/maintenance-alerts | suivi réglementaire : absent de /maintenance-records (404), non saisissable ; l'échéance reste visible dans les alertes |
| Réinitialisation du suivi d'entretien | MAINTENANCE_TRACKING_ALREADY_STARTED | maintenanceTrackingMode du véhicule | réécrit les points de départ de toutes les échéances |
| Structure des saisons | 422 REFERENCE_NOT_FOUND sur une saison inconnue | GET /seasons | le jeu de saisons est créé avec l'organisation ; l'API règle les multiplicateurs, pas la création ni la suppression de saison |
| Lieux gérés | 422 REFERENCE_INACTIVE sur un lieu désactivé | GET /locations | non bloquant : { address } libre, name facultatif, reste toujours possible |
| Blocage d'un client | 409 CUSTOMER_BLOCKED (BOOKING_REFUSED pour une clé partenaire) | isBlocked de la fiche client | décision du loueur ; son motif reste interne |
| Fiche entreprise du loueur | 409 CREDIT_NOTE_NOT_ISSUABLE à l'annulation ou au remboursement | refus nommé dans l'erreur | informations légales du loueur, à compléter dans Lexio |
| Trop-perçu d'une clé partenaire | 409 OVERPAYMENT_RESOLUTION_REQUIRED avec resolvableByKey: false | balance de la location | décision d'argent réservée au loueur |
title et les detail des erreurs sont en français. Les contrats lisibles par machine (code, errors[].code, énumérations, type) sont stables et indépendants de la langue ; il n'y a pas de négociation Accept-Language.