Crewmano · Developers

Crewmano API

Read your customers, jobs, quotes, invoices, payments and visits, add customers, jobs and requests, and get webhooks the moment something happens. Works with Zapier, Make, your website, or your own code.

Start

  1. In Crewmano, an owner opens Settings → API & webhooks and makes a key (read only, or read and write).
  2. Copy it: it's shown once. Treat it like a password.
  3. Try it:
curl https://crewmano.com/api/v1/me \
  -H "Authorization: Bearer cm_live_your_key"

Base address: https://crewmano.com/api/v1. Everything is JSON. Money is in cents (12500 = $125.00, in your business's currency). Times are ISO 8601 in UTC.

Keys

Send the key on every request as Authorization: Bearer cm_live_…. A key belongs to one business and sees only that business. Read-only keys can't add anything. Revoke a key in Settings and it stops working at once.

Reading

GET https://crewmano.com/api/v1/jobs?limit=25
GET https://crewmano.com/api/v1/jobs?changed_since=2026-10-01T00:00:00Z
GET https://crewmano.com/api/v1/invoices?status=open&customer_id=…
GET https://crewmano.com/api/v1/jobs/{id}

Lists come newest first: { "object": "list", "data": [...], "has_more": true, "next_cursor": "…" }. For the next page, send starting_after=<next_cursor>. limit is 1–100 (default 25). To keep another system in step, poll with changed_since (or use webhooks).

Filters: status, job_id, customer_id, invoice_id (where the record has them).

Adding

With a read-and-write key you can add customers, jobs and requests (leads). Requests land in Work → Requests and notify the team, like a request from your online form.

curl -X POST https://crewmano.com/api/v1/requests \
  -H "Authorization: Bearer cm_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "contact": { "name": "Sam Fox", "phone": "312-555-0199" },
        "description": "Deep clean, 3 bed 2 bath", "source": "Website" }'

You get the new record back with 201.

Records

PathFieldsAdd (POST)
/customersid, name, company_name, email, phone, language, properties[], archived, created_at, updated_at{ "name": "Pat Lee", "email": "pat@example.com", "phone": "312-555-0100", "property": { "address_line1": "9 Elm St", "city": "Chicago", "region": "IL", "postal_code": "60625" } }
/jobsid, number, title, description, status, customer_id, property_id, start_date, target_end_date, sold_at, completed_at, lost_at, archived{ "title": "Kitchen repaint", "customer_id": "…" } or { "title": "…", "customer": { "name": "…" }, "property": { "address_line1": "…" } }
/requestsid, status, contact {name, phone, email, address}, description, source, customer_id, job_id, received_at{ "contact": { "name": "Sam Fox", "phone": "312-555-0199" }, "description": "Deep clean, 3 bed", "source": "Website" }
/quotesid, job_id, number, version, title, status, subtotal_cents, tax_cents, total_cents, deposit_percent, valid_until, sent_at, approved_at, approved_by_name, lines[]—
/invoicesid, job_id, quote_id, number, kind, status, issue_date, due_date, total_cents, amount_paid_cents, amount_credited_cents, balance_cents, lines[]—
/paymentsid, invoice_id, amount_cents, tip_cents, card_fee_cents, paid_on, method, reference, voided_at—
/visitsid, job_id, purpose, title, starts_at, ends_at, status, en_route_at, arrived_at, completed_at, crew[]—

Webhooks

Add an endpoint in Settings → API & webhooks (or with the API, below). Crewmano sends a POST with JSON to your https:// address within about a minute of each event:

{
  "id": "…",
  "object": "event",
  "type": "invoice.paid",
  "created_at": "2026-10-14T15:04:05Z",
  "business_id": "…",
  "data": { "object": { "object": "invoice", "id": "…", "status": "paid", ... } }
}

data.object is the record as it is when sent, in the same shape as the API. Events:

customer.created · customer.updated · request.created · job.created · job.status_changed · quote.sent · quote.approved · quote.declined · invoice.issued · invoice.paid · invoice.voided · payment.recorded · payment.voided · visit.scheduled · visit.on_the_way · visit.completed · visit.cancelled

Answer with any 2xx within 10 seconds. Otherwise it's tried again after 1 min, 5 min, 30 min, 2 h, 6 h and 12 h. An endpoint that keeps failing for 3 days is turned off (turn it back on in Settings). Events can arrive more than once or out of order: use the event id to skip ones you've seen.

Checking signatures

Each request has a Crewmano-Signature header: t=<unix time>,v1=<signature>. The signature is an HMAC-SHA256 of `${t}.${raw body}` with your endpoint's signing secret (shown once when you add it). In Node:

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const expected = createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  const ok = expected.length === parts.v1?.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
  const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) < 300; // within 5 minutes
  return ok && fresh;
}

Zapier & other tools

Tools can subscribe and unsubscribe themselves with a read-and-write key:

POST   https://crewmano.com/api/v1/webhooks      { "url": "https://hooks.zapier.com/…", "events": ["request.created"] }
GET    https://crewmano.com/api/v1/webhooks      (the webhooks made with this key)
DELETE https://crewmano.com/api/v1/webhooks/{id}

The response to POST includes the signing secret, once. To test a key, call GET /me.

Errors & limits

Errors look like { "error": { "code": "invalid_request", "message": "…" } } with status 400 / 422 (bad input), 401 (key missing or revoked), 403 (read-only key), 404, or 429. Each key can make 120 requests a minute; after a 429, wait for Retry-After seconds.

Questions? Ask from Help & feedback inside Crewmano.