API reference

Employee management

Sync your roster from an HRIS, WMS, or onboarding flow: list, create, update, and deactivate employees, and look up the locations they can be assigned to. Every request is scoped to your company by the API key. The externalId you supply (your own employee code) is the key used by every other endpoint, including /today and the reporting API.

# Base URL (all paths below are relative to it)
https://toolboxminute.com/api

# Authenticate every request, either way:
Authorization: Bearer tbm_live_••••
X-Api-Key: tbm_live_••••
Endpoints
GET/v1/employees
List the roster (active only by default, paged).
POST/v1/employees
Create an employee.
PUT/v1/employees/{externalId}
Update an employee (partial: omitted fields are unchanged).
DELETE/v1/employees/{externalId}
Deactivate an employee (soft: history is retained).
GET/v1/locations
List the company's locations and their timezones.

List employees

GET/v1/employees

Returns the roster ordered by last name, then first name. Inactive employees are excluded unless you ask for them.

Query parameterTypeDefaultNotes
pageinteger11-based page number.
pageSizeinteger100Capped at 500.
includeInactivebooleanfalsePass true to include deactivated employees.
# List active employees
GET /api/v1/employees?page=1&pageSize=50
Host: toolboxminute.com
X-Api-Key: tbm_test_wWlSr6q-BPXUN67BPbzyVhR703QGmSlk34EEWGqRBtA

# 200 OK
{
  "items": [
    {
      "externalId": "E-1003",
      "firstName": "Tasha",
      "lastName": "Green",
      "email": null,
      "location": "Dallas DC",
      "department": null,
      "timeZone": null,
      "preferredLanguage": null,
      "isActive": true
    },
    {
      "externalId": "E-1002",
      "firstName": "Miguel",
      "lastName": "Herrera",
      "email": null,
      "location": "Dallas DC",
      "department": null,
      "timeZone": null,
      "preferredLanguage": null,
      "isActive": true
    }
  ],
  "page": 1,
  "pageSize": 50,
  "total": 8
}
FieldTypeNotes
externalIdstringYour employee code; the key used by every other endpoint.
firstNamestring
lastNamestring
emailstring or nullMany workers have no corporate email.
locationstring or nullLocation name. Internal ids are never exposed.
departmentstring or nullDepartment name.
timeZonestring or nullExplicit IANA timezone override; null means the employee follows the location or company default.
preferredLanguagestring or nullPreferred lesson language (BCP-47, e.g. "en", "es"). Read-only in this API.
isActivebooleanfalse after a DELETE (see below).

Create an employee

POST/v1/employees

Location and department are matched by name (case-insensitive) and created automatically when the name is new, the same rules as CSV import. Only new employees consume plan seats.

Body fieldTypeRequiredNotes
externalIdstringyesMax 256 characters. Unique per company (case-insensitive).
firstNamestringyesMax 128 characters.
lastNamestringyesMax 128 characters.
emailstringnoMax 256 characters.
locationstringnoSite name; resolved to an existing location or created when new. Max 256 characters.
departmentstringnoDepartment name; resolved or created like location. Max 256 characters.
timeZonestringnoIANA timezone (e.g. "America/Chicago") overriding the location/company default. Max 64 characters.
# Create an employee
POST /api/v1/employees
Host: toolboxminute.com
X-Api-Key: tbm_test_wWlSr6q-BPXUN67BPbzyVhR703QGmSlk34EEWGqRBtA
Content-Type: application/json

{
  "externalId": "E-2001",
  "firstName": "Jordan",
  "lastName": "Reyes",
  "email": "jordan.reyes@example.com",
  "location": "Dallas DC",
  "department": "Receiving",
  "timeZone": "America/Chicago"
}

# 201 Created
Location: /api/v1/employees/E-2001/today

{
  "externalId": "E-2001",
  "firstName": "Jordan",
  "lastName": "Reyes",
  "email": "jordan.reyes@example.com",
  "location": "Dallas DC",
  "department": "Receiving",
  "timeZone": "America/Chicago",
  "preferredLanguage": null,
  "isActive": true
}

The Location response header points at the new employee's /today resource, so a sync job can immediately follow it to fetch a launch URL.

# 409 Conflict: the external id is already taken (comparison is case-insensitive,
# and deactivated employees keep their id reserved)
{
  "title": "duplicate_external_id",
  "detail": "An employee already exists for external id 'E-2001'.",
  "status": 409
}

# 409 Conflict: the plan's employee limit is full
{
  "title": "seat_cap_reached",
  "detail": "Employee limit reached (50). Add seats in Billing to raise the cap.",
  "status": 409
}

Update an employee

PUT/v1/employees/{externalId}

Partial update with three simple rules:

  • Fields left null (or omitted) are unchanged.
  • Send an empty string ("") to clear an optional field: email, location, department, or timeZone. Clearing timeZone reverts the employee to the location/company default.
  • firstName and lastName must be non-blank when provided (blank values are a 400).

Location and department resolve by name and are created when new, exactly like create.

# Move to Fort Worth, clear the department, keep everything else
PUT /api/v1/employees/E-1003
Host: toolboxminute.com
X-Api-Key: tbm_test_wWlSr6q-BPXUN67BPbzyVhR703QGmSlk34EEWGqRBtA
Content-Type: application/json

{
  "location": "Fort Worth Hub",
  "department": ""
}

# 200 OK
{
  "externalId": "E-1003",
  "firstName": "Tasha",
  "lastName": "Green",
  "email": null,
  "location": "Fort Worth Hub",
  "department": null,
  "timeZone": null,
  "preferredLanguage": null,
  "isActive": true
}

# 404 Not Found: no employee has that external id
{
  "title": "unknown_user",
  "detail": "No employee was found for external id 'E-9999'.",
  "status": 404
}

Deactivate an employee

DELETE/v1/employees/{externalId}

Delete is a soft deactivation, never an erase. Completion history stays intact for compliance records; the employee simply stops being scheduled.

  • isActive becomes false; the employee disappears from the default list (use includeInactive=true to see them).
  • Idempotent: deactivating an already-inactive employee still returns 204.
  • The external id stays reserved, so a later POST with the same id returns 409 duplicate_external_id rather than creating a duplicate record.
  • Deactivated employees still count toward the plan's employee cap.
DELETE /api/v1/employees/E-1003
Host: toolboxminute.com
X-Api-Key: tbm_test_wWlSr6q-BPXUN67BPbzyVhR703QGmSlk34EEWGqRBtA

# 204 No Content (404 unknown_user when the id does not exist)

List locations

GET/v1/locations

The company's sites, ordered by name. Employee endpoints reference locations by name, so this is the lookup to sync against; the id can be stored for correlation but is never used as input.

GET /api/v1/locations
Host: toolboxminute.com
X-Api-Key: tbm_test_wWlSr6q-BPXUN67BPbzyVhR703QGmSlk34EEWGqRBtA

# 200 OK
[
  {
    "id": "9c2f4b1e-7a53-4e8d-b6f0-2d81c5a9e347",
    "name": "Dallas DC",
    "ianaTimeZone": "America/Chicago"
  },
  {
    "id": "e517d8a2-3c96-42fb-9d14-6b0a87f2c1d5",
    "name": "Fort Worth Hub",
    "ianaTimeZone": null
  }
]

ianaTimeZone is null when the location follows the company default.

Errors

Errors are RFC 7807 problem responses (application/problem+json) with the machine code in title, as shown in the samples above.

StatusCodeWhen
400Validation failed (missing required field, over-length value, blank name on update). The body carries an errors dictionary keyed by field.
404unknown_userNo employee matches the external id (update and delete).
409duplicate_external_idCreate with an external id that is already taken, including by a deactivated employee.
409seat_cap_reachedCreate would exceed the plan's employee limit; detail includes the cap.
429rate_limitedToo many requests; retry after the Retry-After header. Live keys: 300 requests/minute per key.

Try it in the sandbox

The samples above run as-is against the public sandbox company, Acme Logistics, using the shared test key (it only ever resolves to sandbox data):

X-Api-Key: tbm_test_wWlSr6q-BPXUN67BPbzyVhR703QGmSlk34EEWGqRBtA
  • Roster: eight employees, E-1001 through E-1008 (the hero employee is E-1001), across the Dallas DC and Fort Worth Hub locations.
  • The sandbox key is limited to 60 requests/minute per IP.
The sandbox resets nightly at 04:00 UTC. Employees you create or change through this API vanish at the reset and the seeded roster comes back, so do not point a production sync at it.