Vacation Tracker Docs
REST API v2

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

v1v2
Base pathhttps://api.vacationtracker.io/v1https://api.vacationtracker.io/v2
Authenticationx-api-key: <key> headerAuthorization: Bearer vt_live_... header
KeysCompany-wide, unscopedOwned 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" } }
Paginationlimit and nextTokenSame: limit and nextToken, with nextToken at the top level next to data
AccessRead onlyRead and write, plus webhooks
Single resourcesNot availableGET /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": {}
  }
}
FieldDescription
codeStable error code. Branch on this, not on the text.
nameShort name of the error
descriptionWhat went wrong
resolutionHow to fix it
requestIdInclude it when you contact support
detailsOptional 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.

v1v2ScopeDifferences
GET /v1/departmentsGET /v2/departmentsdepartments:readSame fields. GET /v2/departments/{id} also returns parentId and approvers.
GET /v1/locationsGET /v2/locationslocations:readSame fields. GET /v2/locations/{id} also returns the year start, first day of the week, quota reset and rollover expiry settings.
GET /v1/labelsGET /v2/labelslabels:readSame fields. userCount is always a number (0 when no user has the label).
GET /v1/leave-typesGET /v2/leave-typesleave-types:readAdds position and calendarStatus.
GET /v1/leavesGET /v2/leavesleaves:readAdds 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/usersGET /v2/usersusers: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 rolev1 keyv2 key
AdministratorThe whole workspaceThe whole workspace
Location adminThe whole workspaceOnly the locations they manage
ApproverThe whole workspaceOnly the departments they approve. The key cannot read departments, locations or labels.
UserThe whole workspaceOnly 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

  1. 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:read and holidays: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.

  2. Switch the base URL and header. Change https://api.vacationtracker.io/v1 to https://api.vacationtracker.io/v2, and replace the x-api-key: <key> header with Authorization: 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_..."
  3. Adapt response handling. Read data (and nextToken) without checking status: "ok". On errors, read error.code instead of message. Check the field differences in the endpoint mapping if you parse leave types, leaves or expanded users strictly.

  4. 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.

  5. 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.

On this page