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
fromettoacceptent une date (2026-09-01) ou un instant ISO.toest 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) |