Vacation TrackerDeveloper Docs
REST API v2API referenceUsers

Project a user's leave balance

This API method projects the balance of one leave type for a user over the current and the next leave year, and answers one of two questions. Requires balances:read.

Ask exactly one of:

  • date: the projected balance on that date. The answer is BALANCE, with the balance in days and hours.
  • amount together with unit: the earliest time off of that size the balance covers. The answer is WINDOW, with the start date, the end date and the working days the window costs. unit is working for working days, calendar for calendar days and weeks for weeks; a calendar or weeks window is a fixed span of which only the working days count, so the same two weeks cost less when they contain a public holiday or a non-working day.

Sending neither date nor amount, both of them, or amount without unit, fails with 400 VALIDATION_ERROR.

A user that does not exist, is outside the key's data reach, or whose projection the key owner may not see, returns the same 404 RESOURCE_NOT_FOUND. A user with nothing to project (a leave type without a limit, no leave year covering today, or no working hours) returns 422 REQUEST_REJECTED.

When no window within the projection horizon covers the amount, the answer is NOT_ANSWERABLE with the horizon end date instead of a window. The projection reaches the end of the next leave year, which is also the horizonEnd of the response.

series carries the projected balance on each accrual date in ascending order, so a date between two points has the balance of the earlier one, and the drop at the leave year boundary is the part of the balance the rollover cap does not carry over.

The key owner can ask about themselves, about anyone in the company when they are an admin, about users in the locations they manage as a location admin, and about users in the departments they approve for. Any other user fails with 403 FORBIDDEN.

Errors: 400 VALIDATION_ERROR (details.fields), 403 FORBIDDEN (the key owner may not see this user's projection), 404 RESOURCE_NOT_FOUND (the user is outside the key's data reach, or the leave type does not exist for the user) and 422 REQUEST_REJECTED (the leave type has no limit, so there is no balance to project).

GET
/v2/users/{id}/projection

Authorization

bearerAuth balances:read
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: balances:read

Path Parameters

id*string

resource ID.

Query Parameters

leaveTypeId*string

the ID of the leave type to project.

date?string

the date to read the projected balance on, in YYYY-MM-DD format. Send it instead of amount and unit.

Formatdate
amount?number

the size of the time off to find the earliest date for, greater than 0 and at most 1000. Requires unit, and is sent instead of date.

Range0 < value <= 1000
unit?string

the unit of amount. Only working days count in every unit; calendar and weeks fix the span of the window instead of its cost.

Value in"working" | "calendar" | "weeks"

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v2/users/string/projection?leaveTypeId=leaveType-fcc1e5c5-ba93-479d-bd58-8f860ba10615"

{
  "data": {
    "userId": "user-63b17e97-d38b-4586-8498-bb4f5d2e2c14",
    "leaveTypeId": "leaveType-fcc1e5c5-ba93-479d-bd58-8f860ba10615",
    "question": {
      "type": "DATE",
      "date": "2027-04-15"
    },
    "answer": {
      "type": "BALANCE",
      "date": "2027-04-15",
      "days": 6.67,
      "hours": 53.36
    },
    "series": [
      {
        "date": "2026-11-01",
        "days": 1.67,
        "hours": 13.36
      },
      {
        "date": "2026-12-01",
        "days": 3.34,
        "hours": 26.72
      },
      {
        "date": "2027-01-01",
        "days": 5,
        "hours": 40
      },
      {
        "date": "2027-02-01",
        "days": 1.67,
        "hours": 13.36
      },
      {
        "date": "2027-03-01",
        "days": 3.34,
        "hours": 26.72
      },
      {
        "date": "2027-04-01",
        "days": 5,
        "hours": 40
      }
    ],
    "horizonEnd": "2027-12-31"
  }
}

{
  "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": "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"
  }
}