Requêtes et réponses
URL de base
https://api.freshperf.fr/v1. Toutes les routes de cette rubrique sont relatives à cette base, et seul HTTPS est servi.
Requêtes
| Requête | Règle |
|---|---|
| Corps | JSON, avec Content-Type: application/json |
| Paramètres de chemin | les identifiants publics du tableau, plus bas |
| Filtres et pagination | paramètres de requête |
| Champs inconnus dans un corps | ignorés |
L'API refuse une valeur mal formée plutôt que de la corriger en silence :
| Cas | Réponse |
|---|---|
?limit=abc | 400 VALIDATION_ERROR:limit |
?limit=0, ?limit=500 | 400 VALIDATION_ERROR:limit |
?cursor= illisible | 400 VALIDATION_ERROR:cursor |
?status= hors énumération | 400 VALIDATION_ERROR:status |
| Champ de corps manquant ou hors énumération | 400 VALIDATION_ERROR:<champ> |
Réponses
Un succès est une enveloppe :
{ "data": { "code": "SRV-TJUAQ1-1951", "status": "ACTIVE" } }
Une liste ajoute la pagination :
{ "data": [ { "code": "SRV-TJUAQ1-1951", "status": "ACTIVE" } ], "pagination": { "nextCursor": "MTg4", "limit": 20 } }
| Champ | Type | Sens |
|---|---|---|
pagination.nextCursor | string ou null | à repasser en ?cursor=. null sur la dernière page |
pagination.limit | entier | la taille de page réellement appliquée |
201 pour une création, 200 sinon. Les deux routes de PDF répondent le fichier, pas du JSON.
Pagination
?limit= vaut 1 à 100, 20 par défaut. Une valeur hors bornes répond 400 VALIDATION_ERROR:limit.
Le curseur est opaque et se lit sur deux mécaniques différentes selon la route. Ne le construisez pas vous-même.
| Mécanique | Routes | Comportement |
|---|---|---|
| Dernier identifiant vu | GET /services, GET /orders, GET /invoices | une insertion pendant la marche ne décale pas les pages |
| Décalage | GET /balance/transactions, GET /credit-notes, GET /tickets | une insertion pendant la marche décale les pages suivantes |
Les listes vont du plus récent au plus ancien, sauf GET /tickets, trié sur la dernière mise à jour.
L'exception : les notifications
GET /account/notifications ne suit aucune de ces règles.
| Notifications | Règle |
|---|---|
| Pagination | ?page= (1 par défaut, minimum 1) et ?limit= (20 par défaut, maximum 50) |
| Enveloppe | {"data": {"items": [...], "total": n, "unread": n}}, sans bloc pagination |
Erreurs
Toute erreur, sur toute route, a la même forme :
{ "error": { "code": "API_SCOPE_MISSING:services.power:write", "message": "This API key does not hold the scope this operation needs.", "docs": "https://freshperf.fr/api-reference" } }
codeest un marqueur stable. Branchez votre code dessus.messageest une phrase en anglais pour les humains. Elle peut changer ; ne l'analysez pas.
Certains marqueurs portent un suffixe après deux-points :
| Marqueur | Suffixe |
|---|---|
API_SCOPE_MISSING:<scope> | la permission manquante |
API_ACTION_NOT_ALLOWED:<action> | l'action fournisseur refusée |
VALIDATION_ERROR:<champ> | le champ ou le paramètre fautif |
PRODUCT_NOT_FOUND:<shortname>, PRODUCT_NOT_ORDERABLE:<shortname>, RECURRENCE_REQUIRED:<shortname>, RECURRENCE_NOT_OFFERED:<shortname> | le produit de la ligne de commande |
INVALID_METADATA_KEY:<clé>, INVALID_METADATA_VALUE:<clé>, MISSING_REQUIRED:<clé> | la caractéristique de la ligne |
SERVICE_NOT_FOUND n'a jamais de suffixe.
Classes de statut :
| Statut | Sens |
|---|---|
400 | la requête est mal formée ou l'état ne la permet pas |
401 | clé absente ou inconnue |
402 | un paiement n'est pas passé |
403 | permission, plafond ou adresse |
404 | inexistant, ou hors de la portée de la clé |
409 | conflit avec l'état actuel, ou refus du fournisseur |
429 | trop d'appels, ou geste trop fréquent |
500 | de notre côté |
502 | l'infrastructure derrière le service a échoué |
503 | un PDF n'est pas encore prêt |
La liste complète est dans Référence des erreurs.
Identifiants
| Ressource | Champ | Forme | D'où il vient |
|---|---|---|---|
| Service | code | SRV- + base36 + - + 4 chiffres, ex. SRV-TJUAQ1-1951 | GET /services |
| Commande | orderNumber | ORD-AAAAMMJJ-NNNN, ex. ORD-20260828-0001 | GET /orders, ou la réponse de POST /orders |
| Facture | code | INV-AAAAMM-NNNN, ex. INV-202608-0083 | GET /invoices |
| Avoir | code | AV-AAAAMM-NNNN, ex. AV-202608-0001 | GET /credit-notes |
| Ticket | ticketNumber | TKT- + base36 + - + 4 chiffres, ex. TKT-TKH21B-6604 | GET /tickets, ou la réponse de POST /tickets |
| Moyen de paiement | id | entier | GET /payment-methods |
| Clé SSH | id | entier | GET /ssh-keys |
| Sauvegarde | backupId | chaîne propre au fournisseur, ex. 9b5d3938-cbd9-4946-8357-05badcdf6c33 | GET /services/{code}/backups → result |
| Paiement | id | entier | GET /invoices/{code} → payments[] |
Traitez-les comme opaques. Les identifiants d'infrastructure (instanceId, ownerId, vmid, nœud, cluster) sont retirés de toutes les réponses, à toute profondeur.
Montants et dates
- Les montants sont des entiers en centimes :
"totalCents": 1199vaut 11,99 €. Jamais de flottant. currencyest un code ISO 4217, et peut valoirnullsur un document sans montant.- Les dates sont des millisecondes depuis l'epoch, en UTC :
"nextBillingAt": 1789437385056. - Les prix de catalogue et les prix récurrents d'un service sont hors taxe ;
taxCentsvoyage à côté ettotalCentsinclut les deux. amountCentsd'un mouvement de solde est signé : positif au crédit, négatif au débit.
En-têtes
Que vous envoyez :
| En-tête | Quand |
|---|---|
Authorization: Bearer fpk_... | toujours |
Content-Type: application/json | dès qu'il y a un corps |
Idempotency-Key | sur les quatre routes qui engagent de l'argent (voir Limites de débit et idempotence) |
Que vous recevez :
| En-tête | Quand | Sens |
|---|---|---|
X-Request-Id | toute réponse | identifiant de l'appel, présent aussi dans le journal de la clé. À citer au support |
Cache-Control: no-store | toute réponse sauf /openapi.json | rien ne se met en cache |
X-RateLimit-Limit, -Remaining, -Reset | réponses authentifiées | le budget le plus serré de la minute en cours. Reset est un horodatage en secondes epoch, pas un délai |
Retry-After | 429, 409 IDEMPOTENCY_IN_PROGRESS, 503 PDF_NOT_AVAILABLE | secondes à attendre |
WWW-Authenticate | 401 | Bearer realm="freshperf-api" |
Idempotent-Replayed: true | rejeu | la réponse vient d'un appel identique antérieur |
GET /v1/openapi.json répond Cache-Control: public, max-age=300 avec un ETag, et ne porte pas les en-têtes de débit.
Navigateurs
Seule l'origine du site FreshPerf est autorisée en CORS, pour la console d'essai de la référence. Une clé d'API n'a pas sa place dans une page web : appelez /v1 depuis vos serveurs.
Compatibilité
L'API est versionnée dans le chemin (/v1). Dans v1 nous ajoutons des champs, des routes et des valeurs d'énumération ; nous n'en retirons ni n'en renommons.
Écrivez donc des clients qui ignorent les champs inconnus et tolèrent une valeur d'énumération nouvelle. Un changement incompatible serait un /v2, annoncé à l'avance.