TrainAR REST API
The TrainAR REST API gives you direct HTTP access to your TrainAR account.
The TrainAR REST API gives you direct HTTP access to your TrainAR account. All endpoints are available at the base URL https://api.trainar.ai/v1/ and authenticated with a tak_* API key.
Every request must include:
Authorization: Bearer tak_your_key_here
Content-Type: application/json
Responses are always JSON. Errors follow a consistent shape:
{ "error": "ERROR_CODE", "message": "Human-readable description" }
Tasks
Tasks are operational work items that can be created manually, via integrations, or via the API. The task lifecycle is open -> in_progress -> completed (or cancelled).
Create a task
POST /api-tenant-tasks
Required scope: write:tasks
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Task title |
description |
string | No | Longer description or notes |
priority |
string | No | low, medium (default), high, or urgent |
due_date |
ISO-8601 string | No | Target completion date |
assigned_to |
UUID | No | User ID within your account to assign the task to |
external_id |
string | No | Your system's reference ID (for matching records) |
Response: 201 Created
{
"id": "uuid",
"title": "Service boiler at 12 Elm Street",
"description": null,
"status": "open",
"priority": "medium",
"source": "api",
"external_id": "JOB-4921",
"assigned_to": { "user_id": "uuid", "name": "Jordan Smith" },
"due_date": "2026-06-01T00:00:00.000Z",
"created_at": "2026-05-18T10:00:00.000Z",
"completed_at": null,
"session_id": null
}
Example:
curl -X POST \
https://api.trainar.ai/v1/api-tenant-tasks \
-H "Authorization: Bearer tak_your_key_here" \
-H "Content-Type: application/json" \
-d '{"title":"Service boiler at 12 Elm Street","external_id":"JOB-4921","priority":"high"}'
Error codes:
| Status | Code | Cause |
|---|---|---|
| 400 | BAD_REQUEST |
Missing title, invalid priority, or assigned_to user not in account |
| 403 | INSUFFICIENT_PERMISSIONS |
Key lacks write:tasks scope |
Creating a task fires a task.created webhook to any subscribed endpoints.
List tasks
GET /api-tenant-tasks
Required scope: read:tasks
Query parameters:
| Parameter | Type | Description |
|---|---|---|
status |
string | Filter by status: open, in_progress, completed, cancelled |
source |
string | Filter by source: manual, api, integration |
assigned_to |
UUID | Filter by assigned user |
from |
ISO-8601 string | Created at or after this timestamp |
to |
ISO-8601 string | Created at or before this timestamp |
limit |
integer | Max results per page, 1-100 (default 50) |
offset |
integer | Pagination offset (default 0) |
Response: 200 OK
{
"tasks": [ /* array of task objects, same shape as create response */ ],
"total": 142,
"limit": 50,
"offset": 0
}
Results are ordered by created_at descending (newest first).
Example:
curl "https://api.trainar.ai/v1/api-tenant-tasks?status=open&limit=20" \
-H "Authorization: Bearer tak_your_key_here"
Update a task
PATCH /api-tenant-tasks?task_id=<uuid>
Required scope: write:tasks
Query parameter:
| Parameter | Required | Description |
|---|---|---|
task_id |
Yes | UUID of the task to update |
Request body (all fields optional -- send only what you want to change):
| Field | Type | Description |
|---|---|---|
title |
string | New title |
description |
string | Updated description |
status |
string | New status: open, in_progress, completed, cancelled |
priority |
string | low, medium, high, urgent |
due_date |
ISO-8601 string | Updated due date |
assigned_to |
UUID or null | Reassign or unassign |
Response: 200 OK -- same task object shape as create.
Example:
curl -X PATCH \
"https://api.trainar.ai/v1/api-tenant-tasks?task_id=abc-123" \
-H "Authorization: Bearer tak_your_key_here" \
-H "Content-Type: application/json" \
-d '{"status":"in_progress"}'
Error codes:
| Status | Code | Cause |
|---|---|---|
| 400 | BAD_REQUEST |
Invalid field value or missing task_id |
| 404 | NOT_FOUND |
Task not found or belongs to a different account |
If the status changes, a task.status_changed webhook fires. If the new status is completed, a task.completed webhook fires in addition.
Sessions
Read session history and update session-level metadata. Sessions themselves are created by the on-glasses Core App; this API is for retrieval and limited post-hoc edits.
List sessions
GET /api-tenant-sessions
Required scope: read:sessions
Query parameters:
| Parameter | Type | Description |
|---|---|---|
status |
string | Filter by status (e.g. completed, in_progress, failed) |
role |
string | Filter by session role: trainer or trainee |
from |
ISO-8601 string | Started at or after this timestamp |
to |
ISO-8601 string | Started at or before this timestamp |
limit |
integer | 1-100 (default 50) |
offset |
integer | Pagination offset (default 0) |
Response: 200 OK
Returns a paginated list of sessions with metadata (name, status, role, duration, started/completed timestamps, video URL, procedure count). Use the Account MCP get_session tool for the full per-session detail including summary and procedures.
Update a session
PATCH /api-tenant-sessions?session_id=<id>
Required scope: write:sessions (not exposed in the standard API-key UI — contact us if you need this for an integration).
Allowed body fields: session_name, session_description, session_summary, duration_seconds, procedure_count (alias: procedures_count), video_url, thumbnail_url. All optional — send only what changes.
Error codes:
| Status | Code | Cause |
|---|---|---|
| 400 | BAD_REQUEST |
Missing/invalid session_id or body field |
| 400 | CONFLICTING_ALIASES |
Both procedure_count and procedures_count sent with different values |
| 404 | NOT_FOUND |
Session doesn't exist in your account |
| 405 | METHOD_NOT_ALLOWED |
Only GET and PATCH are accepted |
Users
Invite a user
POST /api-tenant-users
Required scope: manage:users
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
email |
string | Yes | Email address for the invitation |
full_name |
string | Yes | Display name |
roles |
string[] | Yes | Array of roles. Currently accepts ["admin"]. Trainer/trainee assignment happens via seat assignment after the user accepts their invitation. |
Response: 201 Created
{
"success": true,
"user_id": "uuid",
"email": "jordan@example.com",
"full_name": "Jordan Smith",
"roles": ["admin"],
"message": "User invited successfully"
}
An invitation email is sent to the address automatically. The user sets their password on first login.
Example:
curl -X POST \
https://api.trainar.ai/v1/api-tenant-users \
-H "Authorization: Bearer tak_your_key_here" \
-H "Content-Type: application/json" \
-d '{"email":"jordan@example.com","full_name":"Jordan Smith","roles":["admin"]}'
Error codes:
| Status | Code | Cause |
|---|---|---|
| 400 | BAD_REQUEST |
Missing or invalid fields |
| 403 | FORBIDDEN |
Key lacks manage:users scope |
| 409 | CONFLICT |
Email already exists in this account, is deactivated (use reactivate endpoint), or belongs to another organisation |
List users
GET /api-tenant-users
Required scope: read:users
Query parameters:
| Parameter | Type | Description |
|---|---|---|
role |
string | Filter by role: trainer, trainee, tenant_admin |
status |
string | Filter by status: active, inactive |
limit |
integer | 1-100 (default 50) |
offset |
integer | Pagination offset (default 0) |
Response: 200 OK
{
"users": [
{
"id": "uuid",
"email": "jordan@example.com",
"full_name": "Jordan Smith",
"role": "trainer",
"status": "active",
"seat": {
"id": "uuid",
"device_name": "HoloLens-01",
"device_token_set": true
},
"last_session_at": "2026-05-17T14:30:00.000Z",
"total_sessions": 12,
"total_minutes": 186,
"created_at": "2026-03-01T09:00:00.000Z"
}
],
"total": 8,
"limit": 50,
"offset": 0
}
seat is null if no seat has been assigned to the user yet.
Skills
Execute a skill
POST /api-tenant-skills-execute
Required scope: write:skills
Executes a TrainAR skill server-to-server. The skill runs against your account's knowledge base. If you do not provide a session_context, the endpoint auto-resolves one from the first active assigned seat in your account.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
skill_id |
UUID | Yes | ID of the skill to execute. Find IDs in Dashboard → Skills or via the MCP list_skills tool. |
arguments |
object | Yes | Input parameters the skill expects (shape varies per skill) |
session_context |
object | No | Optional session context override (see below) |
session_context fields (all optional if auto-resolving):
| Field | Type | Description |
|---|---|---|
seat_id |
UUID | Seat to associate the execution with |
user_id |
UUID | User running the skill |
role |
string | trainer or trainee |
session_id |
UUID | Link to an open session |
Response: 200 OK -- shape varies by skill.
Example:
curl -X POST \
https://api.trainar.ai/v1/api-tenant-skills-execute \
-H "Authorization: Bearer tak_your_key_here" \
-H "Content-Type: application/json" \
-d '{
"skill_id": "skill-uuid-here",
"arguments": { "query": "How do I bleed a radiator?" }
}'
Error codes:
| Status | Code | Cause |
|---|---|---|
| 400 | BAD_REQUEST |
Missing skill_id or arguments, or no active seat to auto-resolve |
| 400 | DISABLED |
Skill exists but is inactive |
| 403 | NOT_ENTITLED |
Skill is not in your account's bundle allocation |
| 404 | NOT_FOUND |
Skill ID does not exist |
Webhooks
Webhook subscription is managed through the same API. Rather than repeating the full reference here, see Webhooks for the complete guide including the subscribe, check, and delete endpoints, payload shapes, and HMAC signature verification.
Quick reference:
| Method | Path | Scope | Action |
|---|---|---|---|
POST |
/webhook-subscribe |
manage:webhooks |
Subscribe a new endpoint |
GET |
/webhook-subscribe?endpoint_id=<id> |
manage:webhooks |
Check whether an endpoint exists |
DELETE |
/webhook-subscribe?endpoint_id=<id> |
manage:webhooks |
Unsubscribe an endpoint |
Common error responses
| Status | Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST |
Validation error — see message for the specific field |
| 401 | MISSING_AUTH / INVALID_KEY |
No Authorization header, malformed header, or key not found |
| 403 | INSUFFICIENT_PERMISSIONS / NOT_ENTITLED |
Valid key but insufficient scope, or skill not in account's bundle allocations |
| 404 | NOT_FOUND |
Record does not exist or belongs to another account |
| 405 | METHOD_NOT_ALLOWED |
Wrong HTTP method for the endpoint |
| 409 | CONFLICT |
Duplicate record (invite endpoint) |
| 500 | INTERNAL_ERROR |
Server-side error — retry with backoff |
All error bodies use the same shape: { "error": "CODE", "message": "Human-readable description" }. There are no code, field, or trace_id sub-fields.
Forms
Forms are reusable templates (for example service sheets or gas safety records) that engineers complete during a session. Manage your account's forms and browse the platform form library over the API. All form endpoints live at https://api.trainar.ai/v1/api-tenant-forms.
List your forms
GET /api-tenant-forms
Required scope: read:forms. Query parameters: limit (default 50, max 100), offset (default 0). Add library=true to browse the platform form library available to your account instead of your own forms.
Response: 200 OK
{
"forms": [
{
"id": "uuid",
"name": "Gas Safety Record (CP12)",
"aliases": ["CP12"],
"category": "compliance",
"source": "platform",
"is_active": true,
"is_platform_library": false,
"imported_from": "uuid",
"template_image_url": "https://.../cp12.png",
"template_mime_type": "image/png",
"field_count": 14,
"fields": [],
"created_at": "2026-06-01T00:00:00.000Z",
"updated_at": "2026-06-01T00:00:00.000Z"
}
],
"limit": 50,
"offset": 0
}
Get a single form
GET /api-tenant-forms?id={form_id}
Required scope: read:forms. Returns 404 NOT_FOUND if the form is not yours or not in the library.
Import a form from the library
POST /api-tenant-forms
Required scope: manage:forms. Body: { "action": "import", "template_id": "uuid" } where template_id is a platform-library form. Returns 201 Created with the imported form.
Create your own form
POST /api-tenant-forms
Required scope: manage:forms
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Form name |
template_image_url |
string | One of url/base64 | URL of the template image or PDF |
template_image_base64 |
string | One of url/base64 | Base64-encoded template |
aliases |
string[] | No | Alternative names |
category |
string | No | Grouping label |
fields |
array | No | Field definitions |
template_mime_type |
string | No | Defaults to image/png (png/jpg/pdf/webp) |
Response: 201 Created with the new form.
curl -X POST \
https://api.trainar.ai/v1/api-tenant-forms \
-H "Authorization: Bearer tak_your_key_here" \
-H "Content-Type: application/json" \
-d '{"name":"Service Sheet","template_image_url":"https://example.com/sheet.png","category":"service"}'
Update a form
PUT /api-tenant-forms?id={form_id}
Required scope: manage:forms. Body (all optional): name, aliases, is_active, category, fields, template_image_url. Returns 200 OK with the updated form.
Delete a form
DELETE /api-tenant-forms?id={form_id}
Required scope: manage:forms. Returns 200 OK { "deleted": true, "id": "uuid" }.
Error codes:
| Status | Code | Cause |
|---|---|---|
| 400 | BAD_REQUEST |
Missing name on create, or missing id on update/delete |
| 403 | INSUFFICIENT_PERMISSIONS |
Key lacks the required read:forms / manage:forms scope |
| 404 | NOT_FOUND |
Form not found, or not owned by your account |