Companies, employees and contracts
The client's establishments, the people employed there, and the contracts that bind them. These reads carry personal data: every field is classified in the API reference, and some are withheld unless you were granted a scope of their own.
Read Discovery first — the referentials it describes are what the identifiers below resolve against.
Companies
GET /api/v1/client/companies (scopes: companies:read)
GET /api/v1/client/companies/{id} (same scopes)
{
"items": [
{
"id": "lPBpV3",
"name": "Atelier Central",
"code": "AC-01",
"siret": "12345678900012",
"siren": "123456789",
"isHeadquarter": true,
"isManagingEstablishment": false,
"businessSign": "Atelier",
"address": {
"street": "12 rue des Fabriques",
"postalCode": "69001",
"city": "Lyon",
"country": "France"
},
"phoneNumber": "0400000000",
"legalCategoryLabel": "SAS",
"activityCode": "2562B",
"activityLabel": "Mecanique industrielle",
"collectiveAgreementName": "Metallurgie",
"collectiveAgreementIdcc": "3248",
"weeklyWorkingHours": 35,
"masterCompanyId": "dV3QPr",
"masterName": "Groupe Central"
}
],
"nextCursor": null
}
| Field | What it tells you |
|---|---|
id |
Opaque identifier. Every other read takes it as companyId. |
siret, siren |
Public legal identifiers, in clear — they are not opaque, and you may match on them. |
isHeadquarter |
true for the registered office. |
collectiveAgreementIdcc |
The IDCC code, which joins the collective-agreement referential. |
masterCompanyId |
The parent establishment, when there is one. |
The list returns one entry per establishment of your perimeter. A credential mandated on a group pages through every establishment it reaches; one mandated on a single company still holds exactly one entry. The shape of the answer is the same either way, so page through it rather than reading the first element.
Employees
GET /api/v1/client/employees (scopes: employees:read)
GET /api/v1/client/employees/{id} (same scopes)
{
"items": [
{
"id": "1VaKPa",
"fullName": "Camille Dupont",
"civility": "Mme",
"employeeNumber": "E-0042",
"professionalEmail": null,
"companyId": "dV3QPr",
"companyName": "Atelier Central",
"isActive": true,
"jobFunction": "Chef d\u0027atelier",
"managerResourceId": "GNnBNO",
"managerFullName": "Renaud Lambert"
},
{
"id": "DJABPM",
"fullName": "Camille Dupont",
"civility": "Mme",
"employeeNumber": "E-0042",
"professionalEmail": null,
"companyId": "dV3QPr",
"companyName": "Atelier Central",
"isActive": true,
"jobFunction": "Chef d\u0027atelier",
"managerResourceId": "GNnBNO",
"managerFullName": "Renaud Lambert"
}
],
"nextCursor": null
}
| Field | What it tells you |
|---|---|
id |
Opaque identifier — the {id} the contract and leave reads take. |
employeeNumber |
The staff number as the employer set it. Not guaranteed unique across companies. |
isActive |
false for someone who has left. They are still returned. |
managerResourceId |
Their manager, when that manager's record sits in the same company. |
managerFullName |
The manager's name, on the same condition. |
A manager in another company comes back as null, not as an unresolvable id. Groups where people report
across establishments will therefore see gaps in the reporting line. That is deliberate: naming someone
outside the perimeter you were granted would disclose them.
Confidential and archived records are never returned, by any read. An employee marked confidential in
Staff & Go is absent from this list and answers 404 by id — the same answer an identifier that does not
exist gets, so you cannot tell the two apart. That is the point.
Employment contracts
GET /api/v1/client/employees/{id}/employments (scopes: employments:read)
GET /api/v1/client/employments/{id} (same scopes)
{
"id": "lPBpV3",
"employeeId": "GNnBNO",
"companyId": "dV3QPr",
"startDate": "2020-01-06",
"endDate": null,
"contractType": "CDI",
"jobTitle": "Chef d atelier",
"hiringClass": "CADRE",
"weeklyWorkingHours": 35,
"managerResourceId": "XJ2rJL",
"grossSalary": null,
"netSalary": null,
"salaryPeriod": null,
"restricted": [
{
"reason": "requires_scope",
"fields": [
"grossSalary",
"netSalary",
"salaryPeriod"
]
},
{
"reason": "requires_company_opt_in",
"fields": [
"grossSalary",
"netSalary",
"salaryPeriod"
]
}
]
}
| Field | What it tells you |
|---|---|
id |
The contract. |
employeeId |
Whose it is — the identifier the employee reads take. |
contractType |
Spelt out ("CDI"), in your language — send Accept-Language. |
jobTitle |
The job, free text as the employer wrote it. |
hiringClass |
The professional status as recorded. See the warning below. |
weeklyWorkingHours |
Contractual weekly hours. |
grossSalary, netSalary, salaryPeriod |
Pay. Behind a scope of its own — see below. |
hiringClass is free text with no referential behind it. It is written by a payroll connector's sync
field and by a document template, with no foreign key and nothing in Staff & Go joining it to the
professional-status referential. Do not join it on hiringCode, whatever the two happen to look like:
they coincide only when the connector uses the same vocabulary, and nothing enforces that.
Expect several contracts where you might expect one. An employee accumulates amendments over a career, and each is a row here. The listing is paged; page it to the end rather than reading the first entry.
An employee id that is not one of yours answers 404, not an empty page. It designates the subject of
the read, and a subject that does not resolve has no list to be empty. That differs from a filter, which
narrows a set and can legitimately narrow it to nothing.
Withheld fields
Some fields are returned only to a partner that was granted a scope for them. Pay is the first:
employments:read:compensation, required in addition to employments:read, which never implies it.
When a field is withheld it comes back null and is named in restricted:
"grossSalary": null,
"restricted": [
{ "reason": "requires_scope", "fields": ["grossSalary", "netSalary", "salaryPeriod"] },
{ "reason": "requires_company_opt_in", "fields": ["grossSalary", "netSalary", "salaryPeriod"] }
]
A null on its own never means "withheld". A field that is null and absent from restricted really
has no value recorded. The two are different facts and this contract keeps them apart — do not report one as
the other to your users.
| Reason | What unblocks it |
|---|---|
requires_scope |
Ask the client's administrator to grant the sub-scope on your credential. |
requires_company_opt_in |
The company itself has not opened the field. Only it can. |
requires_plan |
Your partner plan does not cover the field. |
Several reasons can appear at once, and they all matter. They are settled by different parties — the administrator who provisioned your credential, the company being read, and your own plan — so clearing one leaves the others standing. A partner told only about the "first" obstacle would obtain it and come back to the very same withheld field.
restricted travels inside each entry, including in a page, never once above the list. On a listing
spanning several companies, some will have opened a field and others not, so no single envelope could
describe a page truthfully. When nothing was withheld the array is empty — one shape, always.
Three reads carry this envelope: employment contracts, leave lines and variable payroll elements. They are
the only ones with a field that can be withheld, and each has a sub-scope of its own —
employments:read:compensation, leaves:read:reason, payroll:read:amounts — as well as its own company
opt-in. None of the three implies another: contractual pay and what a payroll month recorded are two separate
decisions, taken separately by each company. The others — companies, employees, balances, referentials,
expense reports, meal voucher counts, payslips — have
no restricted key at all, and that absence says there is nothing here to withhold, never that nothing was. A
null on those reads is therefore always a value that is not recorded: the manager in another company
described above is one of those, not a withheld field.