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 -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 -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:
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.
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.
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.
/meThe authenticated key's tenant and scopes./leadsList leads for the tenant./leads/:idRetrieve a single lead./leadsCreate a lead./leads/:idUpdate a lead./contactsList contacts for the tenant./contacts/:idRetrieve a single contact./contactsCreate a contact./contacts/:idUpdate a contact./jobsList jobs for the tenant./jobs/:idRetrieve a single job./jobsCreate a job./jobs/:idUpdate a job./estimatesList estimates for the tenant./estimates/:idRetrieve a single estimate./estimatesCreate an estimate./estimates/:idUpdate an estimate./invoicesList invoices for the tenant./invoices/:idRetrieve a single invoice./invoicesCreate an invoice./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:
{
"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
| Status | Meaning |
|---|---|
| 200 | Request succeeded. |
| 401 | Missing or invalid API key. |
| 403 | The key is valid but lacks the required scope. |
| 429 | Rate 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:
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:
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:
Payload shape
Each delivery is JSON carrying the event name and the affected object. An invoice.paid delivery, for example, looks like this:
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.