https://maintainr-backend-production.up.railway.app/api/v1/integrationAuthenticate with
Authorization: Bearer mk_test_… or mk_live_….
Create keys and webhooks in the app under Settings → Integrations (org admins).
Team provisioning
Anyone who taps Start form or Review in your app must exist in the same Maintainr organization. Use the same work email you send as actorEmail or viewerEmail.
- Invite users in Maintainr (Users → Invite), or
- Enable auto-provision users in Settings → Integrations (optional JIT; restrict allowed email domains).
Primary flow: Start form
POST /tasks/start is the recommended entry point. It creates or reuses a task by externalTaskId, opens a fill session for the technician, and returns links.open.
Open links.open with the system (not an embedded webview). If Maintainr is installed, the app opens; otherwise the browser. Response includes ownerEmail and canResume — gate Continue with canResume && ownerEmail === currentTechEmail. A different actorEmail on start reassigns the task (handoff).
Prefill system keys
Use these canonical keys (and field ids from GET /forms/{id} for custom fields):
| Key | Purpose |
|---|---|
_jobName | Job / work order display name |
_repairOrder | Repair order number |
_customerName | Customer name |
On create only, aliases such as RO, _roNumber, and Customer Name are also copied into these keys when unset.
Poll by externalTaskId
GET /tasks?externalTaskId=RO-1042-form-1 returns the current task (status, ownerEmail, canResume). Use when a webhook is late. Returns TASK_NOT_FOUND if no task exists.
Handoff: PATCH /tasks/{submissionId}/assign with the coworker’s email, or start again with their actorEmail.
Review & PDF
Review in Maintainr
POST /tasks/{submissionId}/view-session with { "viewerEmail": "manager@dealership.com" }. Open links.view for read-focused access.
Download PDF
GET /submissions/{id}/pdf — call from your server with the API key and stream the PDF to your app.
Share customer report
POST /submissions/{id}/share-link — store links.publicReport on the job in your system.
Partner UI states
| Your UI | Maintainr signal |
|---|---|
| Not started | No task for externalTaskId |
| In progress | canResume: true (IN_PROGRESS, RETURNED, IN_RELAY) |
| Under review | Status UNDER_REVIEW or webhook submission.under_review |
| Completed | Status COMPLETED or webhook submission.completed |
| Returned | Webhook submission.returned |
| Customer quote | Webhook quote.customer_responded |
Buttons by state
| State | Start form | Review | Share | |
|---|---|---|---|---|
| Not started | POST /tasks/start | — | — | — |
| In progress | POST /tasks/start | view-session | — | — |
| Under review | — | view-session | — | — |
| Completed | — | view-session | GET pdf | share-link |
Bulletin check (Arrival)
POST /bulletins/check (scope forms:read) matches org-subscribed active bulletins for a unit — same logic as Maintainr’s in-app TSB step. Soft-fail on your side if Maintainr is down; empty matches is not an error.
Response is the standard envelope: use data.unit and data.matches. Enums: target = unit|engine; severity = advisory|bulletin|recall; certainty = potential|confirmed. Sorted recalls first. Also: GET /bulletins lists active subscribed bulletins.
Webhooks
Configure endpoints in Settings → Integrations. Verify X-Maintainr-Signature (HMAC-SHA256 of the raw body with your webhook secret). Deliveries are retried automatically.
Events: task.created, task.reassigned, submission.under_review, submission.completed, submission.returned, quote.customer_responded.
Payload data includes submissionId, status, externalRepairOrderId, externalTaskId, assignedTo, ownerEmail, canResume, links, and completedBy on lifecycle events.
Error codes
| Code | Meaning |
|---|---|
ASSIGNEE_NOT_FOUND | Email not in org; invite user or enable JIT |
ACTOR_REQUIRED | Missing actorEmail and actorId |
EMAIL_DOMAIN_NOT_ALLOWED | JIT on but domain not allowed |
FORM_NOT_FOUND | Invalid formId for org |
TASK_NOT_FOUND | Invalid submission id |
IDEMPOTENCY_CONFLICT | Same idempotency key, different body |
INSUFFICIENT_SCOPE | API key missing required scope |
API key scopes
| Scope | Access |
|---|---|
forms:read | List forms and users |
tasks:read | List/query tasks, view-session |
tasks:write | start, assign, fill-session, share-link |
submissions:read | Get submission, events, PDF |
New keys include all default scopes. OpenAPI: GET /v1/integration/openapi.json (requires API key).
Ready to connect?
Generate a test key in the app, point webhooks at your staging URL, and use the curl example above with your form IDs.