The TL;DR Trainer REST API provides programmatic access to manage users, administrators, API keys, and other resources.
Base URL
- Production:
https://api.tldrtrainer.com/api/v1
Authentication
All endpoints require API key authentication. Include your API key in one of these headers:
Authorization: Bearer tldr_live_xxxxx
or
X-API-Key: tldr_live_xxxxx
API keys are scoped to an organisation.
Error Responses
All errors follow this format:
{
"error": {
"code": "error_code",
"message": "Human-readable error message"
}
}
Common error codes:
unauthorized- Missing or invalid API keynot_found- Resource not foundvalidation_error- Invalid request dataconflict- Resource already exists or state conflictinternal_error- Server error
Endpoints
API Keys
GET List API Keys
GET /api/v1/api-keys
Returns all API keys for your organisation.
Response:
{
"data": [
{
"id": "uuid",
"name": "Production Key",
"key_prefix": "tldr_live_abc123",
"status": "active",
"created_at": "2024-01-15T10:00:00Z",
"created_by": "admin@example.com",
"last_used_at": "2024-06-01T14:30:00Z",
"expires_at": null
}
],
"meta": {
"total": 1,
"active": 1,
"disabled": 0
}
}
POST Create API Key
POST /api/v1/api-keys
Request:
{
"name": "My API Key",
"expires_in_days": 365
}
Response (201):
{
"data": {
"id": "uuid",
"name": "My API Key",
"key_prefix": "tldr_live_abc123",
"key": "tldr_live_abc123xyz...",
"status": "active",
"created_at": "2024-06-20T10:00:00Z",
"created_by": "api:tldr_live_xxx",
"expires_at": "2025-06-20T10:00:00Z"
},
"warning": "This API key will only be shown once. Please copy it now and store it securely."
}
GET Get API Key
GET /api/v1/api-keys/:id
PATCH Update API Key
PATCH /api/v1/api-keys/:id
Request:
{
"name": "Updated Name"
}
POST Disable API Key
POST /api/v1/api-keys/:id/disable
POST Enable API Key
POST /api/v1/api-keys/:id/enable
DELETE Delete API Key
DELETE /api/v1/api-keys/:id
Returns 204 No Content on success.
Users
GET List Users
GET /api/v1/users
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number (1-indexed) |
| per_page | integer | 25 | Items per page (max 100) |
| status | string | - | Filter: active, invited, deactivated |
| search | string | - | Search in name and email |
| sort | string | created_at | Sort: name, email, created_at, updated_at |
| order | string | desc | Order: asc, desc |
Response:
{
"data": [
{
"id": "uuid",
"email": "john@example.com",
"name": "John Doe",
"slack_user_id": "U12345678",
"status": "active",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-06-01T14:30:00Z"
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 150,
"total_pages": 6
}
}
POST Create User
POST /api/v1/users
Request:
{
"email": "jane@example.com",
"name": "Jane Smith",
"slack_user_id": "U87654321"
}
Validation:
email- Required, valid email format, unique within organisationname- Required, 1-255 charactersslack_user_id- Optional, must match patternU[A-Z0-9]+
Response (201): Returns the created user.
New users are created with status invited.
GET Get User
GET /api/v1/users/:id
PATCH Update User
PATCH /api/v1/users/:id
Request:
{
"name": "Jane D. Smith",
"email": "jane.smith@example.com",
"slack_user_id": "U99999999"
}
All fields are optional. Only provided fields are updated. Set slack_user_id to empty string to remove it.
POST Activate User
POST /api/v1/users/:id/activate
Activates a user so they can receive tips.
Preconditions:
- User status must be
invitedordeactivated - User must have a Slack user ID configured
Error Codes:
conflict- User already activemissing_slack_id- No Slack user ID configured
POST Deactivate User
POST /api/v1/users/:id/deactivate
Stops tip delivery but preserves user data.
Error Codes:
conflict- User already deactivated
DELETE Delete User
DELETE /api/v1/users/:id
Soft deletes a user. Data is preserved but user is excluded from queries. Returns 204 No Content.
POST Bulk Import Users
POST /api/v1/users/import
Import users from a CSV file.
Request: multipart/form-data
| Field | Type | Required | Description |
|---|---|---|---|
| file | file | Yes | CSV file (max 5MB, max 1000 rows) |
| on_duplicate | string | No | skip (default) or update |
CSV Format:
email,name,slack_user_id
john@example.com,John Doe,U12345678
jane@example.com,Jane Smith,
Response:
{
"data": {
"processed": 100,
"created": 95,
"updated": 3,
"skipped": 2,
"errors": [
{
"row": 45,
"email": "invalid-email",
"error": "Invalid email format"
}
]
}
}
Administrators
Administrators are users who can access the customer console to manage the organisation. They are stored in AWS Cognito.
GET List Administrators
GET /api/v1/administrators
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number (1-indexed) |
| per_page | integer | 25 | Items per page (max 100) |
| status | string | - | Filter: active, pending |
| search | string | - | Search in name and email |
Response:
{
"data": [
{
"username": "admin-abc123",
"email": "admin@example.com",
"name": "Admin User",
"status": "active",
"enabled": true,
"created_at": "2024-01-15T10:00:00Z",
"last_login_at": "2024-06-20T08:30:00Z"
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 3,
"total_pages": 1
}
}
Status Values:
active- Administrator has accepted invitation and can log inpending- Administrator has been invited but hasn’t set their password yet
Notes:
last_login_atmay be null if the administrator hasn’t logged in yetenabledindicates whether the administrator account is enabled in Cognito
POST Invite Administrator
POST /api/v1/administrators/invite
Invites a new administrator to the organisation. Sends an invitation email with a temporary password.
Request:
{
"email": "newadmin@example.com",
"name": "New Admin"
}
Validation:
email- Required, valid email format, must not already be an administratorname- Required, 1-255 characters
Response (201 Created):
{
"data": {
"username": "admin-xyz789",
"email": "newadmin@example.com",
"name": "New Admin",
"status": "pending",
"enabled": true,
"created_at": "2024-06-20T10:00:00Z"
}
}
Side Effects:
- Creates user in AWS Cognito with temporary password
- Sends invitation email via Cognito
- Invitation valid for 7 days
Error Codes:
validation_error- Invalid email or nameconflict- Email already an administrator
GET Get Administrator
GET /api/v1/administrators/:username
Retrieves details for a specific administrator, including group memberships.
Path Parameters:
| Parameter | Description |
|---|---|
| username | Cognito username |
Response (200 OK):
{
"data": {
"username": "admin-abc123",
"email": "admin@example.com",
"name": "Admin User",
"status": "active",
"enabled": true,
"created_at": "2024-01-15T10:00:00Z",
"last_login_at": "2024-06-20T08:30:00Z",
"groups": [
{
"id": "grp_123",
"display_name": "Super Admins"
}
]
}
}
Notes:
- Groups are sourced from the admin groups database (managed via SCIM)
- Returns 404 if administrator doesn’t exist or belongs to a different organisation
DELETE Remove Administrator
DELETE /api/v1/administrators/:username
Removes (disables) an administrator from the organisation.
Path Parameters:
| Parameter | Description |
|---|---|
| username | Cognito username |
Response: 204 No Content on success.
Side Effects:
- Disables administrator in AWS Cognito (soft delete, does not hard delete)
- Removes administrator from all admin groups
- Audit event logged
Error Codes:
not_found- Administrator doesn’t exist or belongs to different organisationconflict- Cannot remove the last administrator
Notes:
- This operation disables the administrator rather than deleting them, preserving audit history
- At least one administrator must remain in the organisation
Courses
GET List Courses
GET /api/v1/courses
Returns all courses in the TL;DR Trainer catalogue with organisation-specific enablement status.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
| page | integer | 1 | Page number (1-indexed) |
| per_page | integer | 25 | Items per page (max 100) |
| category | string | - | Filter by category |
| search | string | - | Search in name and description |
| is_active | boolean | true | Filter by active status |
Response:
{
"data": [
{
"id": "crs_abc123",
"name": "Security Fundamentals",
"description": "Essential security concepts for all employees",
"category": "security",
"tip_count": 20,
"is_active": true,
"is_enabled": true,
"enabled_at": "2024-03-01T00:00:00Z",
"priority": 1,
"created_at": "2024-01-01T00:00:00Z"
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 6,
"total_pages": 1
},
"categories": ["compliance", "leadership", "security"]
}
Notes:
is_enabledindicates if the course is enabled for the calling organisationenabled_atandpriorityare only present when the course is enabledcategoriesincludes all distinct categories from active courses for filtering UI
GET Get Course
GET /api/v1/courses/:id
Returns detailed information about a specific course, including organisation-specific status and statistics.
Path Parameters:
| Parameter | Description |
|---|---|
| id | Course ID (e.g., crs_abc123) |
Response (200 OK):
{
"data": {
"id": "crs_abc123",
"name": "Security Fundamentals",
"description": "Essential security concepts for all employees",
"category": "security",
"tip_count": 20,
"is_active": true,
"created_at": "2024-01-01T00:00:00Z",
"updated_at": "2024-06-01T00:00:00Z",
"organization_status": {
"is_enabled": true,
"enabled_at": "2024-03-01T00:00:00Z",
"enabled_by": "admin@example.com",
"priority": 1,
"users_enrolled": 145,
"completion_rate": 0.65
},
"sample_tips": []
}
}
Organisation Status Fields:
| Field | Description |
|---|---|
| is_enabled | Whether the course is enabled for this organisation |
| enabled_at | When the course was enabled |
| enabled_by | Email of administrator who enabled the course |
| priority | Course priority (1 = highest) |
| users_enrolled | Number of users who have received at least one tip |
| completion_rate | Ratio of users who completed all tips (0.0 - 1.0) |
Notes:
organization_statusis only present when the course is enabled for the organisationsample_tipsis reserved for future use and currently returns an empty array- For non-enabled courses, the response includes only the basic course details
Error Codes:
not_found- Course doesn’t exist
POST Enable Course
POST /api/v1/courses/:id/enable
Enables a course for the organisation, making it available for tip delivery to users.
Path Parameters:
| Parameter | Description |
|---|---|
| id | Course ID (e.g., crs_abc123) |
Request Body (Optional):
{
"priority": 2
}
| Field | Type | Default | Description |
|---|---|---|---|
| priority | integer | auto | Delivery priority (lower = higher priority). If not specified, auto-increments from the highest existing priority. |
Response (200 OK):
{
"data": {
"id": "crs_abc123",
"name": "Security Fundamentals",
"is_enabled": true,
"enabled_at": "2024-06-20T10:00:00Z",
"enabled_by": "api:tldr_live_abc",
"priority": 2
}
}
Side Effects:
- Creates an
organization_coursesrecord linking the course to the organisation - Tips from this course will be included in subsequent delivery cycles
- Audit event is logged
Error Codes:
not_found- Course doesn’t existconflict- Course is already enabled for this organisation
Rate Limits
Rate limits are not currently enforced but may be added in future versions.
Changelog
- 2024-12 - Added enable course endpoint
- 2024-12 - Added get course details endpoint
- 2024-12 - Added list courses endpoint
- 2024-12 - Added remove administrator endpoint
- 2024-12 - Added get administrator details endpoint
- 2024-12 - Added administrator invite endpoint
- 2024-12 - Added administrator listing endpoint
- 2024-12 - Initial API key and user management endpoints