API & webhooks
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.
| METHOD | PATH | WHAT IT DOES |
|---|---|---|
| GET | /api/v1/candidates | Every candidate, newest first. Paged. |
| GET | /api/v1/candidates/{id} | One candidate with their applications and current stage. |
| POST | /api/v1/candidates | Put 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/jobs | Open and closed roles, with the department each belongs to. |
| POST | /api/v1/jobs | Open a role in a department. The department is checked against your workspace before the role is made.· needs a write key |
| GET | /api/v1/clients | The departments you hire for. |
| POST | /api/v1/clients | Add a department. Refuses a duplicate name and hands back the id of the record that already exists.· needs a write key |
| GET | /api/v1/applications | Who is on which role, and what stage they are at. |
| GET | /api/v1/placements | Confirmed hires, with start dates. |
| GET | /api/v1/invoices | Billing records, kept for integrations that already read them — see the note on names below. Needs a key that may see money. |
| GET | /api/v1/webhooks | The subscriptions this workspace has. |
| POST | /api/v1/webhooks | Subscribe a URL to one or more events.· needs a write key |
| DELETE | /api/v1/webhooks/{id} | Stop a subscription.· needs a write key |
The questions everybody asks in the first minute.
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.
The API was named before the product became a company's own hiring system, and a name in a `v1` path does not change under an integration. In the API, a `client` is a department and a `placement` is a hire: `/api/v1/clients` lists your departments and `/api/v1/placements` your hires. `/api/v1/invoices` comes from the same history and stays for anything already reading it.
You can create a candidate, a department and a role, and manage webhooks. You cannot move somebody to Offered, make an offer or record a hire. 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 department's own 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.
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.
`?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.
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.
Delivered as a POST with a JSON body and a `x-atsway-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.
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.
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.
Deliberately not every event in the product. A feed full of noise is one nobody keeps subscribed to.
candidate.appliedSomebody applied through the careers pagecandidates.importedA batch of applicants was importedapplication.submittedA candidate was shortlisted for a hiring managerapplication.stage_changedAn application moved stageapplication.rejectedAn application was rejectedinterview.scheduledAn interview was bookedinterview.feedbackInterview feedback was recordedclient.decisionA hiring manager said yes or no in their portalplacement.extendedA fixed-term hire was extendedcurl 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":"[email protected]","skills":["React"]}'The host is your own instance — this product is delivered, not shared, so there is no api.lsats.com holding everybody’s data behind one door.
Keys are made by an administrator under Settings, scoped to what the integration needs and revocable at any time.