Lots de paie — soumettre des missions pour calcul des bulletins
L'API partenaire Pesia vous permet de soumettre les missions d'une période (1 ligne = 1 mission) pour que Pesia calcule les bulletins de paie de vos intérimaires, puis de suivre la progression du calcul jusqu'à la clôture du lot.
Le lot de paie est canonique : il existe un seul lot par agence et par mois. Chaque
POST alimente le lot de la période (créé automatiquement au premier dépôt) — il ne le
remplace pas. Vous pouvez donc soumettre les missions au fil de l'eau, en plusieurs appels.
Migration du payload. Le groupe JSON
end_user_companyest remplacé parstaffing_client_establishmentet l'ancien nom n'est plus accepté. Pour les fichiers CSV,mission_end_user_siretdevientmission_staffing_client_siret.
Prérequis
- Scope requis :
payroll:simulate(voir Scopes) — il couvre la soumission et la lecture du statut des lots. - Le
agencyCodedoit correspondre à une agence de votre organisation. Ce code vous est retourné dans le champcodedeGET /partner/v1/agenciesavec le scopeagency:read. Respectez sa casse exacte. - L'agence doit disposer d'une fiche légale, de paramètres de paie complets et,
pour la
periodde chaque lot, d'un taux AT/MP intérimaire en vigueur le premier jour du mois. Ces données sont paramétrées côté Pesia : l'API ne permet pas de les renseigner. Si l'une manque, contactez votre interlocuteur Pesia (partenaires@pesia.fr).
Tous les montants sont en centimes (entiers) :
1250= 12,50 €. Les heures sont des décimaux.
Soumettre des missions
POST /partner/v1/payroll-batches accepte de 1 à 5 000 lignes par appel. Le header
Idempotency-Key est obligatoire (UUIDv4 ou ULID).
Chaque ligne est complète et autonome : elle porte l'identité du salarié (groupe
worker), les données de la mission (groupe mission) et les éléments variables
éventuels (prime, indemnity) à la racine. Les lignes d'un même salarié
(même worker.external_id, après retrait des blancs de début et de fin et normalisation
Unicode NFC) sont regroupées sur le même bulletin.
Correspondance avec le format CSV. Si vous échangez aussi des fichiers CSV avec Pesia, la correspondance est mécanique : la colonne CSV
worker_x= le champ JSONworker.x,worker_birth_x=worker.birth.x,worker_address_x=worker.address.x,mission_y=mission.y; seule exception,mission_staffing_client_siret=staffing_client_establishment.siret.primeetindemnitysont identiques dans les deux formats. Deux tableaux demissionne suivent pas cette règle :mission.remuneration_linesetmission.post_tax_deductions. Le CSV les porte dans des groupes de colonnes fixes. Voir Lignes de rémunération et Retenues après PAS.Dans un fichier CSV, une colonne d'entiers (un montant en centimes,
mission_days_worked) ne porte que des chiffres. Si une cellule contient350.00,350,00ou1e3, Pesia refuse tout le fichier, sans importer aucune ligne, et indique la ligne et la colonne en cause.
curl -X POST https://api.pesia.fr/partner/v1/payroll-batches \
-H "Authorization: Bearer <token>" \
-H "Idempotency-Key: 9b2f5c1e-7a3d-4e8b-a6c9-2d4f8e0b1a53" \
-H "X-Pesia-Env: production" \
-H "Content-Type: application/json" \
-d '{
"agencyCode": "AG-PARIS-01",
"period": "2026-07",
"paymentDate": "2026-08-05",
"rows": [
{
"worker": {
"external_id": "MAT-4207",
"last_name": "MARTIN",
"first_name": "Camille",
"nir": "2940369111222",
"sex": "02",
"birth": { "date": "1994-03-12", "city": "Lyon", "department": "69" },
"address": { "street": "12 rue de la République", "postal_code": "69002", "city": "Lyon" },
"iban": "FR1420041010050500013M02606",
"agency_entry_date": "2025-11-03"
},
"mission": {
"external_id": "MIS-2026-1001",
"contract_external_id": "CTT-2026-078451",
"start_date": "2026-07-15",
"end_date": "2026-07-24",
"hourly_rate": 1250,
"remuneration_lines": [
{ "type": "NORMAL", "hours": 45 },
{ "type": "OVERTIME", "hours": 4, "premiums": [{ "type": "OVERTIME", "rate": 0.25 }] }
],
"post_tax_deductions": [
{
"type": "EARNED_WAGE_INSTALLMENT",
"amount": 35000,
"event_date": "2026-07-20",
"external_reference": "ACP-42",
"label": "Acompte du 20 juillet"
}
],
"days_worked": 8,
"qualification": "Serveur",
"employee_category": "employe",
"pcs_ese_code": "561b"
},
"staffing_client_establishment": {
"siret": "13002526500013"
}
},
{
"worker": {
"external_id": "MAT-4207",
"last_name": "MARTIN",
"first_name": "Camille",
"nir": "2940369111222",
"sex": "02",
"birth": { "date": "1994-03-12", "city": "Lyon", "department": "69" },
"address": { "street": "12 rue de la République", "postal_code": "69002", "city": "Lyon" },
"iban": "FR1420041010050500013M02606",
"agency_entry_date": "2025-11-03"
},
"mission": {
"external_id": "MIS-2026-1002",
"contract_external_id": "CTT-2026-078460",
"start_date": "2026-07-27",
"end_date": "2026-07-31",
"hourly_rate": 1250,
"remuneration_lines": [{ "type": "NORMAL", "hours": 35 }],
"days_worked": 5,
"address": "4 avenue du 8 Mai 1945",
"postal_code": "69800",
"city": "Saint-Priest"
},
"staffing_client_establishment": {
"siret": "13002526500013"
}
},
{
"worker": {
"external_id": "MAT-9001",
"last_name": "BERNARD",
"first_name": "Alex",
"nir": "1880875123456",
"sex": "01",
"birth": { "date": "1988-08-15", "city": "Paris", "department": "75" },
"address": { "street": "8 avenue des Fleurs", "postal_code": "75012", "city": "Paris" },
"iban": "FR7630006000011234567890189",
"agency_entry_date": "2026-02-16"
},
"mission": {
"external_id": "MIS-2026-1015",
"contract_external_id": "CTT-2026-078502",
"start_date": "2026-07-06",
"end_date": "2026-07-10",
"hourly_rate": 1300,
"remuneration_lines": [{ "type": "NORMAL", "hours": 35 }],
"days_worked": 5,
"address": "10 rue de Charenton",
"postal_code": "75012",
"city": "Paris"
},
"staffing_client_establishment": {
"siret": "13002526500013"
},
"prime": 5000,
"indemnity": 1150
}
]
}'
Champs de la requête
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
agencyCode | string | Oui | Valeur exacte du champ code retourné par GET /partner/v1/agencies avec le scope agency:read. Respectez la casse. |
period | string | Oui | Mois de paie ciblé, format YYYY-MM, à partir de 1970-01. |
paymentDate | string | Oui | Jour de versement des salaires, format YYYY-MM-DD. Distinct de period : voir ci-dessous. |
rows | object[] | Oui | Lignes du lot (1 ligne = 1 mission), 1 à 5 000 par appel. |
period et paymentDate sont deux dates différentesperiod est le mois travaillé. Il résout le SMIC, les minima de la convention collective
et le taux AT/MP de l'agence : le taux intérimaire en vigueur le premier jour de ce mois.
paymentDate est le jour où les salaires sont versés. Il résout le barème du prélèvement
à la source et son abattement pour contrat court. Pour un mois travaillé en juillet et payé le
5 août, envoyez "period": "2026-07" et "paymentDate": "2026-08-05".
Un lot porte une seule date de versement, reprise telle quelle sur la déclaration sociale
nominative et sur l'ordre de virement. Envoyez la même paymentDate pour chaque soumission d'un
lot. Pesia ne peut pas clôturer un lot dont les lignes portent des dates différentes : contactez
votre interlocuteur Pesia.
Champs d'une ligne — obligatoires
| Champ | Type | Description |
|---|---|---|
worker.external_id | string | Matricule du salarié dans votre système (1 à 255 caractères). Clé de regroupement : toutes les lignes d'un même matricule alimentent le même bulletin. La DSN le déclare comme matricule s'il compte au plus 30 caractères, tous admis par le cahier technique DSN : lettres, chiffres, lettres accentuées usuelles, espace et ponctuation simple, sans ! # $ % ; < > ? [ \ ] ^ { | } ~. Une valeur faite uniquement de ponctuation, par exemple -, n'est pas déclarée. Une autre valeur, par exemple un UUID de 36 caractères, reste acceptée et regroupe bien les lignes, mais la DSN part sans matricule. À la réception, Pesia retire les blancs au début et à la fin du matricule, par exemple une espace, une espace insécable, une tabulation ou un retour à la ligne. Pesia le normalise ensuite en Unicode NFC : une lettre suivie d'un accent combinant devient la lettre accentuée, en un seul caractère. Pesia ne modifie aucun autre caractère. Ainsi MAT-1, et MAT-1 suivi d'une espace, désignent le même salarié. La limite de 255 caractères s'applique au matricule normalisé. Les réponses renvoient le matricule normalisé dans workerExternalId. |
worker.last_name | string | Nom du salarié. |
worker.first_name | string | Prénom du salarié. |
worker.nir | string | NIR du salarié — 13 ou 15 caractères, avec ou sans espaces (2A/2B accepté en Corse). |
worker.sex | string | "01" homme, "02" femme. |
worker.birth.date | string | Date de naissance, format YYYY-MM-DD. |
worker.address.street | string | Adresse du salarié — numéro et voie. |
worker.address.postal_code | string | Code postal (5 chiffres). |
worker.address.city | string | Commune. |
worker.iban | string | IBAN du salarié — clé de contrôle vérifiée. Requis pour le virement du salaire et le bulletin. |
worker.agency_entry_date | string | Date d'entrée du salarié dans votre ETT, format YYYY-MM-DD — mention « Entrée dans l'ETT » du bulletin de paie. |
mission.contract_external_id | string | Référence du contrat dans votre système. Utilisez la même valeur que le contract.external_id de vos DPAE pour relier contrat et bulletin. C'est cette référence qui nomme le contrat sur le bulletin détaillé (« Contrat CTR-1042 du … au … ») : sans elle, un bulletin multi-contrat n'est rapprochable d'aucun document. |
mission.start_date | string | Premier jour de la période payée par ce bulletin, format YYYY-MM-DD. Ce n'est pas le début du contrat : voir contract_start_date. |
mission.end_date | string | Dernier jour de la période payée par ce bulletin, format YYYY-MM-DD (≥ début). Ce n'est pas le terme du contrat : voir contract_end_date. |
mission.hourly_rate | integer | Taux horaire en centimes, strictement positif (1250 = 12,50 €). |
mission.remuneration_lines | array | Heures travaillées sur la mission, une entrée par combinaison de nature et de majorations — voir Lignes de rémunération. Au moins une heure est requise par ligne de lot, et le total d'une mission ne dépasse pas 744 h. |
mission.days_worked | integer | Jours travaillés (1 à 31), borné à la durée calendaire de la mission. |
staffing_client_establishment.siret | string | SIRET valide de l'établissement où la mission est réalisée (14 chiffres et checksum Luhn). |
La DSN n'accepte qu'une partie des caractères. Pesia convertit les textes à la déclaration, et le bulletin garde la valeur reçue :
- nom et prénoms : lettres, lettres accentuées usuelles, espace,
'et-. « Ștefan » devient « Stefan », « N’Diaye » devient « N'Diaye » ; - commune du domicile : lettres sans accent, chiffres et espace. « Saint-Mandé » devient « Saint Mande » ;
- voie et commune de naissance : les mêmes caractères que le nom, plus les chiffres et le point ;
mission.contract_external_id, déclaré comme numéro de contrat : lettres, chiffres,- . @ / _, espace età é è ê ë ï ô ç. « CTR#2026 » devient « CTR 2026 ».
Un nom, un prénom, une voie ou une commune sans aucun caractère accepté, par exemple écrit dans un autre alphabet, bloque la clôture du lot. Corrigez les lignes du salarié et re-soumettez-les tant que le lot est ouvert.
Champs d'une ligne — optionnels
| Thème | Champ | Type | Défaut | Description |
|---|---|---|---|---|
| Corrélation | mission.external_id | string | — | Référence de la mission dans votre système — renvoyée telle quelle dans missions[] des réponses. |
| Identité | worker.nir_key | string | calculée | Clé de contrôle du NIR, de 01 à 97. Elle doit correspondre au NIR. |
| Identité | worker.birth.city | string | — | Commune de naissance — libellé (ex. Lyon). Absent à l'import accepté ; la clôture du lot le refuse (DSN). |
| Identité | worker.birth.department | string | — | Département de naissance (01-95, 2A/2B, 971-976, 984-988, 99 à l'étranger). Absent à l'import accepté ; la clôture du lot le refuse (DSN). |
| Identité | worker.birth.country | string | FR si né en France | Pays de naissance — code ISO 3166-1 alpha-2. Requis à la clôture quand le département vaut 99. |
| Identité | worker.birth.insee_code | string | — | Code INSEE de la commune de naissance (complément optionnel du libellé). |
| Identité | worker.email | string | — | Email du salarié. |
| Identité | worker.agency_exit_date | string | — | Date de sortie de l'ETT, format YYYY-MM-DD (≥ date d'entrée). Absente = salarié toujours inscrit ; l'absence remplace une sortie précédemment transmise (chaque lot transmet l'état complet). |
| Identité | worker.nationality | string | FR | Nationalité — code ISO 3166-1 alpha-2 (FR, PT, MA). Absent = FR pour la DSN, y compris pour un salarié né à l'étranger. Binational : indiquez FR si le salarié a la nationalité française. Apatride ou réfugié : non pris en charge dans cette version, contactez Pesia. |
| Protection sociale | worker.reached_414h | boolean | statut en vigueur le mois de period, sinon false | Seuil des 414 heures atteint. Il fixe le taux de prévoyance et la répartition de la mutuelle intérimaires. Absent : Pesia reprend le statut du salarié en vigueur le mois de period, commun à toutes les agences de votre organisation, sinon false. Même valeur sur toutes les lignes du salarié, ou absent sur toutes : sinon le salarié est refusé (ok: false). |
| Protection sociale | worker.health_insurance_enrolled | boolean | adhésion en vigueur si le seuil ne change pas, sinon worker.reached_414h | Adhésion à la mutuelle intérimaires, sous le seuil comme au-dessus. false au-dessus du seuil : dispense. Absent : Pesia reprend l'adhésion en vigueur le mois de period si le seuil ne change pas, sinon la valeur de worker.reached_414h. Salarié dispensé : envoyez false dès le lot où worker.reached_414h passe à true, sinon Pesia l'inscrit à la mutuelle. Même valeur sur toutes les lignes du salarié, ou absent sur toutes : sinon le salarié est refusé (ok: false). |
| Mission | mission.qualification | string | — | Qualification / poste, affiché sur le bulletin. |
| Mission | mission.pcs_ese_code | string | 9999 | Code PCS-ESE de l'emploi (ex. 621a). Consultez la liste complète des codes et libellés acceptés, aussi lisible par GET /partner/v1/pcs-ese-codes. Absent, Pesia déclare 9999 (profession non connue) : la clôture n'est pas bloquée par ce champ, mais la DSN est moins précise. |
| Mission | mission.employee_category | string | catégorie par défaut de l'agence | Catégorie du salarié : cadre_dirigeant, cadre, agent_de_maitrise, employe, ouvrier. Pilote le statut cadre. Absent, Pesia applique la catégorie par défaut configurée pour l'agence ; si l'agence n'en a aucune, la clôture du lot refuse la ligne. Renseignez ce champ dès que la catégorie diffère du défaut de l'agence — un cadre déclaré sous un défaut ouvrier part en DSN sous le mauvais statut. |
| Mission | mission.recourse_reason | string | accroissement_activite | Motif de recours au travail temporaire : remplacement_salarie, accroissement_activite, emploi_saisonnier, contrat_usage, remplacement_chef_entreprise, remplacement_chef_exploitation_agricole, insertion_difficultes_sociales, complement_formation, apprentissage, remplacement_temps_partiel, attente_suppression_poste, interimaire_boeth. |
| Lieu de mission | mission.address | string | établissement utilisateur | Voie du lieu de mission. Si ce champ est absent, Pesia utilise l'adresse de staffing_client_establishment. S'il est présent, postal_code et city sont obligatoires. |
| Lieu de mission | mission.postal_code | string | établissement utilisateur | Code postal du lieu de mission (5 chiffres), requis uniquement avec mission.address. |
| Lieu de mission | mission.city | string | établissement utilisateur | Commune du lieu de mission, requise uniquement avec mission.address. |
| Lieu de mission | mission.insee_code | string | calculé | Code INSEE de la commune du lieu de mission. Pesia le complète à partir du code postal et de la commune lorsqu'il est absent, ou le vérifie lorsqu'il est fourni. |
| Mission | mission.ifm_exclusion | string | — | Cas d'exclusion légale de l'IFM pour la mission (art. L1251-33) — voir IFM et ICCP. Absent = IFM due (cas nominal). |
| Mission | mission.post_tax_deductions | array | [] | Retenues appliquées après le prélèvement à la source. Une mission accepte 20 entrées au maximum, et un même type peut revenir. Voir Retenues après PAS. |
| Contrat | contract_start_date | string | mission.start_date | Premier jour du contrat, format YYYY-MM-DD. Obligatoire avec contract_end_date. Absent, le contrat est la mission de la ligne. |
| Contrat | contract_end_date | string | mission.end_date | Fin du terme du contrat, format YYYY-MM-DD. Obligatoire avec contract_start_date. Terme précis : la fin inscrite au contrat. Terme imprécis : la fin de la durée minimale. |
| Contrat | contract_term_kind | string | terme précis | precis (date de fin nommée au contrat) ou imprecis (durée minimale seule). Dit ce que contract_end_date signifie. Absent, Pesia lit un terme précis. |
| Primes | prime | integer | — | Complément de rémunération du mois soumis à cotisations, en centimes. |
| Primes | indemnity | integer | — | Indemnité non soumise (panier, transport…), en centimes, ajoutée au net. |
| Primes | prime_label | string | Prime | Libellé de la prime manuelle sur le bulletin, de 1 à 200 caractères. |
| Primes | indemnity_label | string | Indemnité | Libellé de l'indemnité manuelle sur le bulletin, de 1 à 200 caractères. |
Une ligne porte deux faits distincts, et ils ne se déduisent pas l'un de l'autre.
mission.start_date et mission.end_date délimitent la période payée par ce bulletin.
Elles proratisent le plafond mensuel de sécurité sociale, et elles bougent à chaque bulletin
d'une même mission.
contract_start_date et contract_end_date délimitent le contrat. Elles décident de
l'abattement de prélèvement à la source réservé aux contrats de deux mois ou moins, et elles
gardent la même valeur sur tous les bulletins d'un même contrat.
Tant qu'une ligne couvre une mission entière, les deux coïncident et vous n'avez rien à
envoyer. Dès qu'une mission de plus de deux mois est payée en plusieurs tranches mensuelles,
chaque tranche ressemble à un contrat court : renseignez contract_start_date et
contract_end_date, sinon l'abattement s'applique à tort et le prélèvement est sous-évalué.
Pesia ne peut pas détecter l'oubli. Une dernière tranche porte exactement les mêmes dates qu'une mission courte entière.
Le champ prime couvre uniquement le complément du mois. Une prime non mensuelle
requiert une qualification spécifique. Son libellé ne définit jamais cette qualification.
Les champs déclaratifs portent des valeurs métier. Pesia les traduit vers les codes de la norme DSN ; ne transmettez jamais un code DSN à leur place.
Retenues après PAS
mission.post_tax_deductions porte les sommes à retirer après le prélèvement à la source.
Le tableau accepte 20 entrées au maximum par mission. Cette limite borne la taille de la
requête et ne porte aucune règle métier. Plusieurs entrées peuvent porter le même type :
donnez alors à chacune sa propre external_reference pour les distinguer.
Le format CSV garde un seul groupe de colonnes par type, donc une seule entrée par type. Pour plusieurs entrées du même type, utilisez l'API.
| Champ | Type | Description |
|---|---|---|
type | string | EARNED_WAGE_INSTALLMENT, SALARY_ADVANCE_REPAYMENT ou WAGE_GARNISHMENT. |
amount | integer | Montant en centimes. Utilisez une valeur de 1 à 100 000 000. |
event_date | string | Date facultative au format YYYY-MM-DD. |
external_reference | string | Référence facultative. Le bulletin détaillé l'imprime. Utilisez 255 caractères au maximum, sans caractère de contrôle ni caractère invisible. Recommandée pour distinguer deux entrées du même type. |
label | string | Libellé facultatif. Le bulletin détaillé l'imprime, sauf s'il répète le libellé du type. Le bulletin simplifié imprime seulement le total de chaque type. Utilisez 200 caractères au maximum, sans caractère de contrôle ni caractère invisible. |
Le fichier CSV porte une retenue dans 4 colonnes, une par champ JSON du tableau ci-dessus. Une mission ne déclare qu'une entrée par type dans le fichier : pour plusieurs entrées du même type, utilisez l'API.
| Retenue | Colonne | Champ JSON |
|---|---|---|
EARNED_WAGE_INSTALLMENT | mission_earned_wage_installment_amount | amount |
EARNED_WAGE_INSTALLMENT | mission_earned_wage_installment_event_date | event_date |
EARNED_WAGE_INSTALLMENT | mission_earned_wage_installment_external_reference | external_reference |
EARNED_WAGE_INSTALLMENT | mission_earned_wage_installment_label | label |
SALARY_ADVANCE_REPAYMENT | mission_salary_advance_repayment_amount | amount |
SALARY_ADVANCE_REPAYMENT | mission_salary_advance_repayment_event_date | event_date |
SALARY_ADVANCE_REPAYMENT | mission_salary_advance_repayment_external_reference | external_reference |
SALARY_ADVANCE_REPAYMENT | mission_salary_advance_repayment_label | label |
WAGE_GARNISHMENT | mission_wage_garnishment_amount | amount |
WAGE_GARNISHMENT | mission_wage_garnishment_event_date | event_date |
WAGE_GARNISHMENT | mission_wage_garnishment_external_reference | external_reference |
WAGE_GARNISHMENT | mission_wage_garnishment_label | label |
Chaque colonne porte la même règle que son champ JSON, décrite dans le tableau ci-dessus. Le fichier CSV ajoute 4 règles propres à son format :
- La colonne
amountporte un entier en centimes, écrit avec des chiffres seuls :35000pour 350,00 €. Si la cellule contient350.00,350,00ou1e3, Pesia refuse tout le fichier, sans importer aucune ligne, et indique la ligne et la colonne en cause. - Une mission sans retenue d'un type laisse les 4 colonnes de ce type vides.
- Une colonne
event_date,external_referenceoulabelremplie exige la colonneamountdu même type. - Ces 12 colonnes sont facultatives dans l'en-tête du fichier, et leur ordre n'est pas obligatoire.
Pesia additionne les entrées d'un même type sur toutes les missions du salarié. Pesia refuse le calcul si le total des retenues dépasse le net après PAS. Un total égal au net après PAS produit un net à verser nul et aucun virement salarié.
Ce refus n'apparaît pas dans la réponse du POST, qui reste 202. Le bulletin du salarié prend
ensuite le statut FAILED avec l'erreur PARTNER_PAYROLL_PAYSLIP_COMPUTATION_FAILED
(retryable: false — voir Suivre le lot), et failedCount augmente d'une
unité. Corrigez les retenues, puis soumettez de nouveau les lignes du salarié : son bulletin
repasse en PENDING, puis Pesia le calcule de nouveau.
Vous restez responsable de la légalité de chaque acompte pour un salarié intérimaire. Vous restez responsable du plafond de remboursement d'une avance. Vous calculez aussi la quotité saisissable. Pesia reçoit le montant final. Pesia ne calcule pas ces trois règles dans cette version.
Lignes de rémunération
mission.remuneration_lines porte les heures de la mission. Une mission en déclare au
plus six, et chacune sépare deux choses :
- la nature des heures —
NORMAL,OVERTIME(supplémentaires) ouCOMPLEMENTARY(complémentaires, temps partiel) ; - les majorations qui s'y appliquent, chacune avec son taux explicite.
Une majoration de 0.25 augmente le taux horaire de 25 %. Ce n'est pas un coefficient :
1.25 serait une augmentation de 125 %. Les majorations d'une même ligne s'additionnent, et
chacune s'applique au taux horaire de base.
| Champ | Type | Défaut | Description |
|---|---|---|---|
type | string | — | NORMAL, OVERTIME ou COMPLEMENTARY. Requis. |
hours | number | — | Heures de cette ligne (décimal, strictement positif). Requis. |
premiums | array | [] | Majorations appliquées à ces heures. |
premiums[].type | string | — | OVERTIME, COMPLEMENTARY, NIGHT, SUNDAY ou HOLIDAY. |
premiums[].rate | number | — | Taux d'augmentation, strictement positif (0.25 = +25 %). |
hourly_wage_cents | integer | mission.hourly_rate | Taux horaire de base de cette ligne, en centimes. À renseigner uniquement si une ligne se paie à un autre taux que la mission. |
overtime_kind | string | VARIABLE | Réservé aux lignes OVERTIME : VARIABLE (aléatoires) ou STRUCTURAL (structurelles, inscrites au contrat). Le taux de majoration ne la détermine pas — des heures à +25 % peuvent relever de l'une ou l'autre nature, et la DSN les déclare séparément. |
Deux règles de forme, et toutes deux sont des règles de droit :
- une ligne
OVERTIMEporte obligatoirement une majorationOVERTIME, et une ligneCOMPLEMENTARYune majorationCOMPLEMENTARY— le code du travail place ce taux au-dessus de zéro, donc aucune valeur par défaut n'est possible ; - une majoration
OVERTIMEouCOMPLEMENTARYne s'applique qu'à des heures de sa propre nature.NIGHT,SUNDAYetHOLIDAYs'appliquent à toutes les natures.
Un type de majoration n'apparaît qu'une fois par ligne : deux majorations de nuit sur les mêmes heures paieraient la nuit deux fois.
Exemple — des heures supplémentaires travaillées le dimanche
Un salarié fait 70 heures normales, 13 heures supplémentaires à +25 %, 2 heures à +50 %, puis 8 heures supplémentaires un dimanche (+50 % de majoration dominicale) et 6 heures supplémentaires de nuit (+15 %). Taux horaire : 13,50 €.
"remuneration_lines": [
{ "type": "NORMAL", "hours": 70 },
{ "type": "OVERTIME", "hours": 13, "premiums": [{ "type": "OVERTIME", "rate": 0.25 }] },
{ "type": "OVERTIME", "hours": 2, "premiums": [{ "type": "OVERTIME", "rate": 0.5 }] },
{ "type": "OVERTIME", "hours": 8,
"premiums": [{ "type": "OVERTIME", "rate": 0.25 }, { "type": "SUNDAY", "rate": 0.5 }] },
{ "type": "OVERTIME", "hours": 6,
"premiums": [{ "type": "OVERTIME", "rate": 0.25 }, { "type": "NIGHT", "rate": 0.15 }] }
]
Le bulletin porte alors 99 heures et 1 507,28 € de brut au titre des heures :
| Rubrique | Heures | Montant |
|---|---|---|
| Heures normales | 70 | 945,00 € |
| Heures supplémentaires (125 %) | 13 | 219,38 € |
| Heures supplémentaires (150 %) | 2 | 40,50 € |
| Heures supplémentaires (125 %) | 8 | 135,00 € |
| Majoration dimanche | — | 54,00 € |
| Heures supplémentaires (125 %) | 6 | 101,25 € |
| Majoration nuit | — | 12,15 € |
Les 8 heures du dimanche et les 6 heures de nuit restent trois lignes distinctes de
Heures supplémentaires (125 %). Les fusionner étalerait la majoration dominicale sur
27 heures au lieu des 8 qui l'ont gagnée.
Les majorations du moment portent un montant sans heures : le décompte des heures du bulletin reste juste, et le salarié retrouve le nombre d'heures qu'il a travaillées.
Colonnes du fichier CSV
Le fichier CSV porte chaque ligne de rémunération dans les colonnes du tableau ci-dessous. N
est le numéro de la ligne de rémunération, de 1 à 6.
| Colonne | Champ JSON |
|---|---|
mission_lineN_type | type |
mission_lineN_hours | hours |
mission_lineN_premium | premiums[].rate de la majoration OVERTIME ou COMPLEMENTARY, selon la nature de la ligne |
mission_lineN_premium_night | premiums[].rate de la majoration NIGHT |
mission_lineN_premium_sunday | premiums[].rate de la majoration SUNDAY |
mission_lineN_premium_holiday | premiums[].rate de la majoration HOLIDAY |
Chaque colonne porte la même règle que son champ JSON, décrite dans le tableau des champs, plus haut dans cette section. Le fichier CSV ajoute 5 règles propres à son format :
- L'en-tête nomme le type de chaque majoration : la cellule ne porte que son taux (
0.25= +25 %). La colonnemission_lineN_premiumporte la majoration propre à la nature de la ligne. Remplissez-la sur une ligneOVERTIMEouCOMPLEMENTARY, et laissez-la vide sur une ligneNORMAL. Sinon, Pesia refuse le fichier. - Numérotez les lignes de rémunération sans trou, à partir de
N= 1 : si la ligne 3 est remplie, les lignes 1 et 2 le sont aussi. Un groupe de colonnes vide entre deux groupes remplis est refusé, et l'erreur nomme la colonnemission_lineN_typedu groupe vide. - Écrivez les heures et les taux avec un point décimal :
7.5,0.25. Une virgule est refusée. - Les colonnes d'une ligne de rémunération sont facultatives dans l'en-tête, et leur ordre n'est pas obligatoire. Une mission sans aucune heure est refusée.
- Le fichier n'a pas de colonne pour
hourly_wage_centsni pourovertime_kind. Chaque ligne se paie au taux horaire de la mission (mission_hourly_rate), et ses heures supplémentaires sontVARIABLE. Pour un autre taux horaire, ou pour des heuresSTRUCTURAL, utilisez l'API.
Libellés des primes et indemnités
Placez les libellés à la racine de chaque ligne, à côté des montants. Par exemple, ajoutez ces champs à une ligne de mission complète :
{
"prime": 2500,
"prime_label": "Majoration dimanche",
"indemnity": 1800,
"indemnity_label": "Frais kilométriques"
}
Les colonnes CSV portent les mêmes noms. Omettez un libellé dans le JSON, ou laissez sa cellule CSV vide, pour conserver « Prime » ou « Indemnité ». Un texte fourni contient de 1 à 200 caractères. Un libellé seul ne crée pas de ligne sur le bulletin : le montant associé doit être supérieur à zéro.
Le libellé change uniquement le texte du bulletin. Il ne change ni les cotisations, ni le code DSN. Il reste une seule prime et une seule indemnité manuelles par mission.
Indemnités sur le bulletin simplifié
Pesia produit le bulletin sous deux présentations. Le bulletin détaillé présente chaque contrat séparément. Le bulletin simplifié regroupe les cotisations par catégorie, les retenues après PAS par type et les indemnités.
Le bloc « Indemnités et primes » contient votre champ indemnity et les autres indemnités
du bulletin, par exemple le repas ou le blanchissage. Votre champ prime n'entre pas dans
ce bloc : il figure dans les éléments de rémunération, avec une ligne par contrat.
Deux lignes se regroupent lorsque le libellé, le taux exact et l'unité sont identiques.
La casse et les espaces du libellé comptent. Pour regrouper les indemnités de deux missions
d'un même bulletin, envoyez le même indemnity_label, caractère pour caractère. Un taux
différent reste sur une autre ligne, même si son arrondi affiché est identique.
La base est la somme des quantités pour une indemnité horaire ou journalière. Elle s'affiche alors sans unité, avec deux décimales au plus. Pour les autres lignes, la base est la somme des montants de base. Le montant est la somme des montants de chaque contrat. Il ne résulte pas d'un nouveau calcul sur la base totale.
Par exemple, quatre indemnités de blanchissage à 0,16 € de l'heure portent sur 3,83 h, 4,25 h, 4,08 h et 4,83 h. Leurs montants sont 0,61 €, 0,68 €, 0,65 € et 0,77 €. Quatre indemnités de repas portent chacune sur un jour à 2,30 €. Le bloc affiche alors :
| Désignation | Base | Taux | Montant (€) |
|---|---|---|---|
| Indemnité de blanchissage | 16,99 | 0,16 € | 2,71 € |
| Indemnité de repas | 4 | 2,30 € | 9,20 € |
La base du blanchissage compte des heures. La base du repas compte des jours. Un nouveau calcul sur la base totale donnerait 2,72 € (16,99 × 0,16 €). Le bulletin affiche 2,71 €, la somme des montants de chaque contrat.
Ce bloc ne porte aucun titre de contrat sur le bulletin simplifié. Les éléments de rémunération conservent leurs titres et leurs lignes par contrat. Le bulletin détaillé conserve sa présentation par contrat.
Adresse du lieu de mission
Par défaut, omettez mission.address, mission.postal_code, mission.city et
mission.insee_code : Pesia prend l'adresse et le code commune INSEE de
staffing_client_establishment, enrichis via SIRENE. Ce cas correspondra dans l'application au choix
« même adresse que l'entreprise utilisatrice ».
Si la mission se déroule ailleurs, fournissez ensemble mission.address,
mission.postal_code et mission.city. Le champ mission.insee_code reste optionnel :
Pesia le résout ou le vérifie via son référentiel de communes. Ce cas correspondra au choix
« adresse différente de l'entreprise utilisatrice ».
Effectif de paie
Pesia calcule chaque bulletin avec l'effectif de paie de l'organisation pour le mois du bulletin, en équivalents temps plein (ETP). Cet effectif détermine les taux liés à la tranche d'effectif, les contributions de mobilité et la DSN.
- Si l'année précédente est définitive, l'effectif de paie est la borne basse de la tranche
d'effectif de cette année :
0(moins de 11 ETP),11(de 11 à 19),20(de 20 à 49),50(de 50 à 249) ou250(250 et plus). Une année est définitive quand elle est terminée et que son effectif est validé. - Sinon, ou si l'année précédente n'a aucun effectif, Pesia utilise l'effectif déclaré dans les paramètres de paie de l'agence.
- Dans les deux cas, une exception PACTE (neutralisation du franchissement d'un seuil d'effectif) en cours au 1er du mois plafonne l'effectif de paie sous le seuil franchi. Par exemple, une exception sur le seuil de 20 ramène l'effectif à 11 au plus.
Pesia fige l'effectif de paie quand vous soumettez les lignes d'un salarié. Si Pesia vous
signale un changement de l'effectif de paie pendant que le lot est ouvert, soumettez de nouveau
les lignes de tous les salariés du lot, avec une nouvelle Idempotency-Key.
Un lot dont les bulletins portent des effectifs différents ne peut pas être clôturé.
L'API ne renvoie pas l'effectif retenu. Pour le connaître, ou pour déclarer une exception PACTE, contactez votre interlocuteur Pesia (partenaires@pesia.fr).
Contributions de mobilité
Quand l'effectif de paie atteint 11 ETP, VM et VMA suivent la commune du lieu de mission. VMRR suit la commune de l'entité légale de l'agence. Pesia calcule les contributions sur le bulletin. La DSN reprend ses assiettes, taux, montants et communes.
Une recherche réussie sans taux applicable signifie que la contribution ne s'applique pas. Une commune sans VMRR produit donc un montant VMRR nul. Une erreur technique fait échouer le calcul. Un lieu nécessaire absent bloque le calcul et la clôture définitive.
IFM et ICCP
Les indemnités légales de fin de mission (IFM, 10 %) et compensatrice de congés payés (ICCP, 10 %, calculée sur le brut IFM comprise) sont calculées automatiquement par Pesia sur chaque bulletin intérim — vous n'avez rien à envoyer. Elles sont versées au fil de l'eau, chaque mois : sur une mission à cheval sur plusieurs mois, la somme des versements mensuels égale exactement 10 % de la rémunération totale de la mission — vous n'avez donc pas à renvoyer les mois précédents.
Si la mission relève d'un cas légal d'exclusion de l'IFM (art. L1251-33), renseignez
mission.ifm_exclusion avec l'un des codes suivants — l'IFM est alors mise à zéro et la
raison est tracée sur le bulletin :
| Code | Cas |
|---|---|
CDI_PROPOSE_ACCEPTE | CDI proposé par l'entreprise utilisatrice à l'issue de la mission, accepté |
CDI_PROPOSE_REFUSE | CDI équivalent proposé, refusé par le salarié |
FAUTE_GRAVE | Rupture anticipée pour faute grave du salarié |
FORCE_MAJEURE | Rupture pour force majeure |
RUPTURE_ANTICIPEE_SALARIE | Rupture anticipée à l'initiative du salarié |
CONTRAT_SAISONNIER | Contrat saisonnier (si prévu par accord) |
CONTRAT_USAGE | Contrat d'usage (si prévu par accord) |
PERIODE_ESSAI | Rupture pendant la période d'essai |
L'ICCP reste toujours due pour un contrat
CTT— la loi ne prévoit aucun cas d'exclusion : c'est pourquoi seul l'IFM porte ce champ.
Le corps est validé dans son ensemble : une ligne invalide (champ manquant, format incorrect, dates incohérentes…) rejette toute la requête en
400, avec le détail par ligne. Une fois le corps accepté, le traitement devient fail-soft par salarié (voir ci-dessous).
Réponse 202 Accepted
Les missions sont prises en charge et les salariés sont dispatchés vers le calcul asynchrone des bulletins. La réponse détaille le résultat salarié par salarié — le traitement est fail-soft : un salarié en échec ne bloque pas les autres.
{
"success": true,
"data": {
"mode": "production",
"lotId": "0d5c2a4e-9f1b-4c7d-8e3a-6b2f4a8c0d15",
"period": "2026-07",
"lotStatus": "OPEN",
"workers": [
{
"workerExternalId": "MAT-4207",
"ok": true,
"missions": [
{ "missionExternalId": "MIS-2026-1001", "contractExternalId": "CTT-2026-078451" },
{ "missionExternalId": "MIS-2026-1002", "contractExternalId": "CTT-2026-078460" }
]
},
{
"workerExternalId": "MAT-9001",
"ok": false,
"error": {
"code": "SIRENE_STAFFING_CLIENT_CLOSED",
"message": "L'établissement utilisateur est fermé pour la période de mission.",
"retryable": false
},
"missions": [
{ "missionExternalId": "MIS-2026-1015", "contractExternalId": "CTT-2026-078502" }
]
}
]
},
"timestamp": "2026-08-10T12:00:00.000Z"
}
mode—productionousandbox(voir Sandbox).lotId— identifiant du lot canonique alimenté. Conservez-le pour le suivi viaGET /partner/v1/payroll-batches/{lotId}. Il peut êtrenullsi aucun salarié n'a pu être pris en compte (tous enok: false), ou en mode sandbox.lotStatus— statut du lot au moment de la réponse (voir cycle de vie du lot).workers[]— un élément par salarié (regroupement parworker.external_id) :ok: true— le salarié est pris en compte. Pesia lance le calcul de son bulletin ; un calcul refusé prend ensuite le statutFAILED;ok: false— le salarié n'a pas été pris en compte ; corrigez ses lignes et re-soumettez-les. Les autres salariés du lot ont été traités normalement ;error.code— code stable exploitable par votre intégration ;error.retryable— indique si une nouvelle tentative est recommandée. Pour un quota SIRENE,error.retryAfterMsprécise le délai minimal ;missions[]— vos références externes (mission.external_id,mission.contract_external_id), renvoyées pour corrélation.
Rejouer la même requête avec la même Idempotency-Key renvoie la même
réponse (même lotId) sans retraiter les missions.
Un lot par agence et par mois
Le lot de paie est l'unité de traitement mensuelle : Pesia calcule les bulletins des salariés du lot, puis clôture le lot au moment du traitement de la paie du mois.
| Statut | Signification |
|---|---|
OPEN | Lot ouvert — vos soumissions alimentent ce lot. |
CLOSING | Clôture engagée — les derniers bulletins se finalisent. |
CLOSED | Lot clôturé. Toute nouvelle soumission sur la période renvoie 409. |
Pour toute question sur la clôture d'un lot (calendrier, correction après clôture), contactez votre interlocuteur Pesia (partenaires@pesia.fr).
Suivre le lot
curl https://api.pesia.fr/partner/v1/payroll-batches/0d5c2a4e-9f1b-4c7d-8e3a-6b2f4a8c0d15 \
-H "Authorization: Bearer <token>" \
-H "X-Pesia-Env: production"
Réponse 200 OK :
{
"success": true,
"data": {
"lotId": "0d5c2a4e-9f1b-4c7d-8e3a-6b2f4a8c0d15",
"agencyCode": "AG-PARIS-01",
"period": "2026-07",
"lotStatus": "OPEN",
"expectedCount": 3,
"computedCount": 1,
"failedCount": 2,
"closedAt": null,
"workers": [
{
"workerExternalId": "MAT-4207",
"computed": true,
"status": "COMPUTED",
"missions": [
{ "missionExternalId": "MIS-2026-1001", "contractExternalId": "CTT-2026-078451", "pcsEseCode": "561b" },
{ "missionExternalId": "MIS-2026-1002", "contractExternalId": "CTT-2026-078460", "pcsEseCode": "9999" }
]
},
{
"workerExternalId": "MAT-9001",
"computed": false,
"status": "FAILED",
"error": {
"code": "PARTNER_PAYROLL_PAYSLIP_COMPUTATION_FAILED",
"message": "Le calcul du bulletin a échoué.",
"retryable": false
},
"missions": [
{ "missionExternalId": "MIS-2026-1015", "contractExternalId": "CTT-2026-078502", "pcsEseCode": "9999" }
]
},
{
"workerExternalId": "MAT-9002",
"computed": false,
"status": "FAILED",
"error": {
"code": "PARTNER_PAYROLL_PAYSLIP_COMPUTATION_TEMPORARY_FAILURE",
"message": "Le calcul du bulletin est temporairement indisponible. Soumettez de nouveau les lignes du salarié.",
"retryable": true
},
"missions": [
{ "missionExternalId": "MIS-2026-1016", "contractExternalId": "CTT-2026-078503", "pcsEseCode": "621a" }
]
}
]
},
"timestamp": "2026-08-10T12:00:00.000Z"
}
expectedCount— salariés attendus dans le lot.computedCount— salariés dont le bulletin porte le statutCOMPUTED.failedCount— salariés dont le bulletin porte le statutFAILED(les deux causes ci-dessous confondues).closedAt— horodatage de clôture (ISO 8601, UTC),nulltant que le lot est ouvert.workers[].status— statut du calcul :PENDING,COMPUTEDouFAILED.workers[].computed— compatibilité avec les versions précédentes. La valeur esttrueuniquement pour le statutCOMPUTED.workers[].missions[].pcsEseCode— code PCS-ESE que Pesia déclare en DSN pour la mission.9999(profession non connue) indique que la ligne ne portait pas demission.pcs_ese_code, ou portait9999. Pour déclarer la profession réelle, renseignez le code de l'emploi. Le champ est absent quand Pesia ne détient aucun code valide pour la mission. La réponse duPOSTne porte pas ce champ.workers[].error— présent uniquement pour le statutFAILED. Il ne contient aucun diagnostic interne, et prend l'une de ces deux formes fixes :code: "PARTNER_PAYROLL_PAYSLIP_COMPUTATION_FAILED",retryable: false— le calcul refuse les lignes soumises. Corrigez-les avant de les soumettre à nouveau ;code: "PARTNER_PAYROLL_PAYSLIP_COMPUTATION_TEMPORARY_FAILURE",retryable: true— indisponibilité temporaire. Soumettez de nouveau les mêmes lignes du salarié, avec une nouvelleIdempotency-Key. Espacez vos nouvelles tentatives d'un délai croissant (par exemple 1 min, puis 5 min, puis 15 min) et arrêtez après 3 essais. Si l'échec persiste au-delà de ces essais, contactez votre interlocuteur Pesia (partenaires@pesia.fr) plutôt que de soumettre de nouveau les mêmes lignes en boucle.
Le calcul d'un lot se termine généralement en quelques minutes. Une boucle de polling
raisonnable — toutes les 60 secondes, jusqu'à ce que chaque bulletin porte le statut COMPUTED
ou FAILED :
while true; do
body=$(curl -s https://api.pesia.fr/partner/v1/payroll-batches/<lotId> \
-H "Authorization: Bearer <token>" \
-H "X-Pesia-Env: production")
echo "$body" | jq '{lotStatus: .data.lotStatus, computed: .data.computedCount, failed: .data.failedCount, expected: .data.expectedCount}'
[ "$(echo "$body" | jq '(.data.computedCount + .data.failedCount) == .data.expectedCount')" = "true" ] && break
sleep 60
done
Pour être notifié de la publication des bulletins sans interroger l'API en boucle,
abonnez-vous à l'événement payroll.payslip.published du catalogue
Webhooks.
Sandbox
Avec un accès sandbox (header X-Pesia-Env: sandbox), le dépôt est un dry-run : le
payload et le contexte agence (fiche légale, paramètres de paie, taux AT/MP intérimaire de
la période) sont validés, mais
rien n'est persisté et aucun bulletin n'est calculé. La réponse porte
mode: "sandbox", lotId: null et lotStatus: null ; workers[].ok reflète la
validation de chaque salarié et workers[].error décrit les lignes refusées.
Le sandbox permet de valider votre intégration — format des lignes, regroupement par salarié, corrélation par références externes, idempotence — sans polluer un vrai lot de paie (voir Sandbox).
Erreurs
Toutes les erreurs suivent le format application/problem+json (RFC 7807), comme le
reste de l'API.
Après acceptation du corps, une erreur limitée à un salarié reste dans la réponse 202
via workers[].error. Les codes SIRENE_STAFFING_CLIENT_* demandent une correction métier ;
les codes SIRENE dont retryable vaut true signalent une indisponibilité temporaire.
| Statut HTTP | Cause | Action recommandée |
|---|---|---|
400 | Corps invalide (champ manquant ou mal formé, lot vide ou > 5 000 lignes, dates incohérentes, IBAN ou NIR invalide). | Corrigez la requête à partir du détail par ligne fourni dans la réponse. |
400 | Fiche légale ou paramètres de paie de l'agence incomplets. | Contactez votre interlocuteur Pesia (partenaires@pesia.fr) pour compléter la configuration de l'agence. |
400 | Contexte de paie de l'agence refusé (PARTNER_PAYROLL_BATCH_INVALID_INPUT) : aucun taux AT/MP intérimaire en vigueur le premier jour de period, ou donnée de la fiche légale absente. | Vérifiez period. Si elle est correcte, contactez votre interlocuteur Pesia (partenaires@pesia.fr) en citant le code agence, la période et le detail. Voir PARTNER_PAYROLL_BATCH_INVALID_INPUT. |
400 | idempotency_key_required / idempotency_key_invalid_format. | Fournissez un header Idempotency-Key au format UUIDv4 ou ULID. |
403 | insufficient_scope. | Le scope payroll:simulate manque à votre grant — voir Scopes. |
404 | Agence (agencyCode) ou lot (lotId) introuvable. | Vérifiez le champ code retourné par GET /partner/v1/agencies, ou l'identifiant de lot retourné au dépôt. |
409 | Le lot de paie de l'agence pour la période est clôturé — soumission impossible. | Le mois est clos : aucune mission ne peut plus y être ajoutée. Contactez votre interlocuteur Pesia si une régularisation est nécessaire. |
409 | idempotency_key_conflict — même clé réutilisée avec un corps différent. | Générez une nouvelle Idempotency-Key par soumission distincte. |
Voir aussi
- Scopes — scope
payroll:simulate - Codes PCS-ESE — codes acceptés, aussi lisibles par
GET /partner/v1/pcs-ese-codes - Idempotence — header
Idempotency-Key - DPAE — déposer les DPAE des mêmes contrats de mission
- Webhooks — événement
payroll.payslip.published - Sandbox — environnement de test