Migrating from v1
Move an integration from REST API v1 to v2, with the differences in authentication, responses, errors and endpoints.
REST API v1 keeps working as it does today. Nothing changes for existing v1 integrations, and you can move to v2 at your own pace.
- v1 is sealed. It stays read-only and gets no new endpoints or fields. New features, write access and webhooks are v2 only.
- No new v1 keys. Existing v1 keys keep working and are listed as legacy keys on the Open API add-on page, where you can delete them. The dashboard no longer creates v1 keys.
- v1 keys don't work on v2. v2 needs a v2 key (
vt_live_...), created on the same Open API add-on page.
What changes
| v1 | v2 | |
|---|---|---|
| Base path | https://api.vacationtracker.io/v1 | https://api.vacationtracker.io/v2 |
| Authentication | x-api-key: <key> header | Authorization: Bearer vt_live_... header |
| Keys | Company-wide, unscoped | Owned by a user, with scopes (for example leaves:read) that never exceed the owner's role. A key sees only the data its owner can see. |
| Success envelope | { "status": "ok", "data": ... } | { "data": ... } |
| Error envelope | { "status": "error", "message": "..." } or { "message": "..." } | { "error": { "code", "name", "description", "resolution", "requestId", "details" } } |
| Pagination | limit and nextToken | Same: limit and nextToken, with nextToken at the top level next to data |
| Access | Read only | Read and write, plus webhooks |
| Single resources | Not available | GET /v2/<resource>/{id} |
Success responses
v1:
{
"status": "ok",
"data": [{ "id": "...", "name": "Engineering", "isDefault": false, "createdAt": "..." }]
}v2:
{
"data": [{ "id": "...", "name": "Engineering", "isDefault": false, "createdAt": "..." }]
}Errors
Every v2 error, including authentication failures and unknown paths, uses one envelope:
{
"error": {
"code": "...",
"name": "...",
"description": "What went wrong.",
"resolution": "What to do about it.",
"requestId": "...",
"details": {}
}
}| Field | Description |
|---|---|
code | Stable error code. Branch on this, not on the text. |
name | Short name of the error |
description | What went wrong |
resolution | How to fix it |
requestId | Include it when you contact support |
details | Optional extra data, for example the invalid fields |
As in v1, the HTTP status code is the source of truth for success or failure.
Endpoint mapping
All six v1 endpoints have a v2 equivalent that accepts the same query parameters and returns the same fields. The differences are listed below.
| v1 | v2 | Scope | Differences |
|---|---|---|---|
GET /v1/departments | GET /v2/departments | departments:read | Same fields. GET /v2/departments/{id} also returns parentId and approvers. |
GET /v1/locations | GET /v2/locations | locations:read | Same fields. GET /v2/locations/{id} also returns the year start, first day of the week, quota reset and rollover expiry settings. |
GET /v1/labels | GET /v2/labels | labels:read | Same fields. userCount is always a number (0 when no user has the label). |
GET /v1/leave-types | GET /v2/leave-types | leave-types:read | Adds position and calendarStatus. |
GET /v1/leaves | GET /v2/leaves | leaves:read | Adds reason, statusReason and approvedLevel, and fills in createdAt, which v1 leaves empty. With expand, the embedded user and approver use the v2 user shape (departmentId instead of teamId, no role). |
GET /v1/users | GET /v2/users | users:read (plus holidays:read to expand holidays) | Same fields. employeeId, startDate and endDate are null when not set, as in v1. |
v2 validates query parameters that v1 passed through, and answers 400 VALIDATION_ERROR with the invalid fields in details. limit must be an integer between 1 and 1000, and startDate and endDate on GET /v2/leaves must be YYYY-MM-DD dates, without a time.
A v1 key belongs to the workspace, so it always sees everything. A v2 key belongs to the person who created it, and its results are limited to that person's data reach:
| Key owner role | v1 key | v2 key |
|---|---|---|
| Administrator | The whole workspace | The whole workspace |
| Location admin | The whole workspace | Only the locations they manage |
| Approver | The whole workspace | Only the departments they approve. The key cannot read departments, locations or labels. |
| User | The whole workspace | Only themselves. The key cannot read departments, locations or labels. |
To keep what a v1 integration sees, create the v2 key as an administrator. A v2 key can see less than its owner sees in Vacation Tracker, and the Visibility add-on does not change it; see Role ceiling and data reach.
Migration checklist
-
Create a v2 key. On the Open API add-on page, create a key with the scopes your v1 integration uses:
departments:read,locations:read,labels:read,leave-types:read,leaves:read,users:readandholidays:read. These match what a v1 key can read. Create it as an administrator if the integration needs the whole workspace. Copy the secret: it is shown only once. -
Switch the base URL and header. Change
https://api.vacationtracker.io/v1tohttps://api.vacationtracker.io/v2, and replace thex-api-key: <key>header withAuthorization: Bearer vt_live_....# v1 curl https://api.vacationtracker.io/v1/departments \ -H "x-api-key: your-api-key" # v2 curl https://api.vacationtracker.io/v2/departments \ -H "Authorization: Bearer vt_live_..." -
Adapt response handling. Read
data(andnextToken) without checkingstatus: "ok". On errors, readerror.codeinstead ofmessage. Check the field differences in the endpoint mapping if you parse leave types, leaves or expanded users strictly. -
Test. Run the integration against v2 and compare the results with v1. If some data is missing, check the key's scopes and the owner's data reach.
-
Delete the v1 key. When the integration runs on v2, delete the legacy v1 key on the Open API add-on page. A deleted v1 key stops working and can't be recreated.
Webhook events
Event types that webhooks can subscribe to.
List departments
This API method returns every department within the key's data reach, in one response without pagination. Requires `departments:read`. Admins see every department; other users see the departments they manage. Each department has only its ID, name, default flag and creation time; the parent and approvers are returned by create and update.