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_••••
List employees
Returns the roster ordered by last name, then first name. Inactive employees are excluded unless you ask for them.
| Query parameter | Type | Default | Notes |
|---|---|---|---|
page | integer | 1 | 1-based page number. |
pageSize | integer | 100 | Capped at 500. |
includeInactive | boolean | false | Pass 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 }
| Field | Type | Notes |
|---|---|---|
externalId | string | Your employee code; the key used by every other endpoint. |
firstName | string | |
lastName | string | |
email | string or null | Many workers have no corporate email. |
location | string or null | Location name. Internal ids are never exposed. |
department | string or null | Department name. |
timeZone | string or null | Explicit IANA timezone override; null means the employee follows the location or company default. |
preferredLanguage | string or null | Preferred lesson language (BCP-47, e.g. "en", "es"). Read-only in this API. |
isActive | boolean | false after a DELETE (see below). |
Create an employee
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 field | Type | Required | Notes |
|---|---|---|---|
externalId | string | yes | Max 256 characters. Unique per company (case-insensitive). |
firstName | string | yes | Max 128 characters. |
lastName | string | yes | Max 128 characters. |
email | string | no | Max 256 characters. |
location | string | no | Site name; resolved to an existing location or created when new. Max 256 characters. |
department | string | no | Department name; resolved or created like location. Max 256 characters. |
timeZone | string | no | IANA 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
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, ortimeZone. ClearingtimeZonereverts the employee to the location/company default. firstNameandlastNamemust 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 is a soft deactivation, never an erase. Completion history stays intact for compliance records; the employee simply stops being scheduled.
isActivebecomesfalse; the employee disappears from the default list (useincludeInactive=trueto see them).- Idempotent: deactivating an already-inactive employee still returns
204. - The external id stays reserved, so a later
POSTwith the same id returns409 duplicate_external_idrather 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
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.
| Status | Code | When |
|---|---|---|
400 | Validation failed (missing required field, over-length value, blank name on update). The body carries an errors dictionary keyed by field. | |
404 | unknown_user | No employee matches the external id (update and delete). |
409 | duplicate_external_id | Create with an external id that is already taken, including by a deactivated employee. |
409 | seat_cap_reached | Create would exceed the plan's employee limit; detail includes the cap. |
429 | rate_limited | Too 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-1001throughE-1008(the hero employee isE-1001), across the Dallas DC and Fort Worth Hub locations. - The sandbox key is limited to 60 requests/minute per IP.