Developer · Last updated 18 May 2026 · 8 min read

Tenant REST API

The Tenant REST API gives you direct HTTP access to your TrainAR account.

The Tenant 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 tenant 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 tenant
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 tenant

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 Tenant 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 tenant
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 tenant, 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 tenant's knowledge base. If you do not provide a session_context, the endpoint auto-resolves one from the first active assigned seat in your tenant.

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 tenant'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 tenant's bundle allocations
404 NOT_FOUND Record does not exist or belongs to another tenant
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.