Onboardora API, as shipped in the download
Onboardora REST API, Webhooks & MCP (1.1)
Onboardora ships a JSON REST API, signed webhooks and an MCP endpoint, so your onboarding plugs into Power Automate, Zapier, n8n, AI agents, or your own code.
Authentication
Create a key in Settings → API & Webhooks (admin). A key belongs to a user and can do exactly what that user can do in the app — and it carries its own scope on top of that. Send it on every call:
Authorization: Bearer apk_...
The key is shown once at creation and stored hashed. Revoke any time.
| Scope | What it can do |
|---|---|
| Read and write | Everything its owner's role allows |
| Read only | GET/HEAD/OPTIONS only. A write is refused with 403 forbidden at the door, and the MCP endpoint hides write tools entirely |
Endpoints
| Method & path | What it does | |||
|---|---|---|---|---|
GET /api/ping | Verify a key | |||
GET /api/starters | Everyone with a plan, with progress; `?status=planned | active | complete | cancelled` |
GET /api/starters/{id} | One plan: every task with its derived due date, plus documents and equipment. Since 1.1 a starter carries tags (a list, e.g. ["remote"]) | |||
GET /api/tasks/late | Every overdue task across everybody, worst first — the chase list | |||
POST /api/tasks/{id}/complete | Tick a task. Idempotent | |||
GET /api/checklists | The checklist templates and how many tasks each carries | |||
GET /api/openapi.json | OpenAPI 3 spec (no auth) |
Two things are derived, never stored
A task carries an offset in days from the starter's start date (−7 is a week before they arrive, 0 is their first morning). The due date is computed on every read, and late means not done and that date has passed. So moving a start date moves the whole plan, and an integration reading this API and a manager reading the timeline cannot disagree about whether the laptop was ordered.
Why creating a starter is not an endpoint
A starter is a person with a start date, a manager, and a plan copied from a checklist. Creating one from an integration would mean the API also choosing the checklist — and a plan silently built from the wrong template is worse than no plan: the tasks look real, somebody works them, and the thing that was actually needed was never on the list. That decision stays with a human in the app.
What the API will not tell you
personal_email — the address somebody applied with — is never serialized. It exists in the database only so a reminder sent before their work mailbox is created does not bounce. It is also redacted out of every JSON backup.
Webhooks
Add an endpoint in Settings → API & Webhooks, choose events, and Onboardora POSTs JSON signed with your per-endpoint secret:
X-Onboardora-Event: task.completed
X-Onboardora-Signature: sha256=<hmac_sha256(body, secret)>
| Event | Fires when |
|---|---|
starter.created | somebody is added and their plan is built |
task.completed | a task is ticked |
task.reopened | a task is un-ticked |
document.received | a starter uploads a document or acknowledges one on their own page (1.1) |
Verify the signature before trusting a payload.
A chat message instead of the document (1.1)
Each endpoint has a What to send setting. Signed JSON is everything above. A chat message posts one readable line in the shape a Slack incoming webhook takes, with the same event header:
{"text": "Onboarding task done for Priya Raman: Order the laptop and second monitor"}
The line names the starter and the task or document — never an email address, a note, a file or anything the starter typed. Chat tools that expect another shape need their own adapter.
People for Trainora (1.1)
GET /starters/trainora.csv (signed in, any role that can see starters; not an API-key route) downloads your starters as name,email,role,started_on — the columns Trainora's Import people reads as they are (role becomes Trainora's team/role tag). Canceled and erased starters are left out. The two products never call each other. Delivery is best-effort with a short timeout: a slow receiver must never block somebody ticking a task off a list. One attempt per event; the delivery log on the admin page answers "did it fire?".
Limits, honestly
One attempt per webhook, no retry queue. Lists cap at 200 rows. There is no rate limiter on /api/* beyond your own web server's — this is a single-tenant app on your box, and the key is the control.
MCP — the agent door
Onboardora speaks the Model Context Protocol at POST /mcp, so Claude, ChatGPT agents, n8n's AI nodes or your own code can use Onboardora instead of merely reading it. It is the same product underneath: the same bearer key, the same roles, the same derived dates.
Transport is streamable HTTP, stateless: one JSON-RPC 2.0 request in, one JSON response out.
curl -s -X POST https://hr.example.com/mcp \
-H "Authorization: Bearer apk_xxxx" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"my-agent","version":"1"}}}'
Then tools/list to discover, tools/call to act:
curl -s -X POST https://hr.example.com/mcp \
-H "Authorization: Bearer apk_xxxx" -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"whats_late","arguments":{}}}'
Connect it to an assistant
Mint the key in the app first: Settings → 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 address | https://hr.example.com/mcp |
| The key | header Authorization: Bearer apk_xxxx |
| The transport | MCP 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 onboardora https://hr.example.com/mcp \
--header "Authorization: Bearer apk_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": "onboardora",
"server_url": "https://hr.example.com/mcp",
"authorization": "apk_xxxx",
"require_approval": "never"
}
**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"*. Pasted as-is, a block written for another client 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://hr.example.com/mcp \
-H "Authorization: Bearer apk_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: Onboardora implements the protocol, not an integration with a particular vendor.
Tools
| Tool | Writes? | What it does |
|---|---|---|
list_starters | no | Starters with progress (max 100); optional status filter |
get_plan | no | One starter's whole plan, every task with its derived due date and late flag |
whats_late | no | Every overdue task across everybody, worst first |
list_checklists | no | The checklist templates and their task counts |
complete_task | yes | Ticks one task. Idempotent — already-done stays done, with the first name and time |
reassign_task | yes | Hands a task to manager / hr / it / buddy / starter / a named person |
Each write tool checks the key holder's role as the screen does — complete_task needs a role that may tick tasks, reassign_task one that may edit them — so a viewer's key is refused.
There is deliberately no create-starter tool, no delete tool and no settings tool. An agent can tell you what is late, tick things off and move work to the right person. It cannot invent a starter, erase a record, or change who gets chased.
Operational endpoints
| Endpoint | Guard | What it does |
|---|---|---|
GET /backup/scheduled?t=<token> | ops token (Backup & restore) | Writes a redacted JSON backup into data/backups/, keeps the newest 14 |
GET /calendar.ics?t=<token> | its own feed token (Settings) | Read-only iCal feed of start dates and open tasks |
GET /starters/{id}/gdpr.json | admin session | Subject-access export for one person, with their whole plan |
GET /healthz | none | Liveness, app name and version |
cron/remind.php is CLI-only and chases the owner of a task that is due. It is not reachable over HTTP — a reminder runner behind a URL is an unauthenticated "mail everybody" button. Each task is reminded once, ever, and the stamp is written only when a send actually succeeded.
Connect it to Zapier, Make or n8n
Onboardora talks to other tools without a plugin: it sends a webhook (an HTTPS message) the moment something happens, and it accepts calls to the API above. Zapier, Make and n8n can use both.
Before you start
- In Onboardora, open Settings → API & Webhooks. Mint an API key (choose Read only if the other tool only reads), and add a webhook when you have its URL (below).
- Use your install's full address, for example
https://onboardora.yourcompany.com.
When something happens in Onboardora: send it to another tool
- In the other tool, create the trigger that gives you a URL to receive webhooks: - Zapier: the "Webhooks by Zapier" app, trigger Catch Hook (Webhooks by Zapier needs a paid Zapier plan). - Make: module Webhooks → Custom webhook. - n8n: the Webhook node.
- Copy that URL into Onboardora under Settings → API & Webhooks, and choose the event or events below.
- Make the event happen once in Onboardora, so the other tool receives a sample and you can map its fields.
| Event | Fires when |
|---|---|
starter.created | Somebody is added and their plan is built |
task.completed | A task is ticked |
task.reopened | A task is un-ticked |
document.received | A starter uploads a document or acknowledges one on their own page (1.1) |
Every delivery carries X-Onboardora-Signature, an HMAC-SHA256 signature over the raw body. Zapier and Make accept the delivery without checking it, so keep the webhook URL private. In n8n you can check it in a Code node before using the data.
When something happens elsewhere: act in Onboardora
- Zapier: "Webhooks by Zapier", action POST (or Custom Request).
- Make: module HTTP → Make a request.
- n8n: the HTTP Request node.
Send the request to your install's address plus the path from the Endpoints table above, with the header Authorization: Bearer <your API key> and a JSON body. A read-only key cannot write.
The endpoints that change something:
POST /api/tasks/{id}/complete: Tick a task. Idempotent
Example recipes
- starter.created → a row in Google Sheets: webhook trigger on
starter.created(Somebody is added and their plan is built), then the Google Sheets "Create row" action with the fields you want. - task.completed → a message in Slack or Teams: webhook trigger on
task.completed(A task is ticked), then the Slack or Microsoft Teams "Send message" action.
These recipes use only the events and endpoints documented above. We have not run them inside Zapier, Make or n8n ourselves; Onboardora's own automated tests check its API.