Découvrir ce que vous pouvez appeler

Deux lectures vous disent ce que vos identifiants autorisent et ce que la société cliente possède réellement, avant le premier appel d'écriture : votre profil et les référentiels.

Les libellés reviennent dans votre langue

Envoyez Accept-Language et tout libellé lisible de la réponse est rendu dans cette langue — types de contrat, noms de types de congés, et le texte d'un refus. Langues gérées : de-DE, en-US, es-ES, fr-FR, it-IT, pl-PL, pt-PT, tr-TR. Toute autre valeur retombe sur la langue par défaut plutôt que d'échouer : un libellé dans la mauvaise langue est un moindre mal qu'une absence de réponse.

Rien de ce sur quoi vous branchez ne change. Les codes — code d'un type de congé, hiringCode d'un statut professionnel, codeIdcc d'une convention — sont identiques dans toutes les langues, et c'est sur eux que votre intégration s'appuie. Seul ce que vous affichez bouge.

Les identifiants sont opaques — et pas uniques d'un type à l'autre

Tout identifiant servi par cette API — un salarié, un contrat, un établissement, une ligne de congé, un bulletin — est une chaîne opaque. Stockez-la et renvoyez-la telle quelle : n'en construisez pas, n'en analysez pas le contenu, et ne supposez pas que deux d'entre eux se suivent.

La même chaîne peut être un identifiant valide dans plusieurs lectures. Un seul encodage sert toute l'API et rien dans la chaîne ne dit ce qu'elle désigne : une valeur qui se résout en contrat sur GET /api/v1/client/employments/{id} peut se résoudre en même temps en ligne de congé sur GET /api/v1/client/leave-requests/{id} — deux enregistrements sans rapport. Aucune des deux réponses ne le signale, puisque l'identifiant ne porte aucun type.

Ce qu'il faut en faire côté stockage : conservez chaque identifiant avec la lecture dont il provient, jamais dans une table unique d'« ids », et ne rapprochez jamais des identifiants de natures différentes. Passer un identifiant à la mauvaise lecture n'est pas une erreur que cette API peut détecter pour vous : elle répond 404, ou elle répond un enregistrement qui n'a rien à voir avec celui que vous visiez, et les deux ressemblent à une réponse ordinaire.

Votre profil

GET /api/v2/client/partner/profile (scopes : partner:read)

{
  "partnerId": "partner-sample",
  "tenantIds": [
    "PB56eJ"
  ],
  "grantedScopes": [
    "external-api",
    "partner:read",
    "payroll:read",
    "payroll:trigger",
    "referentials:read",
    "companies:read",
    "employees:read",
    "employments:read",
    "leaves:read",
    "beta:access"
  ],
  "plan": "Standard",
  "modules": [
    {
      "moduleId": 1,
      "enabled": true,
      "advanced": false
    }
  ]
}
Champ Ce qu'il vous dit
partnerId, tenantIds L'identité résolue par l'API depuis votre jeton, et les établissements clients sur lesquels vos appels agissent. Un identifiant peut être mandaté sur plusieurs ; jusqu'où il descend sous chacun n'est pas publié ici. Les lectures répondent sur tout le périmètre : une liste couvre tous les établissements qu'il atteint, et un {id} appartenant à l'un d'eux se résout. Un établissement qui n'a pas le module dont une lecture dépend est absent de cette lecture, sans être nommé — mais la réponse dit qu'elle est écourtée, via le bloc partial décrit plus bas.
grantedScopes Les scopes réellement présents sur le jeton — le moyen le plus rapide de diagnostiquer un 403.
plan Votre palier de quota. Voir la réserve ci-dessous.
modules État de chaque module Staff & Go sur l'ensemble de votre périmètre : enabled dès qu'un établissement qu'il atteint le détient, plus advanced pour le second niveau, indépendant, que certaines fonctionnalités exigent. Il ne dit pas quel établissement détient quoi.

plan n'est pas encore un palier commercial. Aujourd'hui tous les identifiants renvoient Standard, parce que le provisioning ne le renseigne pas : traitez-le comme un emplacement réservé, pas comme le plan que vous avez souscrit. Vos valeurs de quota viennent de votre interlocuteur Staff & Go et des en-têtes RateLimit, pas de ce champ. Voir Limites de débit.

Un module non activé fait refuser les appels des capacités qui en dépendent : lire modules d'abord vous épargne un refus. La correspondance entre module et capacité n'est pas encore publiée — demandez-la à votre interlocuteur Staff & Go d'ici là.

GET /api/v1/client/partner/profile reste disponible : il renvoie partnerId, tenantIds et scopes (renommé grantedScopes en v2), et aucun des champs ci-dessus. Voir Versionnement.

tenantId est devenu tenantIds, une liste, sur les deux versions, du jour où un identifiant a pu être mandaté sur plusieurs établissements. C'est un changement cassant, assumé sur v1 plutôt que différé : une valeur unique ne peut pas décrire un tel identifiant, et en publier une aurait été faux, pas seulement incomplet.

Quand un champ n'a pas pu être déterminé

Un champ que l'API n'a pas pu lire revient à null et est nommé dans unavailable, avec le motif :

{
  "modules": null,
  "unavailable": [
    { "reason": "source_not_configured", "fields": ["modules"] }
  ]
}
reason Conduite attendue
source_not_configured La lecture en amont n'est pas branchée sur cet environnement. Réessayer donnera la même réponse — contactez-nous.
source_unreachable Elle a échoué en sortie. Réessayez plus tard.

unavailable est absent quand tout a été déterminé. Un null seul ne veut jamais dire « indéterminé » : "modules": [] signifie qu'aucun module n'est activé pour la société, et c'est une réponse complète.

Quand une liste est plus courte que votre mandat

Vos lectures répondent sur tout le périmètre, mais elles ne servent que les établissements qui détiennent le module dont elles dépendent. Les autres sont absents, et la réponse le dit — sinon une liste écourtée serait indistinguable d'une liste honnêtement vide, alors que les deux appellent la conduite inverse :

{
  "items": [],
  "nextCursor": null,
  "partial": { "reasons": ["module_not_subscribed"] }
}
Valeur de reasons Conduite attendue
module_not_subscribed Une partie de vos établissements ne détient pas le module dont cette lecture dépend. Ni un scope ni une offre ne les débloque : c'est la société cliente qui décide, et c'est à elle qu'il faut le demander.
unspecified La réponse est écourtée et l'API ne sait pas dire pourquoi. Traitez-la comme n'importe quelle autre raison d'aller regarder ce que votre mandat couvre. Rien à réessayer.

partial est absent quand la réponse couvre tout votre périmètre — une réponse complète est donc exactement ce qu'elle était avant que ce bloc existe.

Trois précisions qui évitent les contresens :

Types de congés

GET /api/v1/client/referentials/leave-types (scopes : referentials:read)

{
  "items": [
    {
      "id": 1,
      "code": "SAMPLE_LEAVE",
      "name": "Sample leave",
      "isCustom": false,
      "companyId": null
    },
    {
      "id": 2,
      "code": "SAMPLE_LEAVE",
      "name": "Sample leave (company)",
      "isCustom": true,
      "companyId": "1VaKPa"
    }
  ]
}

Référencez le type par son id, branchez sur son code — name est un libellé que la société peut surcharger : s'y fier casse le jour où quelqu'un le renomme dans Staff & Go.

Un code n'est pas unique dans la réponse. Une société peut définir son propre type par-dessus le type standard dont elle hérite, et les deux portent le même code — comme dans l'exemple ci-dessus. Indexer la réponse par code perd donc une entrée silencieusement ; c'est isCustom (true pour le type propre à la société) qui distingue les deux. Utilisez id s'il vous faut une clé unique.

companyId nomme l'établissement qui a défini le type, et vaut null sur un type standard — la clé est toujours là, comme name. Ce null ne signifie pas un établissement inconnu : il signifie que personne ne l'a défini, que tous les établissements de votre périmètre héritent de la même entrée, et qu'elle est renvoyée une fois. Quand deux établissements définissent chacun leur type sous un même code, c'est companyId qui les distingue.

Tout le périmètre revient en une seule réponse : cet endpoint n'offre encore ni filtre ni pagination.

Types de contrat

GET /api/v1/client/referentials/contract-types (scopes : referentials:read)

{
  "items": [
    {
      "id": 1,
      "name": "Sample contract type",
      "isActive": true
    },
    {
      "id": 2,
      "name": "Sample retired contract type",
      "isActive": false
    }
  ]
}
Champ Ce qu'il vous dit
id La seule ancre sûre de ce référentiel.
name Un libellé, à afficher. Ne branchez jamais dessus.
isActive false pour un type qu'on ne peut plus choisir sur un nouveau contrat.

Ce référentiel n'a pas de code fonctionnel. Contrairement aux types de congés, Staff & Go n'en stocke aucun pour un type de contrat : id est donc la seule clé sur laquelle s'appuyer, et name ne sert qu'à l'affichage.

Les types retirés sont renvoyés, avec isActive à false. Un contrat signé il y a des années peut en référencer un, et l'omettre vous laisserait avec un identifiant que vous ne sauriez pas résoudre. Ne prenez donc pas l'absence d'un type dans un sélecteur pour une absence de cette liste.

C'est un référentiel de la plateforme, pas de la société : la même liste répond à tous les partenaires. Elle revient entière, sans filtre ni pagination.

Statuts professionnels

GET /api/v1/client/referentials/professional-statuses (scopes : referentials:read)

{
  "items": [
    {
      "id": 1,
      "code": "SAMPLE_STATUS",
      "hiringCode": "SMP",
      "name": "Sample professional status",
      "isActive": true
    }
  ]
}
Champ Ce qu'il vous dit
id Identifiant technique.
code Le code fonctionnel stable — c'est lui qu'on branche. Ce référentiel en a un, contrairement aux types de contrat.
hiringCode La clé de rapprochement avec les conventions collectives. Voir la section suivante.
name Libellé, à afficher.
isActive false pour un statut retiré, renvoyé quand même.

Référentiel de la plateforme, comme les types de contrat, et renvoyé entier.

Conventions collectives

GET /api/v1/client/referentials/collective-agreements (scopes : referentials:read)

{
  "items": [
    {
      "id": 10,
      "companyId": "1VaKPa",
      "codeIdcc": "0000",
      "codeCcn": null,
      "codeJo": null,
      "name": "Sample collective agreement",
      "professionalStatusHiringCode": "SMP",
      "isActive": true,
      "isAgreementActive": true
    },
    {
      "id": 10,
      "companyId": "1VaKPa",
      "codeIdcc": "0000",
      "codeCcn": null,
      "codeJo": null,
      "name": "Sample collective agreement",
      "professionalStatusHiringCode": null,
      "isActive": true,
      "isAgreementActive": true
    },
    {
      "id": 10,
      "companyId": "DJABPM",
      "codeIdcc": "0000",
      "codeCcn": null,
      "codeJo": null,
      "name": "Sample collective agreement",
      "professionalStatusHiringCode": "SMP",
      "isActive": false,
      "isAgreementActive": true
    }
  ]
}

Contrairement aux deux précédents, celui-ci porte la donnée de la société : la réponse diffère d'un partenaire à l'autre.

Champ Ce qu'il vous dit
id Identifiant de la convention. Ce n'est pas une clé de la réponse — voir ci-dessous.
companyId L'établissement qui l'applique — l'identifiant opaque servi par /companies.
codeIdcc Le code IDCC, identifiant national de la convention.
codeCcn, codeJo Codes complémentaires, absents quand ils ne sont pas renseignés.
name Libellé de la convention.
professionalStatusHiringCode Le statut professionnel couvert par ce rattachement. null signifie qu'il les couvre tous.
isActive Cet établissement applique-t-il encore ce rattachement.
isAgreementActive La convention existe-t-elle encore au niveau national.

Une entrée est un rattachement, pas une convention. Une société peut appliquer des conventions différentes à des statuts professionnels différents : deux entrées peuvent donc partager le même id — comme dans l'exemple ci-dessus — et c'est le triplet (companyId, id, professionalStatusHiringCode) qui en identifie une. Indexer la réponse par id perd des entrées silencieusement — c'est le même piège que les codes de types de congés, sur une autre clé.

Si votre périmètre couvre plusieurs établissements, vous recevez une entrée par établissement. Deux d'entre eux appliquant la même convention au même statut professionnel, ce sont deux entrées, distinguées par companyId, chacune avec son propre isActive : un établissement peut avoir cessé d'appliquer une convention que l'autre applique encore. isActive parle de l'établissement nommé à côté, jamais de votre périmètre entier.

Le rapprochement avec les statuts professionnels se fait sur hiringCode, jamais sur id. Les deux référentiels vivent dans des tables reliées un-à-plusieurs : aucune ligne de professional-statuses ne correspond une-pour-une à un rattachement, et joindre par id renverrait une ligne — la mauvaise — sans rien signaler.

Les deux drapeaux répondent à deux questions indépendantes. Savoir que la société n'applique plus un rattachement ne dit rien de l'existence nationale de la convention, et réciproquement. Les rattachements inactifs sont renvoyés pour qu'un contrat plus ancien reste résolvable.

Regroupements de sociétés

GET /api/v1/client/referentials/company-groupings (scopes : referentials:read)

{
  "items": [
    {
      "id": "kNR1P3",
      "name": "Unite legale Sample",
      "code": "UL-SAMPLE",
      "type": "LegalUnit",
      "siren": "123456789",
      "companyIds": [
        "1VaKPa",
        "DJABPM"
      ]
    },
    {
      "id": "RP85Ng",
      "name": "Groupement Sample",
      "code": "GRP-SAMPLE",
      "type": "Custom",
      "siren": null,
      "companyIds": [
        "1VaKPa"
      ]
    }
  ]
}

La manière dont le client organise ses sociétés. Rend les regroupements dont votre périmètre fait partie, sans pagination.

Champ Ce qu'il vous dit
id Identifiant opaque du regroupement.
name, code Des libellés que le client peut modifier. Affichez-les ; ne branchez jamais dessus.
type LegalUnit (sociétés partageant un SIREN) ou Custom (regroupement défini par le client). C'est là-dessus qu'on branche.
siren Présent uniquement sur un LegalUnit, en clair — identifiant légal public.
companyIds Quels établissements de votre périmètre en font partie — les identifiants servis par /companies. Jamais vide.

Un regroupement nomme une liste d'établissements, pas un seul. Contrairement à toutes les autres lectures d'ici, il ne porte pas d'établissement d'origine : un regroupement est un ensemble auquel des sociétés appartiennent, pas une chose que l'une d'elles possède. Deux de vos établissements dans un même regroupement, c'est une entrée qui les nomme tous les deux — pas deux entrées.

Ce n'est pas un organigramme. Il ne porte ni ligne hiérarchique, ni responsable, ni salarié, et les regroupements sont plats — il n'y a ni parent ni niveau. Pour savoir qui reporte à qui, lisez managerResourceId sur le salarié (voir Sociétés, salariés et contrats).

Il ne dit jamais combien d'autres sociétés contient un regroupement. companyIds liste les membres que votre mandat couvre, et s'arrête là : un regroupement couvrant des sociétés que vous ne pouvez pas lire ressemble exactement à un regroupement qui n'en couvre pas. Ne lisez pas la taille de cette liste comme la taille du regroupement.

Se resynchroniser

Aucune de ces lectures ne propose de synchronisation incrémentale. Il n'existe pas de filtre modifiedSince, et il n'y en aura pas en v1 : rien, côté Staff & Go, ne porte de date de dernière modification indexée sur ces ressources. Un filtre accepté qui renverrait tout serait pire qu'un filtre absent, puisque vous croiriez avoir reçu un delta.

Deux conséquences pour votre intégration :

Pour les volumes qui ne tiendront pas dans une réponse — les salariés d'un grand compte, par exemple — des exports en masse sont prévus. Votre interlocuteur Staff & Go vous dira quand ils arrivent.