TimeOff Manager API Documentation

Integrate powerful leave management capabilities into your HR systems, payroll software, and custom applications with our comprehensive RESTful API.

Version 1.1 RESTful API JSON Responses

Getting Started

This page documents the company integration API (X-API-Key). Use it from payroll, accounting, and HR tools to read and write staff, leave, schedules, time punches, payroll hours, mileage, and work locations. Native invoicing (estimates, expenses, Stripe) is the web product — it is not duplicated here.

Employee Management

Create, update, and manage employee records

Leave Requests

Submit, approve, and track time-off requests

Time & payroll

Import punches, payroll hours, and day adjustments

Webhooks

HTTPS callbacks for leave, punches, mileage, and adjustments

Base URL

https://timeoffpay.us/api/v1

Authentication

All API requests must include your API key in the request header:

X-API-Key: tk_your_api_key_here

Note: Create keys in the admin dashboard. Integrations can still read a JSON catalog from GET /api/v1 when the request asks for JSON. This page is the human reference.

Permissions

If a key has no permissions list, it can call every company endpoint. If you check specific boxes when creating the key, each route requires one of:

Permission Used for
employees:read GET employees, schedules, time entries, payroll hours, forgot-punch list, mileage, locations, company, holidays, leave summary
employees:write Create/update employees, schedules, one-day hours, import punches, forgot-punch approve/reject, day adjustments, mileage write, work locations, webhooks
leave:read GET leave requests
leave:write Create, update, approve, or reject leave requests
billing:read / billing:write Thin invoice/client slice only, and only if invoicing is enabled for the company
{
  "error": "Forbidden",
  "message": "This API key does not have the required permission: employees:read"
}

Other APIs (not this page)

  • Mobile apps use Sanctum bearer tokens (/api/v1/auth/login) — not documented here.
  • Blog / social automation uses a site-wide key, not company X-API-Key values.

Quick Start Example

cURL Example:

curl -X GET "https://timeoffpay.us/api/v1/employees" \
  -H "X-API-Key: tk_your_api_key_here" \
  -H "Content-Type: application/json"

JavaScript Example:

fetch('https://timeoffpay.us/api/v1/employees', {
  headers: {
    'X-API-Key': 'tk_your_api_key_here',
    'Content-Type': 'application/json'
  }
})
.then(response => response.json())
.then(data => console.log(data));

API Endpoints

Employee Management

GET /employees
List company staff

Retrieve a paginated list of people in your company (employees, managers, and company admins). Platform operator accounts are excluded. Pass role to filter.

Query Parameters:

Parameter Type Description
department string Filter by department
role string Filter by role (e.g. employee, company_admin)
is_active boolean Filter by active status
search string Search by name, email, or employee number
per_page integer Results per page (default: 20)

Example Response:

{
  "data": [
    {
      "id": 123,
      "employee_number": "EMP-001",
      "first_name": "John",
      "last_name": "Doe",
      "name": "John Doe",
      "email": "[email protected]",
      "role": "employee",
      "company_id": 1,
      "title": "Senior Developer",
      "department": "Engineering",
      "manager_id": 45,
      "hire_date": "2022-01-15",
      "employment_type": "full-time",
      "work_phone": "+1-555-0123",
      "mobile_phone": "+1-555-0124",
      "work_locations": [
        {
          "address": "123 Main St, New York, NY 10001",
          "is_primary": true
        }
      ],
      "emergency_contact": {
        "name": "Jane Doe",
        "phone": "+1-555-0125",
        "relationship": "Spouse"
      },
      "payroll_id": "PAY-001",
      "country_code": "US",
      "timezone": "America/New_York",
      "is_active": true,
      "created_at": "2022-01-15T10:30:00Z",
      "updated_at": "2024-12-15T14:20:00Z"
    }
  ],
  "meta": {
    "current_page": 1,
    "last_page": 5,
    "per_page": 20,
    "total": 95
  }
}
POST /employees
Create new employee

Create a new employee record in your company.

Request Body:

{
  "employee_number": "EMP-002",
  "first_name": "Jane",
  "last_name": "Smith",
  "email": "[email protected]",
  "password": "SecurePass123!",
  "title": "HR Manager",
  "department": "Human Resources",
  "manager_id": 45,
  "hire_date": "2023-03-01",
  "employment_type": "full-time",
  "work_phone": "+1-555-0123",
  "mobile_phone": "+1-555-0124",
  "work_locations": [
    {
      "address": "456 Oak Ave, Los Angeles, CA 90210",
      "is_primary": true
    }
  ],
  "emergency_contact": {
    "name": "Bob Smith",
    "phone": "+1-555-0125",
    "relationship": "Brother"
  },
  "payroll_id": "PAY-002",
  "country_code": "US",
  "timezone": "America/Los_Angeles",
  "ssn_sin": "123-45-6789"
}

Field Descriptions:

Field Type Required Description
first_name string Employee's first name
last_name string Employee's last name
email string Unique email address
password string Required. Minimum 8 characters. This is the employee’s login password.
employee_number string Unique employee identifier
title string Job title
department string Department name
manager_id integer ID of reporting manager
hire_date date Date of hire (YYYY-MM-DD)
employment_type enum full-time, part-time, contract, temporary
work_phone string Work phone number
mobile_phone string Mobile phone number
work_locations array Array of work location objects
emergency_contact object Emergency contact information
payroll_id string External payroll system ID
ssn_sin string SSN/SIN (encrypted in storage)
country_code string(2) ISO country code (default: CA)
timezone string Timezone identifier
GET /employees/{id}
Get employee details

Retrieve detailed information about a specific employee, including PTO balance.

Response includes:

  • Complete employee profile information
  • Current-year PTO via the same balance rules as the dashboard (vacation / sick / personal buckets when configured)
  • Pending leave days
PUT /employees/{id}
Update employee

Update an existing employee's information. All fields are optional except for validation constraints.

Note: Use "sometimes" validation rules - only provided fields will be updated.

DELETE /employees/{id}
Deactivate employee

Soft delete by setting employee status to inactive. This preserves historical leave data while preventing new requests.

Leave Requests

GET /leave-requests
List leave requests

Retrieve leave requests with optional filtering.

Query Parameters:

Parameter Type Description
employee_id integer Filter by employee ID
status string Filter by status (pending, approved, rejected)
type string Filter by type (vacation, sick, personal, unpaid)
year integer Filter by year
POST /leave-requests
Create leave request

employee_id must belong to your company (foreign IDs return 422, not 404).

{
  "employee_id": 123,
  "type": "vacation",
  "start_date": "2026-10-01",
  "end_date": "2026-10-03",
  "notes": "Family trip"
}

type: vacation, sick, personal, unpaid, other (annual is still accepted).

POST /leave-requests/{id}/approve
Approve leave request

Approve a pending leave request. Fires webhook leave_request.approved.

POST /leave-requests/{id}/reject
Reject leave request

Optional body: {"rejection_reason": "…"}. Fires leave_request.rejected.

Time entries

Read requires employees:read. Import and adjust require employees:write. Posting the same employee + clock_in again returns the existing row (idempotent).

Method Path Notes
GET /time-entries Query: employee_id, from, to, status (active|completed|adjusted), approved, per_page
POST /time-entries employee_id or employee_email, clock_in, optional clock_out, break_minutes, notes, source. Already-completed imports fire time_entry.completed.
GET /time-entries/{id} Single punch
PUT /time-entries/{id} Fix times, add break, or close an open shift

Schedules

Read requires employees:read. Writes require employees:write. One-day hours create an approved schedule exception for that date only — they do not rewrite Monday–Sunday.

Method Path Notes
GET /employees/{id}/schedule Weekly pattern plus upcoming one-day overrides
PUT /employees/{id}/schedule effective_date, schedules[].day_of_week (0=Sun–6=Sat), is_working, start_time, end_time, break_duration, work_location_id
POST /employees/{id}/one-day-overrides override_date, start_time, end_time, work_location_id, optional break_duration
POST /employees/{id}/one-day-overrides/{id}/cancel Today or future dates only

Forgot-punch requests

Approve creates a completed time entry. Fires forgot_punch.approved.

Method Path Notes
GET /forgot-punch-requests Query: employee_id, status, from, to, per_page
POST /forgot-punch-requests/{id}/approve Optional reviewer_notes
POST /forgot-punch-requests/{id}/reject Optional reviewer_notes

Payroll

GET /reports/payroll-hours uses the same paid-hours engine as the Excel report (scheduled pay, holidays, approved day adjustments). Day adjustments do not change payroll until approved unless you pass apply_immediately.

Method Path Notes
GET /pay-periods Company pay periods
GET /pay-periods/{id}/summary Punch totals for that period
GET /reports/payroll-hours Required query: from, to (YYYY-MM-DD). Optional include_zero_hours
GET /payroll-day-adjustments Query: employee_id, status, from, to
POST /payroll-day-adjustments employee_id, work_date, note (required), hours_delta (−12 to 12), apply_immediately
POST /payroll-day-adjustments/{id}/approve Optional hours_delta, reviewer_notes. Fires payroll_day_adjustment.approved
POST /payroll-day-adjustments/{id}/reject Optional reviewer_notes

Mileage & work locations

Method Path Notes
GET /mileage-trips employee_id, from, to, status, per_page
POST /mileage-trips Requires mileage enabled. employee_id, trip_date, start_address, end_address, distance_km, purpose
POST /mileage-trips/{id}/approve Fires mileage_trip.approved
POST /mileage-trips/{id}/reject Optional rejection_reason
GET /work-locations Company locations
POST /work-locations name required; address/city optional (empty strings stored when omitted)
PUT /work-locations/{id} Partial update

Webhooks

HTTPS URLs only. The signing secret is returned once on create — HMAC-SHA256 of the raw body in X-TimeOff-Signature.

Method Path
GET/webhooks
POST/webhooks
DELETE/webhooks/{id}

Subscribe to any of:

leave_request.created, leave_request.approved, leave_request.rejected, time_entry.completed, time_entry.approved, payroll_run.approved, payroll_day_adjustment.approved, mileage_trip.approved, forgot_punch.approved, invoice.paid

Other Resources

GET /holidays/{country_code}
Get public holidays

Retrieve public holidays for a specific country. Query: year (defaults to current year).

Example Response:

{
  "data": [
    {
      "name": "New Year's Day",
      "date": "2024-01-01",
      "type": "public_holiday",
      "country_code": "US"
    }
  ],
  "meta": {
    "country": "US",
    "year": 2024,
    "total": 10,
    "weekdays": 8
  }
}
GET /reports/leave-summary
Leave balances

Query year. Returns dashboard-accurate buckets: vacation_available, sick_available, remaining_days, pending_days.

GET /company
Company info

id, name, country_code, timezone, total_employees (all company staff, not only role=employee).

GET /leave-types
Leave types

Built-in types plus active custom types for the company.

Invoicing. Native invoicing lives in the web app. If invoicing is enabled for the company, a small slice exists under /billing/clients and /billing/invoices (list/create/show/send/payments/pdf). Estimates, expenses, recurring, and Stripe Connect are not part of this API.

Error Handling

The API uses standard HTTP status codes to indicate success or failure of requests.

Status Code Description
200 Success - Request completed successfully
201 Created - Resource created successfully
401 Unauthorized - Invalid or missing API key
403 Forbidden — missing permission, invoicing disabled, or mileage not enabled
404 Not Found - Resource not found
422 Unprocessable Entity - Validation error
429 Too Many Requests - Rate limit exceeded

Error Response Format

{
  "error": "Validation Error",
  "message": "The given data was invalid.",
  "errors": {
    "email": ["The email field is required."],
    "employee_number": ["The employee number has already been taken."]
  }
}

Rate Limiting

Each API key has its own hourly cap (default 1000 requests per hour, configurable when you create the key). Exceeding it returns HTTP 429. Rate-limit headers are not currently sent; count against the key’s rate_limit setting.

SDKs & Libraries

While we don't yet provide official SDKs, our RESTful API works seamlessly with any HTTP client library in your preferred programming language.

Node.js / JavaScript

Use fetch, axios, or any HTTP client:

axios, fetch, request

Python

Compatible with popular libraries:

requests, httpx, urllib

PHP

Works with standard HTTP clients:

Guzzle, cURL, file_get_contents

Need Help?

Getting Started

Support Channels

  • • Email: [email protected]
  • • Response time: < 24 hours
  • • Available: Monday-Friday, 9 AM - 5 PM PST
  • • Enterprise support available