Customer API
The customer API gives your server read-only access to one employer’s establishments, public holidays, employees, agency workers, teams, tasks, published shifts and time entries. It uses OAuth 2.0 client credentials over HTTPS. The production origin is https://workforce-api.vesta.services.
Activate API access
Section titled “Activate API access”Vesta activates API access per employer. An employer administrator opens Settings > Integrations > API access in the employer portal. While access is not active, that tab shows API access is not active and a Request API access button that emails Vesta.
Create an integration
Section titled “Create an integration”- Sign in to the employer portal as an employer administrator.
- Open Settings > Integrations > API access and select Create integration.
- Enter a name and select only the permissions the software needs.
- Copy the client ID and client secret into your server’s secret store, then select I have stored the secret. The secret is shown only once.
Also copy the Employer ID from the same page: every data URL contains it.
- An integration belongs to the employer and reads only that employer’s data.
- Permissions are fixed when the integration is created. To change them, create a new integration and revoke the old one.
- An employer can have up to 25 active integrations.
- Keep the client secret and access tokens on your server. Never put them in browser code, mobile apps, URLs or logs.
Request an access token
Section titled “Request an access token”Send POST /oauth/customer/token with a form-encoded body. Request one or more scopes, separated by spaces. Authenticate with HTTP Basic:
curl --request POST "https://workforce-api.vesta.services/oauth/customer/token" \
--user "$VESTA_CLIENT_ID:$VESTA_CLIENT_SECRET" \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=establishments:read planning:read'
Or send the credentials in the body (client_secret_post):
curl --request POST "https://workforce-api.vesta.services/oauth/customer/token" \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode "client_id=$VESTA_CLIENT_ID" \
--data-urlencode "client_secret=$VESTA_CLIENT_SECRET" \
--data-urlencode 'scope=establishments:read planning:read'
{ "access_token": "eyJhbGciOi...", "token_type": "Bearer", "expires_in": 300 }
A token is valid for 300 seconds. There is no refresh token: reuse the token for every call until shortly before it expires, then request a new one with the same credentials.
A refused token request returns an OAuth error body such as { "error": "invalid_scope" }:
| Error | Status | When |
|---|---|---|
invalid_request |
400 | A required parameter is missing or malformed, or credentials are sent both in the header and in the body. |
invalid_client |
401 | Unknown client ID, wrong secret, or an integration that was revoked or whose secret was rotated. |
unauthorized_client |
400 | The credentials are valid, but API access is no longer active for the employer. |
invalid_scope |
400 | No scope, an unknown scope, a scope the integration was not given, or a scope whose module the employer no longer has. |
unsupported_grant_type |
400 | grant_type is not client_credentials. |
Do not retry invalid_client or unauthorized_client automatically: they need a change in the portal.
Scopes
Section titled “Scopes”Every scope requires active API access. Some also require a product module.
| Scope | Portal permission | Endpoints | Also requires |
|---|---|---|---|
establishments:read |
Read establishments | /establishments, /public-holidays |
|
employees:read |
Read employees | /employees |
|
agency-workers:read |
Read agency workers | /agency-workers |
|
planning:read |
Read planning | /teams, /tasks, /shifts |
Planning module |
time-entries:read |
Read time entries | /time-entries |
Time registration module |
Endpoints
Section titled “Endpoints”All endpoints are GET requests under https://workforce-api.vesta.services/api/customer/v1/employers/{employerId} and need the header Authorization: Bearer <access_token>.
| Endpoint | Returns | Query |
|---|---|---|
/establishments |
Establishments with their IANA time zone | limit, cursor |
/public-holidays |
Public holidays of one year and the date the employer observes each, after any replacement | year (2000 to 2100, required) |
/employees |
Employees: name and active flag only | limit, cursor |
/agency-workers |
Workers of the agencies the employer works with | limit, cursor |
/teams |
Teams | limit, cursor |
/tasks |
Planning tasks | limit, cursor |
/shifts |
Published shifts | from, to, limit, cursor |
/time-entries |
Time entries | from, to, limit, cursor |
curl "https://workforce-api.vesta.services/api/customer/v1/employers/$VESTA_EMPLOYER_ID/shifts?from=2026-10-01&to=2026-10-31" \
--header "Authorization: Bearer $VESTA_ACCESS_TOKEN"
A token only works on its own employer’s URLs. The API reference lists every field.
Dates and paging
Section titled “Dates and paging”fromandtoare requiredYYYY-MM-DDdates between 2000 and 2100. Both are included. A range covers at most 93 days.- Shifts are selected by their planned date. Time entries are selected by the local day they started at their own establishment, the day
workDatereports. limitis 1 to 200, default 100. A list returns{ "items": [...], "nextCursor": "..." }. SendnextCursorascursor, with the same other parameters, untilnextCursorisnull. A cursor is opaque and only works on the list, employer and date range that returned it.- Paging is not a snapshot. A record that changes while you page can be missed or returned twice; the next full read corrects it.
Keep data in sync
Section titled “Keep data in sync”There is no change feed and no modifiedSince filter. To stay in sync:
- Re-read the date window you keep, for example the past two weeks and the coming four weeks of shifts, and page until
nextCursorisnull. - Upsert every record by
id. - Only after the last page, treat any stored
idin that window that was not returned as deleted. A shift that disappears was deleted or unpublished.
Reference lists are small: re-read them in full the same way. Archived employees drop out of /employees; use isActive for everything else.
Data semantics
Section titled “Data semantics”- Every
idis a UUID that stays the same across reads, including time entry ids. startsAt,endsAtand break times are local times with the establishment’s UTC offset, for example2026-10-03T22:00:00+02:00. Fields ending inUtcare UTC.- Shifts are published shifts only.
dateis the planned day. A shift that runs past midnight ends on the next day.plannedMinutesis the time that actually passes betweenstartsAtandendsAt, minusbreakMinutes: a night shift across a daylight saving change counts one hour more or less than its clock times suggest.fillTypeisInternalfor an employee orExternalfor an agency or external worker. - Time entry
statusisWorking,OnBreak,CompletedorCancelled.endsAtis empty while an entry is open, andworkedMinutesis present only when it isCompleted.shiftIdlinks the published shift the entry was worked on, when there is one.reviewedAtUtcis set once the employer has reviewed the entry. Cancelled entries are kept for history and do not count as worked time. - Shifts and time entries at a deleted establishment are left out. A reference to a deleted team, task, agency or agency worker is
null, just as that record is gone from its own list. An archived employee is the exception: shifts and time entries keep theiremployeeId. - Employees contain
firstName,lastNameandisActiveonly: no national number, bank account, contact details or birth date.
Rate limits
Section titled “Rate limits”| Requests | Limit | Counted per |
|---|---|---|
| Token requests | 30 per 60 seconds | IP address and client ID. With client_secret_post, all token requests from one IP address share one budget. |
| Data requests | 600 per 60 seconds | Employer, shared by all its integrations |
Vesta’s general limit of 500 requests per minute per IP address also applies. Over a limit, the API answers 429 with a Retry-After header in seconds: wait at least that long. Reusing tokens keeps you well within the token budget.
Errors
Section titled “Errors”Data endpoints answer errors as problem details (application/problem+json).
| Status | Meaning | What to do |
|---|---|---|
400 |
A parameter is missing or malformed, such as from, to, year or limit. |
Fix the request. |
401 |
The token is missing, invalid or expired, its integration was revoked or rotated, the employer lost API access, or none of the token’s scopes is still available. | Request a new token once. If that is refused, stop. |
403 |
The token is for another employer, lacks the scope, or the employer lost the module that scope reads. | Check the employer ID and scopes. |
422 |
to is before from, a date is outside 2000 to 2100, the range exceeds 93 days, or the cursor is invalid or belongs to another list, employer or date range. |
Fix the request. |
429 |
A rate limit was reached. | Wait for Retry-After. |
5xx |
Vesta could not answer. | Retry with increasing, jittered delays. |
Rotate or revoke
Section titled “Rotate or revoke”On API access, each integration’s menu offers Rotate secret and Revoke access. Last used shows when the integration last received a token.
- Rotate secret shows a new secret once. The old secret and every token issued with it stop working immediately, so update your server straight away.
- Revoke access permanently disables the integration and all its tokens. You can revoke even after API access has ended.
- Support sessions, in which Vesta staff act in your portal, cannot create integrations or rotate secrets. They can revoke.