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 key
  • not_found - Resource not found
  • validation_error - Invalid request data
  • conflict - Resource already exists or state conflict
  • internal_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:

ParameterTypeDefaultDescription
pageinteger1Page number (1-indexed)
per_pageinteger25Items per page (max 100)
statusstring-Filter: active, invited, deactivated
searchstring-Search in name and email
sortstringcreated_atSort: name, email, created_at, updated_at
orderstringdescOrder: 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 organisation
  • name - Required, 1-255 characters
  • slack_user_id - Optional, must match pattern U[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 invited or deactivated
  • User must have a Slack user ID configured

Error Codes:

  • conflict - User already active
  • missing_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

FieldTypeRequiredDescription
filefileYesCSV file (max 5MB, max 1000 rows)
on_duplicatestringNoskip (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:

ParameterTypeDefaultDescription
pageinteger1Page number (1-indexed)
per_pageinteger25Items per page (max 100)
statusstring-Filter: active, pending
searchstring-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 in
  • pending - Administrator has been invited but hasn’t set their password yet

Notes:

  • last_login_at may be null if the administrator hasn’t logged in yet
  • enabled indicates 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 administrator
  • name - 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 name
  • conflict - Email already an administrator

GET Get Administrator

GET /api/v1/administrators/:username

Retrieves details for a specific administrator, including group memberships.

Path Parameters:

ParameterDescription
usernameCognito 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:

ParameterDescription
usernameCognito 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 organisation
  • conflict - 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:

ParameterTypeDefaultDescription
pageinteger1Page number (1-indexed)
per_pageinteger25Items per page (max 100)
categorystring-Filter by category
searchstring-Search in name and description
is_activebooleantrueFilter 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_enabled indicates if the course is enabled for the calling organisation
  • enabled_at and priority are only present when the course is enabled
  • categories includes 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:

ParameterDescription
idCourse 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:

FieldDescription
is_enabledWhether the course is enabled for this organisation
enabled_atWhen the course was enabled
enabled_byEmail of administrator who enabled the course
priorityCourse priority (1 = highest)
users_enrolledNumber of users who have received at least one tip
completion_rateRatio of users who completed all tips (0.0 - 1.0)

Notes:

  • organization_status is only present when the course is enabled for the organisation
  • sample_tips is 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:

ParameterDescription
idCourse ID (e.g., crs_abc123)

Request Body (Optional):

{
  "priority": 2
}
FieldTypeDefaultDescription
priorityintegerautoDelivery 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_courses record 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 exist
  • conflict - 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