API v1
Build on worksorder.ai.
REST over HTTPS, JSON in and out. Raise work orders from your own systems, follow them through the pipeline, and pull quotes, invoices and certificates back — with signed webhooks pushing events to you as they happen. Verified end-to-end against production.
Base URL: https://app.worksorder.ai/api/v1
Authentication
Create a key in Settings → API in the portal (shown once — only a hash is stored). Send it on every request:
Authorization: Bearer wo_live_…
- Keys are firm-scoped and revocable at any time.
- Scopes default to everything; granular scopes (
work_orders:read,invoices:*, …) can be set per key. 401— missing, unknown or revoked key ·403— key lacks the scope.
Conventions
- Pagination:
?page=1&per_page=50(max 100). List responses:{ items, page, per_page, count }. - Times are UTC ISO 8601. Dates are
YYYY-MM-DD. - Errors:
{ "error": "message" }with a 4xx/5xx status. - Download links (
download_url,pdf_url) are signed and expire in ~10 minutes — fetch, don't store.
Work orders
| Method | Path | Notes |
|---|---|---|
| GET | /work-orders | Filters: status (csv), stage, updated_since, q (ref/title) |
| POST | /work-orders | Raise a job — see body below. external_ref dedupes, so retries are safe |
| GET | /work-orders/{id} | Full detail: visits, quotes, invoices, certificates (with downloads), events |
| POST | /work-orders/{id}/status | { "to": "scheduling", "reason": "…" } — portal transition rules apply |
| POST | /work-orders/{id}/notes | { "text": "…" } |
Raise a work order
POST /work-orders
{
"title": "EICR — 3 bed flat",
"job_type": "eicr",
"urgency": "routine",
"customer_id": "uuid (optional)",
"property_id": "uuid — or supply property:",
"property": { "address_line1": "14 Test St", "town": "Southend", "postcode": "SS1 1AA" },
"tenant": { "name": "…", "phone": "07…", "email": "…" },
"required_by": "2026-09-30",
"po_number": "PO-123",
"external_ref": "your-system-id-456"
}job_type values: eicr, eic, minor_works, gas_safety, boiler_service, epc, pat, fire_alarm, emergency_lighting, legionella, remedial_electrical, plumbing, heating, general_maintenance, other.
Pipeline stage values: attention, intake, booking, booked, on_site, sign_off, release, invoiced.
Customers
GET /customers (?q=name) · POST /customers · GET /customers/{id} (with properties and contacts)
Engineers & diary
GET /engineers · GET /visits?from=YYYY-MM-DD&to=YYYY-MM-DD
Money
GET /quotes (?status=sent,accepted) · GET /quotes/{id} (line items)
GET /invoices (?status=…) · GET /invoices/{id} (line items + signed pdf_url)
Compliance
GET /certificates (?kind=eicr&expires_before=YYYY-MM-DD)
GET /certificates/{id} — includes C1/C2/C3/FI observations and a signed download_url
Webhooks
GET /webhooks — list subscriptions · POST /webhooks — { "url": "https://…", "events": ["status_changed"] } (empty events = all; response includes secret once) · DELETE /webhooks/{id} — unsubscribe.
Deliveries are batches of work-order timeline events, POSTed to your URL within ~5 minutes:
{ "events": [ { "id": "…", "work_order_ref": "WO-000123", "kind": "status_changed",
"from_status": "booked", "to_status": "completed", "created_at": "…" } ] }Verify the wo-signature: sha256=<hex> header — an HMAC-SHA256 of the raw body with your webhook secret (compare case-insensitively, in constant time). Handlers must be idempotent; failed deliveries retry on the next run, and 10 consecutive failures deactivate the subscription (re-create to resume).
Get a key
API keys are self-served from Settings → API in the portal. Not a customer yet? Get in touch.
Put the office on rails
See a works order book itself.
A 20-minute demo: drop in a real works order, watch the agent text the tenant, and follow the job to a sent invoice.