Developer documentation

The Crewd API & Webhooks

Build on top of Crewd. Read and write your leads, contacts, jobs, estimates and invoices over a versioned REST API, and subscribe to signed webhook events so your systems react the moment something changes.

Base URL https://api.crewd.site/api/public/v1 Key + scope auth Signed webhooks

Overview

The Crewd public API is a versioned REST interface that lets you read and write the same records your team works with in the Crewd app. Every request is authenticated with an API key, scoped to your tenant, and rate-limited per key.

All endpoints are served under a single, versioned base URL and exchange JSON. Pair the API with webhooks to keep your own systems in sync in near real time instead of polling.

API keys are tenant-scoped. A key can only read and write data that belongs to the workspace it was created in.

Quickstart

Create a key in Crewd → Settings → API Keys, then make your first authenticated call. The example below lists jobs for the authenticated tenant.

curl
curl -H "Authorization: Bearer crewd_xxx" \
  https://api.crewd.site/api/public/v1/jobs

Confirm which tenant and scopes your key carries by calling GET /me:

curl
curl -H "x-api-key: crewd_xxx" \
  https://api.crewd.site/api/public/v1/me

Authentication

Every request must be authenticated with an API key. Create keys in Crewd → Settings → API Keys.

A key looks like crewd_xxxxxxxx… and is shown only once at creation. Store it somewhere safe (a secrets manager or environment variable) the moment you see it — Crewd cannot show it to you again. If a key is lost or leaked, revoke it and create a new one.

Treat API keys like passwords. Never embed them in client-side code, mobile apps, or public repositories — they grant access to your tenant's data.

Authenticating requests

Send your key on every request using either header — pick whichever fits your HTTP client:

http
Authorization: Bearer crewd_xxx
# — or —
x-api-key: crewd_xxx

Base URL

All endpoints live under the public, versioned base URL. Paths in this reference are relative to it.

base url
https://api.crewd.site/api/public/v1

Scopes

Each API key carries a set of scopes that determine what it can do. A request needs the scope that matches the operation — for example, reading jobs requires jobs:read, while creating or updating them requires jobs:write. If a key is missing the required scope, the request is rejected with 403 Forbidden.

leads:read leads:write jobs:read jobs:write invoices:write

Scopes follow the same resource:action pattern across resources (for example contacts:read, estimates:write). Grant a key only the scopes it needs. Call GET /me at any time to inspect the exact scopes attached to a key.

Rate limits

Requests are rate-limited per key. If you exceed the limit, requests are throttled — back off and retry. Spread bulk work over time and cache responses where you can rather than polling tightly; for change-driven flows, prefer webhooks over polling.

Endpoints

Public API v1. All paths are relative to https://api.crewd.site/api/public/v1 and every request must include a valid API key with the matching scope.

Identity
GET/meThe authenticated key's tenant and scopes.
Leads
GET/leadsList leads for the tenant.
GET/leads/:idRetrieve a single lead.
POST/leadsCreate a lead.
PUT/leads/:idUpdate a lead.
Contacts
GET/contactsList contacts for the tenant.
GET/contacts/:idRetrieve a single contact.
POST/contactsCreate a contact.
PUT/contacts/:idUpdate a contact.
Jobs
GET/jobsList jobs for the tenant.
GET/jobs/:idRetrieve a single job.
POST/jobsCreate a job.
PUT/jobs/:idUpdate a job.
Estimates
GET/estimatesList estimates for the tenant.
GET/estimates/:idRetrieve a single estimate.
POST/estimatesCreate an estimate.
PUT/estimates/:idUpdate an estimate.
Invoices
GET/invoicesList invoices for the tenant.
GET/invoices/:idRetrieve a single invoice.
POST/invoicesCreate an invoice.
PUT/invoices/:idUpdate an invoice.

Response shape

Every list endpoint is tenant-scoped — it returns only records that belong to the key's workspace — and wraps results in a data array:

json
{
  "data": [
    { "id": "job_1a2b3c", "name": "Hartwell Residence" },
    { "id": "job_4d5e6f", "name": "Maple St. Remodel" }
  ]
}

Field names and values above are illustrative. A 403 means your key lacks the scope for that operation; check it with GET /me.

Common status codes

StatusMeaning
200Request succeeded.
401Missing or invalid API key.
403The key is valid but lacks the required scope.
429Rate limit exceeded for this key — back off and retry.

Webhooks

Webhooks push events to your server the moment something happens in Crewd, so you don't have to poll. Configure them in Crewd → Settings → Webhooks.

A webhook subscription has two parts: a Payload URL (your HTTPS endpoint) and the events you want to receive. Crewd generates a unique signing secret per subscription — use it to verify that incoming requests genuinely came from Crewd.

For each subscribed event, Crewd sends an HTTP POST with a JSON event payload to your Payload URL.

Verifying signatures

Every delivery includes a signature header so you can confirm authenticity and integrity:

http
X-Crewd-Signature: sha256=<hex>

Where <hex> is HMAC-SHA256(secret, rawRequestBody). To verify, recompute the HMAC over the raw request body (the exact bytes received, before any JSON parsing) using your signing secret, then compare with a constant-time comparison:

node.js
const crypto = require('crypto');
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.header('X-Crewd-Signature')));

Capture the raw body before your framework parses it (for example with express.raw()). Reject any request whose signature does not match, and always compare in constant time to avoid timing attacks.

Event types

Subscribe to any of the events below, or use * to receive them all:

* lead.created lead.updated contact.created job.created job.updated estimate.created estimate.approved invoice.created invoice.sent invoice.paid payment.recorded changeOrder.approved bill.approved

Payload shape

Each delivery is JSON carrying the event name and the affected object. An invoice.paid delivery, for example, looks like this:

json
POST https://your-server.com/webhooks/crewd
X-Crewd-Signature: sha256=9f86d081884c...

{
  "event": "invoice.paid",
  "data": {
    "id": "inv_1a2b3c",
    "status": "paid"
  }
}

The exact fields inside data depend on the object type; the example values above are illustrative.

Delivery & retries

Respond with a 2xx status quickly to acknowledge receipt. Do any heavy processing asynchronously (enqueue the event and return right away) so the delivery doesn't time out. If your endpoint fails or does not return a 2xx, Crewd retries the delivery.

Because deliveries can be retried, make your handler idempotent — processing the same event twice should be safe.

Create an API key Back to crewd.site