Summary and recommendation
Workable exposes a REST API at https://www.workable.com/spi/v3 - note the spi path segment, not api;
requests to the wrong base path return 404 silently.
Authentication uses Bearer tokens generated per-account in Settings > Integrations > API Access Tokens, with scopes assigned at token creation time.
A token missing r_members scope cannot list members regardless of the generating user's admin status.
The API is recruitment-domain-focused: it covers members, jobs, candidates, and webhook subscriptions.
It is not a general HR or identity management API.
SCIM provisioning is not natively supported - user lifecycle automation must be built on top of the REST endpoints or handled manually.
For teams building an identity graph across systems, Workable member data (id, email, role, created_at) can serve as a node in that graph, but there is no delta or changed-since filter for members, requiring full-list fetches and local diffing on each sync cycle.
API quick reference
| Has user API | Yes |
| Auth method | Bearer token (API access token generated in Workable account settings) |
| Base URL | Official docs |
| SCIM available | No |
| SCIM plan required | Premier (Enterprise) |
Authentication
Auth method: Bearer token (API access token generated in Workable account settings)
Setup steps
- Log in to Workable as an account admin.
- Navigate to Settings > Integrations > API Access Tokens.
- Click 'Generate new token', provide a label, and select required scopes.
- Copy the generated token and include it in the Authorization header as: Authorization: Bearer
.
Required scopes
| Scope | Description | Required for |
|---|---|---|
| r_jobs | Read access to jobs and job-related data. | Listing jobs |
| r_candidates | Read access to candidates and applications. | Listing/reading candidates |
| w_candidates | Write access to create or update candidates. | Creating/updating candidates |
| r_members | Read access to account members (users). | Listing account members |
User object / data model
| Field | Type | Description | On create | On update | Notes |
|---|---|---|---|---|---|
| id | string | Unique identifier for the member. | system-assigned | immutable | Used as reference in other API calls. |
| name | string | Full name of the member. | required | optional | |
| string | Email address of the member. | required | optional | Must be unique within the account. | |
| role | string | Role of the member within the account (e.g., admin, member, recruiter). | optional | optional | Possible values depend on Workable's role definitions. |
| created_at | datetime (ISO 8601) | Timestamp when the member was created. | system-assigned | immutable | |
| avatar_url | string (URL) | URL to the member's avatar image. | optional | optional |
Core endpoints
List account members
- Method: GET
- URL:
https://www.workable.com/spi/v3/members - Watch out for: Requires r_members scope. Returns paginated results; follow 'paging.next' for subsequent pages.
Request example
GET /spi/v3/members
Authorization: Bearer <token>
Response example
{
"members": [
{"id": "abc123", "name": "Jane Doe", "email": "jane@example.com", "role": "admin"}
],
"paging": {"next": "https://..."}
}
Get a single member
- Method: GET
- URL:
https://www.workable.com/spi/v3/members/{id} - Watch out for: Returns 404 if member ID does not exist or token lacks r_members scope.
Request example
GET /spi/v3/members/abc123
Authorization: Bearer <token>
Response example
{
"id": "abc123",
"name": "Jane Doe",
"email": "jane@example.com",
"role": "admin",
"created_at": "2023-01-15T10:00:00Z"
}
List jobs
- Method: GET
- URL:
https://www.workable.com/spi/v3/jobs - Watch out for: Requires r_jobs scope. Use 'state' query param to filter by published/draft/closed.
Request example
GET /spi/v3/jobs?limit=50
Authorization: Bearer <token>
Response example
{
"jobs": [
{"id": "job1", "title": "Engineer", "state": "published"}
],
"paging": {"next": "https://..."}
}
List candidates
- Method: GET
- URL:
https://www.workable.com/spi/v3/candidates - Watch out for: Requires r_candidates scope. Candidate data may be large; use since_id for incremental sync.
Request example
GET /spi/v3/candidates?limit=20
Authorization: Bearer <token>
Response example
{
"candidates": [
{"id": "cand1", "name": "John Smith", "email": "john@example.com"}
],
"paging": {"next": "https://..."}
}
Create candidate
- Method: POST
- URL:
https://www.workable.com/spi/v3/jobs/{shortcode}/candidates - Watch out for: Requires w_candidates scope. Must post to a specific job shortcode; there is no global candidate creation endpoint.
Request example
POST /spi/v3/jobs/ENG001/candidates
Authorization: Bearer <token>
Content-Type: application/json
{"candidate":{"name":"Alice","email":"alice@example.com"}}
Response example
{
"candidate": {
"id": "cand99",
"name": "Alice",
"email": "alice@example.com",
"created_at": "2024-06-01T12:00:00Z"
}
}
Get candidate
- Method: GET
- URL:
https://www.workable.com/spi/v3/candidates/{id} - Watch out for: Requires r_candidates scope.
Request example
GET /spi/v3/candidates/cand99
Authorization: Bearer <token>
Response example
{
"candidate": {
"id": "cand99",
"name": "Alice",
"email": "alice@example.com"
}
}
List account subscriptions/events (webhooks)
- Method: GET
- URL:
https://www.workable.com/spi/v3/subscriptions - Watch out for: Webhook subscriptions are managed via the API itself. Requires appropriate token scope.
Request example
GET /spi/v3/subscriptions
Authorization: Bearer <token>
Response example
{
"subscriptions": [
{"id": "sub1", "event": "candidate_created", "target": "https://yourapp.com/hook"}
]
}
Create webhook subscription
- Method: POST
- URL:
https://www.workable.com/spi/v3/subscriptions - Watch out for: Target URL must be publicly accessible and respond with HTTP 200 to Workable's verification request.
Request example
POST /spi/v3/subscriptions
Authorization: Bearer <token>
Content-Type: application/json
{"subscription":{"event":"candidate_created","target":"https://yourapp.com/hook"}}
Response example
{
"subscription": {
"id": "sub2",
"event": "candidate_created",
"target": "https://yourapp.com/hook"
}
}
Rate limits, pagination, and events
- Rate limits: Workable enforces rate limits per API token. The documented limit is 10 requests per second. Exceeding the limit returns HTTP 429.
- Rate-limit headers: Yes
- Retry-After header: No
- Rate-limit notes: Workable docs mention rate-limit headers are returned on responses. Retry-After header behavior is not explicitly documented. Back off and retry on 429 responses.
- Pagination method: cursor
- Default page size: 10
- Max page size: 100
- Pagination pointer: since_id / limit
| Plan | Limit | Concurrent |
|---|---|---|
| All plans | 10 requests/second | 0 |
- Webhooks available: Yes
- Webhook notes: Workable supports webhook subscriptions managed via the /subscriptions API endpoint. Webhooks deliver POST payloads to a configured target URL when specified events occur.
- Alternative event strategy: Not documented
- Webhook events: candidate_created, candidate_updated, candidate_moved, candidate_hired, candidate_deleted, job_created, job_updated, job_published, job_closed
SCIM API status
- SCIM available: No
- SCIM version: 2.0
- Plan required: Premier (Enterprise)
- Endpoint: Not documented
Limitations:
- SCIM provisioning is not natively available in Workable as a built-in feature per official help documentation.
- SSO is available on Premier plan or as an add-on on Standard (annual billing).
- User provisioning via SCIM is not documented as a supported capability; IdP integrations (Okta, Entra, Google Workspace, OneLogin) are supported for SSO only.
- Automated user lifecycle management must be handled via the REST API or manual processes.
Common scenarios
Three integration patterns are well-supported by the current API surface.
For directory sync, call GET /spi/v3/members with r_members scope, paginate via paging.next cursor URLs (do not construct pagination URLs manually), and diff the full member list locally there is no incremental endpoint.
Map id, email, role, and created_at to your internal schema.
For real-time candidate events, register a webhook via POST /spi/v3/subscriptions targeting a publicly accessible HTTPS endpoint that responds HTTP 200 to Workable's verification ping.
Supported events include candidate_hired, candidate_moved, and job_closed, among others.
Workable does not document a webhook signing secret - implement idempotency checks or source IP validation on your receiver.
For programmatic candidate creation, first retrieve the target job's shortcode via GET /spi/v3/jobs, then POST to /spi/v3/jobs/{shortcode}/candidates with w_candidates scope.
There is no account-level candidate creation endpoint;
every candidate must be scoped to a specific job shortcode.
Sync Workable account members to an internal directory
- Generate an API token with r_members scope in Workable Settings > Integrations > API Access Tokens.
- Call GET /spi/v3/members to retrieve the first page of members.
- Follow 'paging.next' URLs until no next page is returned.
- Map each member's id, name, email, and role to your internal user schema.
- Store the last synced timestamp and use it to detect changes on subsequent runs (Workable does not provide a delta/changed-since filter for members).
Watch out for: There is no incremental/delta endpoint for members; full list must be fetched each sync cycle and diffed locally.
Receive real-time candidate events via webhook
- Expose a publicly accessible HTTPS endpoint on your application.
- Generate an API token with appropriate scope.
- POST to /spi/v3/subscriptions with the desired event type (e.g., candidate_hired) and your target URL.
- Workable will send a verification request to your endpoint; respond with HTTP 200.
- Handle incoming POST payloads from Workable at your target URL for each event.
Watch out for: Webhook payloads should be validated for authenticity; Workable does not document a signing secret mechanism - verify source IP or implement idempotency checks.
Programmatically add a candidate to a job
- Generate an API token with r_jobs and w_candidates scopes.
- Call GET /spi/v3/jobs to find the target job and note its shortcode.
- POST to /spi/v3/jobs/{shortcode}/candidates with the candidate's name, email, and any additional profile fields.
- Store the returned candidate id for future reference or status tracking.
Watch out for: Candidate creation is always scoped to a specific job via its shortcode; ensure the job is in 'published' or 'draft' state - closed jobs may reject new candidates.
Why building this yourself is a trap
The most common integration failure points with Workable's API are scope mismatches and pagination misuse. Tokens are immutable after generation - if a required scope was omitted, a new token must be generated. Pagination is cursor-based using paging.next from the response body;
constructing page-offset URLs manually will produce incorrect or duplicate results.
Rate limits are enforced at 10 requests per second per token with HTTP 429 on breach. Rate-limit headers are returned on responses, but a Retry-After header is not explicitly documented - implement exponential backoff on 429s rather than relying on header-driven retry timing.
For bulk member or candidate syncs, this ceiling is easy to hit without deliberate throttling.
The absence of native SCIM is the most significant architectural constraint: IdP integrations with Okta, Entra ID, Google Workspace, and OneLogin are supported for SSO only, not provisioning.
Any automated user deprovisioning flow must call the REST API or trigger a manual admin action - there is no SCIM DELETE or deactivation endpoint documented in the current API reference.
Automate Workable workflows without one-off scripts
Stitchflow builds and maintains end-to-end IT automation across your SaaS stack, including apps without APIs. Built for exactly how your company works, with human approvals where they matter.