Ownware
Home›Trainora›API
Trainora · API

Trainora API, as shipped in the download

Trainora — API, webhooks and the agent door

Everything here runs on your own server. Nothing is proxied, and no request leaves your network unless you point it somewhere.

Authentication

Mint a key in the app: API & agents → API keys. Send it as a bearer token:

Authorization: Bearer trk_...

Each key has a scope:

ScopeWhat it can do
Read and writeEverything the key's user could do in the browser.
Read onlyGET, HEAD and OPTIONS. Anything else is refused with 403 forbidden at the door — not per route, because the route that forgets is the one that leaks.

A key can never exceed its user's role. A read key belonging to an admin still cannot write.

Endpoints

MethodPathWhat it does
GET/api/pingVerify a key; returns its scope.
GET/api/matrixThe whole grid: every person against every competency, with each cell's state and expiry date.
GET/api/attentionExpired, never recorded and expiring — worst first. `?state=expired\none\expiring`.
GET/api/subjectsThe register of people.
GET/api/subjects/{id}One person and what they hold.
GET/api/requirementsThe competencies tracked, with their validity in months.
GET/api/recordsRecords, newest first. ?subject_id= ?requirement_id=
POST/api/recordsRecord a completion.
GET/api/openapi.jsonOpenAPI 3 description of all of the above.
GET/healthzLiveness, no key required.

Record a completion

curl -X POST https://trainora.example.com/api/records \
  -H "Authorization: Bearer trk_xxxx" -H "Content-Type: application/json" \
  -d '{"subject_id": 4, "requirement_id": 2, "achieved_on": "2026-08-01"}'

You cannot set the expiry. It is computed from the competency's own validity, because a client that could choose its own expiry date would make the whole register meaningless. The same call refuses, with a sentence, when:

  • the date is in the future, or is not a real date;
  • the competency does not apply to that person's team or role;
  • the person or the competency is archived, or does not exist.

These are the same refusals the grid shows and the MCP tool returns, because all four doors call one function.

Webhooks

record.created fires whenever a completion is recorded, from any door. Configure receivers under API & agents. One delivery attempt per event, with a log — that is a log, not a retry queue.

Limits, honestly

  • List endpoints cap at 500 rows. This is a compliance register for one organisation, not a warehouse.
  • There is no endpoint that creates a person, a competency or a session. Those are deliberate screens with their own guards; the API is for reading the register and recording completions.
  • No endpoint issues a certificate. Trainora does not issue certificates — see the hand-off below.

The certificate hand-off

GET /export/certora-recipients.csv emits exactly name,course,email, one row per person who currently holds something that has not expired. Those three header names are [Certora](../29-certora)'s recipient-import contract; import the file there and issue from it.

It is a file hand-off on purpose: no request leaves this box, and Trainora never learns whether a certificate was issued. If you want that link automated, drive Certora's own API with this file.


MCP — the agent door

Trainora speaks the Model Context Protocol at POST /mcp, so Claude, ChatGPT agents, n8n's AI nodes or your own code can use the register rather than merely read it. It is the same product underneath: the same bearer key, the same roles, and the same expiry engine. An agent cannot do anything its key's user could not do in the browser — and it cannot compute an expiry date, ever.

Transport is streamable HTTP, stateless: one JSON-RPC 2.0 request in, one JSON response out.

Connect it to an assistant

Mint the key in the app first: API & agents → API keys. Choose Read only when the assistant should answer questions but never change anything — the endpoint then lists only the read tools and refuses the rest by name, so a careless prompt cannot write. Full access behaves as before.

Every client needs the same three facts, and nothing in the handshake is vendor-specific:

The addresshttps://trainora.example.com/mcp
The keyheader Authorization: Bearer trk_xxxx
The transportMCP over streamable HTTP, stateless

Claude — one command, or the same URL and header as a custom connector in the desktop and web apps:

claude mcp add --transport http trainora https://trainora.example.com/mcp \
  --header "Authorization: Bearer trk_xxxx"

ChatGPT and the OpenAI API — one entry in the Responses API's tools array (in ChatGPT itself, the same URL and key go in as a connector):

{
  "type": "mcp",
  "server_label": "trainora",
  "server_url": "https://trainora.example.com/mcp",
  "authorization": "trk_xxxx",
  "require_approval": "never"
}

Own Your AI reads a list of servers in this shape:

{
  "mcpServers": [
    { "id": "trainora", "name": "Trainora", "url": "https://trainora.example.com/mcp",
      "token": "trk_xxxx", "enabled": true }
  ]
}

**Every other client spells the same three facts differently — copy the shape from its own documentation, not from here.** VS Code is the clearest example of why: its configuration reference (read 6 September 2026) puts servers in .vscode/mcp.json under a "servers" object — *"an object that maps server names to their configurations"* — not an mcpServers array. Pasted as-is, the block above will not load there. The id, the URL and the token are what travel; the JSON around them belongs to whichever client you are configuring.

A local model, n8n, or your own code — n8n's MCP Client node takes the URL and the same Authorization: Bearer header; a model running on your own machine reaches it through any MCP client, so nothing leaves your network at all. Writing it yourself is one POST of JSON-RPC 2.0:

curl -X POST https://trainora.example.com/mcp \
  -H "Authorization: Bearer trk_xxxx" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

Any client that speaks MCP's streamable-HTTP transport works, including ones that do not exist yet: Trainora implements the protocol, not an integration with a particular vendor.

Tools

ToolReads or writesWhat it is for
matrixreadsThe whole grid, with coverage.
attentionreadsWhat is expired, missing or expiring — the list to work down.
subject_detailreadsOne person: what they hold and what they are missing.
requirementsreadsThe competencies and how long each stays valid.
record_completionwritesRecord that somebody met a competency on a date.

Every read tool is annotated readOnly, which is what makes a read-scoped key see only those four. A read key calling record_completion by name gets a refusal naming the tool, not a silent failure.

← Back to Trainora · Manual · Quickstart · Test run

Affiliate program
Recommend tools people own — earn 35% on every sale. 90-day tracking, instant delivery, payouts by Lemon Squeezy.
Become an affiliate →