Pagination, erreurs, idempotence

Trois mécanismes transversaux, identiques sur toute l'API.

Pagination par curseur

Les collections ajoutées en 3.0 (événements, livraisons de webhooks, entrées de temps, journal fiscal) se parcourent par curseur :

GET /companies/{id}/events?limit=50
→ { "data": [ … ], "nextCursor": "MTIzNDU", "hasMore": true }

GET /companies/{id}/events?limit=50&cursor=MTIzNDU

Le curseur est opaque : ne le décodez pas, ne le construisez pas. Il reste valable quelles que soient les insertions entre deux pages. limit vaut 50 par défaut et 200 au plus.

Les collections plus anciennes (ventes, achats, partenaires, mouvements) renvoient un tableau, filtré par période et limité par limit (100 par défaut) :

GET /companies/{id}/sales?from=2026-09-01&to=2026-09-30&limit=200

Les ventes et les achats acceptent aussi page et size pour une pagination classique : ?page=2&size=50 renvoie alors { data, total, totals } — les lignes, leur nombre, et les sommes de la période. Pour une synchronisation complète, parcourez mois par mois ou page par page.

Erreurs

Une erreur est une réponse HTTP 4xx ou 5xx dont le corps porte un code stable :

{ "statusCode": 400, "message": "field-required:walletId", "error": "Bad Request" }

Le code est fait pour être lu par un programme : un identifiant en minuscules, parfois suivi d'un détail après :. L'application le traduit en phrase ; vous pouvez faire pareil. Les plus courants :

Statut Code Sens
400 field-required:<champ> Un champ obligatoire manque
400 amount-required Le montant doit être supérieur à zéro
400 amount-above-outstanding Le paiement dépasse le reste à payer
400 insufficient-stock:<article>:<disponible> Pas assez en stock
400 plan-limit:<clé>:<autorisé> Le plafond du plan est atteint
401 api-key-invalid Clé inconnue ou révoquée
403 forbidden Portée manquante, ou écriture avec une clé de test
403 missing-permission La portée ne couvre pas cette action
404 company-not-found, sale-not-found, … La ressource n'existe pas pour cette entreprise
409 in-flight La même requête, avec la même clé d'idempotence, est encore en cours
429 too-many-requests Trop de requêtes pour cette clé ; voir Retry-After

Un 5xx est une faute de notre côté. Réessayez avec un délai croissant ; la requête est sûre à rejouer si elle portait une clé d'idempotence.

Idempotence

Toute écriture peut porter un en-tête Idempotency-Key :

POST /companies/{id}/sales
Idempotency-Key: till-7-2026-09-28-00042

La première requête qui arrive avec une clé donnée est exécutée ; sa réponse (statut compris) est conservée 24 heures et rejouée à toute requête qui présente la même clé, avec l'en-tête Idempotent-Replay: true. Une requête qui arrive pendant que la première s'exécute encore reçoit 409.

La clé est libre : entre 8 et 200 caractères. Choisissez quelque chose qui identifie l'opération métier (le numéro du ticket de la caisse, l'identifiant de la ligne dans votre système), pas un UUID tiré au moment de l'envoi — sinon un réessai en tirera un autre et le mécanisme ne sert à rien.

Dates et filtres

  • from et to acceptent une date (2026-09-01) ou un instant ISO. to est inclus jusqu'à la fin du jour dans le fuseau de l'entreprise.
  • Les filtres sont des paramètres de requête (?status=paid&businessUnitId=…). Les champs filtrables sont listés dans le schéma OpenAPI.

En-têtes utiles

En-tête Sens
X-Request-Id (réponse) Identifiant de la requête, à citer dans un signalement
Retry-After (réponse 429) Secondes avant de réessayer
Idempotent-Replay: true (réponse) La réponse vient du cache d'idempotence
Accept-Language (requête) La langue des libellés générés (PDF, rappels)

فقرة خاطئة أو ناقصة؟ راسلنا.