API & webhooks

Yes, it connects to your system

A REST API over everything in the product and signed webhooks for everything that happens in it. Keys are made in Settings, scoped to one workspace, and revocable. No partner programme, no sales call, no waiting list.

METHODPATHWHAT IT DOES
GET/api/v1/candidatesEvery candidate, newest first. Paged.
GET/api/v1/candidates/{id}One candidate with their applications and current stage.
POST/api/v1/candidatesPut somebody in the database. Refuses a duplicate email and hands back the id of the record that already exists.· needs a write key
GET/api/v1/jobsOpen and closed roles, with the client each belongs to.
POST/api/v1/jobsOpen a role against a client. The client is checked against your workspace, and an end client is refused as the company to invoice.· needs a write key
GET/api/v1/clientsThe client accounts.
POST/api/v1/clientsAdd a client account. Refuses a duplicate company name and hands back the id of the record that already exists.· needs a write key
GET/api/v1/applicationsWho is on which role, and what stage they are at.
GET/api/v1/placementsConfirmed placements, with start dates.
GET/api/v1/invoicesInvoices with their status and amounts. Needs a key that may see money.
GET/api/v1/webhooksThe subscriptions this workspace has.
POST/api/v1/webhooksSubscribe a URL to one or more events.· needs a write key
DELETE/api/v1/webhooks/{id}Stop a subscription.· needs a write key

What you need to know before you write a line

The questions everybody asks in the first minute.

Authentication

A key per workspace, sent as `Authorization: Bearer sk_live_…`. Keys are made in Settings → API & webhooks and shown once. Two scopes: read, and write which implies read. There is no third level worth the screen it would need.

What you can and cannot write

You can create a candidate, a client and a job, and manage webhooks. You cannot move somebody to Offered, raise an invoice or place anybody. The three you can create are the ones an integration actually asks for — a form on your own site, a spreadsheet in Zapier, an HRMS pushing applicants, a client system opening a requirement — and each is “put this record in the database”, where the rules are ours and a script cannot get them wrong. The rest is judgement, and an API that lets a script move somebody to Offered is an API that will move somebody to Offered by accident. Commercial terms are excluded on purpose: fee model, payment terms and the vendor chain decide who gets billed what, and no integration has a reason to set them.

Rate limit

600 requests a minute per key. Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`, so a client can slow down before it is told to; a refusal is a 429 with `Retry-After`. The limit exists to stop a loop with no delay in it, which is the commonest integration bug there is.

Paging

`?page=` and `?pageSize=` on every list, with `?q=` for a text search. Responses carry `rows`, `total`, `page` and `pageSize` — the same shape everywhere, so a client written against one list works against all of them.

Errors

One shape: `{ "error": "…" }` with a real HTTP status, and `detail` where there is something useful to say. Never a 200 with a failure inside it.

Webhooks

Delivered as a POST with a JSON body and an `X-StaffingCRM-Signature` header: `sha256(timestamp.body)` with your endpoint's secret, the same scheme Stripe and GitHub use. Verify it before you trust the body. Failed deliveries are retried with a backoff, and the attempts are visible in Settings.

Zapier and Make

Both work today with nothing extra to install: a webhook subscription is the trigger, and the REST endpoints are the actions. There is no separate connector because there is nothing for one to do.

Versioning

The path carries the version. `v1` will not change shape under you — a field may be added, never removed or renamed. Anything that has to break becomes `v2` beside it.

Everything you can subscribe to

Deliberately not every event in the product. A feed full of noise is one nobody keeps subscribed to.

candidate.appliedSomebody applied through the careers page
candidates.importedA batch of candidates was imported
application.submittedA candidate was submitted to a client
application.stage_changedAn application moved stage
application.rejectedAn application was rejected
interview.scheduledAn interview was booked
interview.feedbackInterview feedback was recorded
client.decisionA client shortlisted or rejected in the portal
deal.wonA deal was won
deal.lostA deal was lost
placement.extendedA placement was extended
timesheet.submittedA timesheet was sent to the client
timesheet.submitted_by_contractorA contractor sent their hours
invoice.raised_from_timesheetsAn invoice was raised from timesheets

A first request

curl https://your-instance.example/api/v1/candidates \
  -H "Authorization: Bearer sk_live_…"

# create one
curl https://your-instance.example/api/v1/candidates \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{"name":"Priya Sharma","email":"priya@example.in","skills":["React"]}'

The host is your own instance — this product is delivered, not shared, so there is no api.staffingcrm.com holding everybody’s data behind one door.