Skip to content

Documentation

The API

One key per integration, with the scopes it needs and nothing else. Keys are made in Settings → API, shown once, and every call is logged with what it did.

Calling it

Send your key as Authorization: Bearer staffena_…. Everything is scoped to the company the key belongs to — there is no parameter for choosing a company, because a key that could would be a key worth stealing.

Writes take an Idempotency-Key header. Send the same key twice and the second call replays the first answer rather than doing it again, which is what makes a retry safe after a timeout.

People and their records

GET/api/v1/peoplescope people:read

Everybody on the books.

The directory another system matches its own records against. Deliberately not the whole row: pay, documents and anything about somebody’s private life are not a directory read.

Parameters

limit
How many rows, up to 200. e.g. 100
offset
Where to start, for the next page. e.g. 100
department
One department, by its id.
office
One office, by its id.
active
true for people still employed, false for leavers.
since
Only what changed after this moment — how a nightly sync avoids the whole company. e.g. 2026-09-01T00:00:00Z

What comes back

id
Ours. Stable for ever.
employeeId
The company’s own number for them.
fullName
As the company writes it.
email
Their work address, where there is one.
jobTitle
What they are called.
departmentId
Their department, by id.
officeId
Their office, by id.
employmentType
Full time, part time, and whatever else the company uses.
startedOn
Their first day.
endedOn
Their last, once there is one.
active
Whether they are still employed.
updatedAt
What `since` compares against.

Leave

GET/api/v1/leavescope leave:read

Leave that has been asked for and decided.

For a rota system that needs to know who is away, and for a data warehouse counting absence. Balances are not here: a balance is computed from a policy, and a number copied out of it goes stale the moment somebody books a day.

Parameters

limit
How many rows, up to 200. e.g. 100
offset
Where to start, for the next page. e.g. 100
person
One person, by their id.
status
pending, approved, rejected or cancelled.
from
Leave ending on or after this date. e.g. 2026-01-01
to
Leave starting on or before it. e.g. 2026-12-31

What comes back

id
The request.
staffId
Whose it is.
type
The leave type’s key, as the company named it.
startsOn
First day away.
endsOn
Last day away.
days
Working days, as this company counts them.
status
Where the request got to.

Attendance

GET/api/v1/attendancescope attendance:read

When people started and stopped.

Sessions rather than punches, because a session is the thing with a length. A door system writing punches in uses the device endpoint instead, which authenticates a machine rather than an integration.

Parameters

limit
How many rows, up to 200. e.g. 100
offset
Where to start, for the next page. e.g. 100
person
One person, by their id.
from
Sessions starting on or after this moment.
to
Sessions starting on or before it.

What comes back

id
The session.
staffId
Whose it is.
startedAt
When they started.
endedAt
When they stopped, or null while they are still in.
source
Where it came from: the browser, a terminal, a correction.

POST/api/v1/attendancescope attendance:write

Punches from a machine.

A batch of up to a thousand, which is how a terminal uploads a day at a time rather than making a request per person. Send the same batch twice with the same idempotency key and the second one changes nothing.

Parameters

punches[].cardRef
The card or employee number the machine read.
punches[].at
When it happened, as an instant. e.g. 2026-09-20T07:58:00Z
punches[].direction
in or out. Anything else is read as in.
Idempotency-Key
A header. The same key replays the first answer rather than writing twice.

What comes back

sessions
How many sessions the batch made.
duplicates
Punches already known, ignored rather than doubled.
held
Punches naming nobody, kept for somebody to resolve.

Pay

GET/api/v1/payscope pay:read

Published payslips.

Totals rather than lines, and published ones only: a draft is a figure somebody is still editing, and an accounting system that imported one would be reconciling against a number that changed.

Parameters

limit
How many rows, up to 200. e.g. 100
offset
Where to start, for the next page. e.g. 100
person
One person, by their id.
month
One month, as the payslip period. e.g. 2026-09

What comes back

id
The payslip.
staffId
Whose it is.
periodStart
The month it is for.
gross
Before deductions.
deductions
What came off.
net
What was paid.
currency
What it was paid in.

POST/api/v1/payscope pay:write

Amounts to pay or deduct.

Up to five hundred at a time, for a commission engine or an expenses system that decides money elsewhere. Anybody named who is not in this company is refused rather than skipped: writing into another company’s payroll is the one mistake this door cannot be allowed to make.

Parameters

transactions[].staffId
Who it is for, by their id in this company.
transactions[].label
What it is called on the payslip.
transactions[].kind
Whether it is paid or deducted.
transactions[].amount
In the company’s own currency.
transactions[].month
The month it belongs to. e.g. 2026-09
Idempotency-Key
A header. The same key replays the first answer rather than paying twice.

What comes back

written
How many were accepted.
refused
Which were not, and why.