Vacation Tracker Docs
REST API v2API referenceLeaves

Create a leave

This API method creates a leave. Requires `leaves:write`. It waits for the leave to be created and returns it with `201`; if that takes longer than the wait window it returns `202 Accepted` with an operation to poll. - Omit `userId` (or set it to the acting user's ID) to request a leave for the acting user. The leave is pending approval (`OPEN`) unless the leave policy needs no approval, in which case it is returned `APPROVED`. Every rule of the user's leave policy applies, as in the Vacation Tracker dashboard. - Set `userId` to another user's ID to add an already approved leave for a user the acting user manages. This also requires the `leaves:approve` scope, and the user must be within the key's data reach. Only overlapping leaves, dates without working days and part-day leaves longer than a working day block the leave; the other policy rules are not applied. - The acting user is the key owner, or the user in the `X-On-Behalf-Of` header, for example to request a pending leave in an employee's name. **Automations.** If the leave breaks an automation (a blackout period, maximum users away, a probation period, a leave duration limit or a notice period), the request fails with `409 AUTOMATION_OVERRIDE_REQUIRED` and `details.warnings` lists each violation. When adding a leave for another user, repeat the request with every listed `code` in `overrideAutomations` to add the leave anyway. Overrides are not applied to the acting user's own leave requests, so such a leave is rejected with the matching `422` code (`LEAVE_BLACKOUT_PERIOD`, `LEAVE_MAXIMUM_USERS_AWAY`, `LEAVE_PROBATION_PERIOD`, `LEAVE_DURATION_LIMIT` or `LEAVE_NOTICE_PERIOD`). Errors: `400 VALIDATION_ERROR` (`details.fields`), `403 INSUFFICIENT_SCOPE` (`leaves:approve` missing when adding for another user), `403 ON_BEHALF_OF_NOT_ALLOWED`, `403 FORBIDDEN`, `404 RESOURCE_NOT_FOUND` (the user is outside the key's data reach or does not exist), `409 AUTOMATION_OVERRIDE_REQUIRED`, `422 LEAVE_OVERLAP`, `422 LEAVE_NO_WORKING_DAYS`, `422 LEAVE_INVALID_DATE_RANGE`, `422 LEAVE_INVALID_DURATION`, `422 LEAVE_INVALID_HALF_DAY_DURATION`, `422 LEAVE_START_DATE_IN_PAST`, `422 LEAVE_NEGATIVE_BALANCE`, `422 LEAVE_REASON_REQUIRED`, `422 LEAVE_TYPE_INACTIVE`, `422 LEAVE_POLICY_MISSING`, `422 USER_NOT_EMPLOYED_ON_DATES`, the automation codes above, and the `Idempotency-Key` codes.

POST
/v2/leaves

Authorization

bearerAuth leaves:write
AuthorizationBearer <token>

API key sent as Authorization: Bearer vt_live_<secret>. See the Authentication and Scopes sections at the top of this document for how keys are created, which scopes exist and how the key owner's role limits them.

In: header

Scope: leaves:write

Header Parameters

Idempotency-Key?string

optional key (1 to 255 printable ASCII characters) that makes a write request safe to retry. Keys are retained for 24 hours per API key. Retrying with the same key and the same request within those 24 hours replays the original response (with the Idempotent-Replayed header). Reusing the key with a different request returns 422 IDEMPOTENCY_KEY_REUSED; retrying while the first request is still running returns 409 IDEMPOTENCY_REQUEST_IN_PROGRESS; an invalid key returns 400 INVALID_IDEMPOTENCY_KEY.

Length1 <= length <= 255
X-On-Behalf-Of?string

ID of the user the write is performed for. Requires the users:act-on-behalf scope, and the key owner must manage the target user; otherwise the request fails with 403 ON_BEHALF_OF_NOT_ALLOWED. Ignored on GET requests.

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v2/leaves" \  -H "Content-Type: application/json" \  -d '{    "leaveTypeId": "leaveType-fcc1e5c5-ba93-479d-bd58-8f860ba10615",    "startDate": "2026-11-09",    "endDate": "2026-11-13",    "reason": "Family trip"  }'
{
  "data": {
    "id": "leaveRequest-cfe5d39b-f1f0-45aa-85cd-ce93e637a262",
    "userId": "user-63b17e97-d38b-4586-8498-bb4f5d2e2c14",
    "approverId": "user-0f3a9c1e-6c2b-4a5d-9e7f-1b2c3d4e5f60",
    "autoApproved": false,
    "durationCalendarDays": 5,
    "durationWorkingDays": 5,
    "startDate": "2026-11-09",
    "endDate": "2026-11-13",
    "isFullDayLeave": true,
    "leaveTypeId": "leaveType-fcc1e5c5-ba93-479d-bd58-8f860ba10615",
    "locationId": "location-79c77ac8-e5bf-479f-9488-648a58379ff1",
    "departmentId": "team-0a21c25e-6925-4a5b-90ee-ecf82096c4f6",
    "status": "APPROVED",
    "reason": "Family trip",
    "statusReason": null,
    "approvedLevel": null,
    "createdAt": "2026-10-01T09:12:40.062Z"
  }
}
{
  "operation": {
    "id": "pa_3f1c2b9e-8a47-4d5e-9c61-2b7f0e4a1d93",
    "status": "PENDING",
    "url": "/v2/operations/pa_3f1c2b9e-8a47-4d5e-9c61-2b7f0e4a1d93"
  }
}
{
  "error": {
    "code": "VALIDATION_ERROR",
    "name": "ValidationError",
    "description": "A query parameter, body field or header failed validation.",
    "resolution": "Fix the fields listed in details.fields and send the request again.",
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
    "details": {
      "fields": [
        {
          "path": "startDate",
          "message": "\"startDate\" is required"
        }
      ]
    }
  }
}
{
  "error": {
    "code": "UNAUTHORIZED",
    "name": "Unauthorized",
    "description": "The API key is missing, malformed or not recognized.",
    "resolution": "Send a valid API key in the Authorization header as Bearer followed by the full vt_live_ key.",
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef"
  }
}

{
  "error": {
    "code": "INSUFFICIENT_SCOPE",
    "name": "InsufficientScope",
    "description": "The API key does not have the scopes this request needs.",
    "resolution": "Add the scopes listed in details.requiredScopes to the key in the Vacation Tracker dashboard.",
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef",
    "details": {
      "requiredScopes": [
        "leaves:write"
      ]
    }
  }
}

{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "name": "ResourceNotFound",
    "description": "The requested resource does not exist or is outside the data this key can access.",
    "resolution": "Check the ID of the resource named in details.resource and that the key owner can see it.",
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef"
  }
}

{
  "error": {
    "code": "CONFLICT",
    "name": "Conflict",
    "description": "The request conflicts with the current state of the resource.",
    "resolution": "Read the resource again and retry with up-to-date values.",
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef"
  }
}

{
  "error": {
    "code": "LEAVE_NEGATIVE_BALANCE",
    "name": "LeaveNegativeBalance",
    "description": "The user does not have enough balance for this leave.",
    "resolution": "Shorten the leave, use a different leave type, or ask an admin to adjust the quota.",
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef"
  }
}

{
  "error": {
    "code": "RATE_LIMITED",
    "name": "RateLimited",
    "description": "Too many requests were sent in a short time.",
    "resolution": "Wait a few seconds and retry, using the same Idempotency-Key for writes.",
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef"
  }
}
{
  "error": {
    "code": "INTERNAL_ERROR",
    "name": "InternalError",
    "description": "Something went wrong on our side.",
    "resolution": "Retry later, and if it keeps happening, contact Vacation Tracker support and quote the requestId.",
    "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef"
  }
}

List leaves between two dates

This API method returns a paginated list of leaves that overlap the period between `startDate` and `endDate`, for users within the key's data reach. Requires `leaves:read`. Each leave has the same shape as in API v1. - `status` is matched case-insensitively and defaults to `APPROVED`; one status per request. - The key's data reach is applied after each page is read, so a page can hold fewer than `limit` leaves (even none) while `nextToken` is still returned. Keep paginating until `nextToken` is absent. - A key whose owner sees only their own data gets all of their own leaves in one page, without `nextToken`. - With `expand=leaveType`, a key without `leave-types:read-hidden` gets `Hidden Leave Type` as `leaveType.name` for leaves whose leave policy hides the leave type. `leaveTypeId` is always returned. Errors: `400 VALIDATION_ERROR` (a missing or invalid `startDate`, `endDate`, `status`, `limit` or `expand`, or `endDate` before `startDate`; `details.fields` lists them) and `400 INVALID_PAGINATION_TOKEN`.

Get a leave

This API method returns a single leave. Requires `leaves:read`. With `expand`, an embedded `user` or `approver` outside the key's data reach is left out, and only its ID is returned. With `expand=leaveType`, a key without `leave-types:read-hidden` gets `Hidden Leave Type` as `leaveType.name` when the leave's policy hides the leave type; `leaveTypeId` is always returned. Errors: `400 VALIDATION_ERROR` (an unknown `expand` value) and `404 RESOURCE_NOT_FOUND` (the leave does not exist, or belongs to a user outside the key's data reach).