Exports en masse

Les lectures de cette API servent des pages. Elles n'ont volontairement pas de réponse à « donnez-moi les quatre mille salariés » : la pagination est le bon outil pour suivre une société au jour le jour, et le mauvais pour la première synchronisation, où elle coûte quatre-vingts appels et un curseur à tenir.

Les exports sont cette réponse. Vous demandez un fichier, nous le produisons en tâche de fond, vous le téléchargez une fois.

Les trois appels

POST /exports Demander un fichier. Répond immédiatement avec un export en Queued.
GET /exports/{id} Où il en est. C'est ce que vous interrogez en boucle.
GET /exports/{id}/content Le fichier lui-même, une fois le statut à Ready.

Le corps et les réponses ci-dessous sont capturés depuis la suite de tests, pas recopiés : ce que vous voyez est ce que l'API a reçu et renvoyé.

POST /api/v1/client/exports
Idempotency-Key: 6a1f…-une-par-requete
Content-Type: application/json
{
  "type": "employees"
}
{
  "id": "1VaKPa",
  "type": "employees",
  "status": "Queued",
  "failureReason": null,
  "rowCount": null,
  "contentLength": null,
  "createdOn": "<generated>",
  "readyOn": null,
  "expiresOn": null
}

Interrogez GET /api/v1/client/exports/{id} toutes les quelques secondes : c'est peu coûteux et c'est décompté sur votre godet habituel. Quand status vaut Ready, récupérez le contenu.

{
  "id": "1VaKPa",
  "type": "employees",
  "status": "Ready",
  "failureReason": null,
  "rowCount": 2,
  "contentLength": 353,
  "createdOn": "<generated>",
  "readyOn": "<generated>",
  "expiresOn": "<generated>"
}

Ce que vous pouvez exporter

Le catalogue est une liste fermée de types nommés. Vous choisissez un type, vous ne composez jamais une requête.

Type Contenu Scope également nécessaire
employees Les lignes de GET /employees, en CSV employees:read

Un type hérite du scope de la lecture qu'il extrait. exports:write dit que vous pouvez demander des fichiers ; employees:read dit quelles lignes peuvent vous être remises. Demander un type dont vous n'avez pas le scope de lecture est refusé sur-le-champ avec un 403 — pas par un job qui échoue dans la nuit.

Cela va au-delà de l'autorisation : le fichier est produit en parcourant cette lecture même, page par page. Le bornage de périmètre, les salariés qui en sont exclus et les champs qui vous sont retirés sont ceux que la liste applique déjà. Un export ne peut jamais vous remettre une ligne ou un champ que GET /employees aurait retenu.

establishmentId restreint l'extraction à un établissement de votre périmètre. Comme partout ailleurs sur cette API, il restreint et n'élargit jamais — et c'est la sortie de secours face à la limite de taille ci-dessous.

Le fichier

employees est un CSV, et voici sa forme exacte pour que vous l'analysiez sans avoir à deviner :

La réponse est constituée d'octets sous text/csv, avec le nom de fichier dans Content-Disposition. Il n'y a ni enveloppe JSON, ni base64, ni forme partielle, fragmentée ou reprenable de ce téléchargement.

⚠️ C'est une donnée d'intégration, pas un classeur. Une valeur saisie par votre client qui commence par =, +, -, @, une tabulation ou un retour chariot est une formule pour un tableur : une telle valeur est donc écrite précédée d'une apostrophe ('), qu'un tableur affiche comme du texte au lieu de l'évaluer. Cela ne concerne que les champs saisis (fullName, civility, employeeNumber, professionalEmail, companyName, jobFunction, managerFullName) ; les identifiants et isActive ne sont jamais modifiés, et la jointure avec GET /employees tient. Si vous comparez un champ saisi à la lecture JSON, retirez cette apostrophe de tête lorsqu'elle précède l'un de ces caractères.

La limite de taille est fonctionnelle

Une extraction dont le fichier dépasse la taille que cette API transmet en une seule réponse est refusée, pas produite. L'export revient en Failed avec failureReason à TooLarge, et la route de contenu répond alors 413.

Ce n'est pas un quota qu'on attend. Redemandez établissement par établissement — passez establishmentId — ou paginez GET /employees. La limite est paramétrable et votre interlocuteur Staff & Go peut vous indiquer la valeur en vigueur sur votre environnement.

Un échec porte toujours un nom

Un export Failed ne vous laisse jamais deviner. failureReason vaut l'une de ces valeurs :

Motif Ce qui s'est passé, et quoi faire
PerimeterEmpty Tous les établissements désignés par votre mandat ont fermé depuis votre demande. Il n'y a plus rien à extraire.
ModuleNotSubscribed Votre périmètre se résout toujours, mais aucun de ses établissements ne détient le module que ce type lit. C'est la société cliente qui l'ouvre.
TooLarge Voir ci-dessus — restreignez la demande.
Fault L'échec vient de chez nous. Redemandez.

PerimeterEmpty mérite un mot. Vous l'obtenez à la place d'un fichier vide, volontairement : un fichier vide est indiscernable d'une société qui n'a aucun salarié, et les deux appellent des conduites opposées.

Votre périmètre est résolu à la production du fichier

Pas au moment où vous le demandez. Un job attend en file, et un mandat peut être réduit ou révoqué pendant cette attente.

Récupérez vos fichiers tant que votre mandat tient.

Sept jours, puis le contenu est purgé

Un export est conservé sept jours après sa mise à disposition. expiresOn dit exactement quand.

Passé ce délai, l'export répond toujours — avec Expired, pas 404. « Il a existé et il a expiré » et « il n'a jamais existé » sont deux faits différents, et seul le premier vous dit d'en redemander un. rowCount et contentLength survivent à la purge ; les octets, non. La route de contenu répond alors 410.

Demander deux fois

POST /exports exige un Idempotency-Key, comme toute écriture protégée de cette API — voir Idempotence. Un rejeu de la même clé et du même corps vous rend l'export déjà créé, plutôt que de mettre en file un second parcours de votre périmètre.

Il est également décompté sur un godet qui lui est propre, nettement plus serré que votre débit de plan. La création est l'acte coûteux ; le suivi et le téléchargement restent sur le godet habituel, donc ce n'est pas le polling qui vous fera plafonner.

Ce qu'un agent peut faire

create_export et get_export sont des outils MCP. create_export n'est pas idempotent : un agent doit vous demander confirmation avant de l'appeler.

Le téléchargement n'est pas un outil, et ne le deviendra pas. Le résultat d'un agent est du texte qu'un modèle cite et recopie, et ce fichier nomme tous les salariés de votre périmètre. Récupérez-le depuis votre code, stockez-le comme un fichier, et ne le collez pas dans un prompt. Chaque appel est inscrit dans votre journal d'audit.