worksorder.ai

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

MethodPathNotes
GET/work-ordersFilters: status (csv), stage, updated_since, q (ref/title)
POST/work-ordersRaise 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.