The Recooty API lets your own website, HR system, or automation tool talk to
Recooty directly — no manual copy-paste, no CSV imports.
With it you can:
- Show your live jobs anywhere — on your own website, intranet, or a
partner's job board.
- Create and update jobs from another system — an ATS, an ERP, a
spreadsheet-driven workflow, or an automation tool like Power Automate,
Zapier, or Make.
- Receive applications from your own custom form and have them land in
Recooty exactly as if the candidate applied on your careers page.
- Pull candidate details — including screening answers and Recooty's AI
resume insights — into your own dashboards.
- Keep locations and departments in sync so jobs created by machine are
filed in the right place.
Everything lives on one page in Recooty: Settings → Integrations → API.
| Who this article is for. The first half ("Before you start", "Your two
| credentials", "Usage, credits and limits") is written for anyone. The
| per-API sections include the exact requests a developer needs. If you're
| handing this to a developer, they can start at Jobs API.
BEFORE YOU START
1. Your plan must include API Access
The API is a paid feature. If API Access isn't on your plan, you'll see an
upgrade prompt when you try to create a token, and every token-based API call
returns:
{ "message": "Feature not available to your plan" }
with HTTP status 402 Payment Required.
One important exception: the three job listing endpoints (the ones that use
your API key) are not plan-gated. They keep working on any plan, because
they're what powers careers page feeds and the jobs widget. Everything else —
creating jobs, receiving applications, reading candidates, locations,
departments — requires API Access.
2. You need the right permission
What you want to do: See the API page and your API key
Permission needed ... API and documentation
What you want to do: Create or revoke access tokens
Permission needed ... Tokens
If you don't have the Tokens permission, the page still shows your API key and
the full documentation, but instead of the token list you'll see:
| You do not have permission to create or revoke API tokens.
Ask a workspace admin to either grant you the permission or create the token
for you.
3. Find your credentials
Go to Settings → Integrations → API. The first card, API Key & Access Tokens,
holds both credentials.
YOUR TWO CREDENTIALS
Recooty uses two different credentials, and picking the wrong one is the
single most common integration mistake. Every endpoint in the app is tagged so
you can tell at a glance.
API key
Tag in the app ............ REQUIRES API KEY
What it is ................ One permanent key per workspace
Where it goes ............. Already built into the URL
Can read .................. Published (open) jobs only
Can write ................. Nothing
Expires ................... No
Safe in front-end code? ... Yes — it only exposes jobs you've already
published
Access token
Tag in the app ............ REQUIRES TOKEN
What it is ................ A personal access token you create yourself
Where it goes ............. Authorization: Bearer <ACCESS_TOKEN> header
Can read .................. Everything your plan allows
Can write ................. Jobs, applications, locations
Expires ................... 1 year after it's created
Safe in front-end code? ... No — never
The API key
Your API key is shown at the top of the API page. You don't need to paste it
anywhere: Recooty has already embedded it into every URL and code sample on
the page tagged REQUIRES API KEY. Copy the request as shown and it works.
The API key only ever returns jobs you have already published publicly, so it
is safe to use in browser JavaScript or a public widget.
Access tokens
Everything that writes data or reads candidate information needs a personal
access token.
To create one:
1. Go to Settings → Integrations → API.
2. Click Create Token (top-right of the API Key & Access Tokens card).
3. Give it a name that says where it will be used — Website careers form,
Power Automate sync, Staging.
4. Click Create.
5. Copy the token immediately. It is shown once and never again.
If you lose a token, you can't recover it — revoke it and create a new one.
To revoke one: click Revoke next to the token. It stops working immediately,
and any integration using it starts failing with 401.
Good practice:
- One token per integration. If one leaks, you revoke just that one.
- Tokens expire one year after creation — the expiry date is shown on the
token card. Diary a renewal a few weeks before.
- Never put a token in front-end JavaScript, a mobile app, or a public
repository. Anyone holding it can read every candidate in your workspace.
- Tokens are tied to the workspace (team) they were created in. A token can
never see another workspace's data.
THE BASICS EVERY REQUEST FOLLOWS
Base URL
<API_BASE_URL>/api/v1/
The exact base URL for your account is shown on the API page — copy it from
there.
Headers
Accept: application/json
Authorization: Bearer <ACCESS_TOKEN> ← only for REQUIRES TOKEN endpoints
Content-Type: application/json ← when you send a JSON body
Responses are always JSON, with the data wrapped under a named key (job, jobs,
location, applications, …).
Errors are always JSON with a message, plus an errors object when a field
failed validation:
{
"message": "The title field is required.",
"errors": { "title": ["The title field is required."] }
}
Two kinds of ID appear throughout:
ID: Job ID
Looks like ............ 01JQ8Z0M2K7X… (a ULID)
Where it comes from ... id in any job response
ID: Application ID
Looks like ............ 7bd025e9c2
Where it comes from ... application_id in any application response
Location and department IDs are ordinary numbers (12, 3).
Rate limits: Recooty does not currently apply a published per-minute limit to
these endpoints. Build your integration to pause and retry if it ever receives
a 429, and avoid tight polling loops — see Integration recipes.
USAGE, CREDITS AND LIMITS
This is the part most integrations get surprised by, so it's worth reading
even if you're not technical.
Three things on your plan can be used up. When any of them runs out, the API
replies 402 with a plain-English message — it does not silently do half the
work.
Resource: Jobs
What consumes it ... Each job that is Open or Internal
What frees it ...... Closing a job
Resource: Locations
What consumes it ... Each location that exists
What frees it ...... Deleting a location
Resource: Application view credits
What consumes it ... The first time an application's full details are
viewed
What frees it ...... Nothing — credits refresh with your plan
How job credits actually work
- A job saved as DRAFT costs nothing.
- The moment it becomes Open or Internal, it uses one job credit.
- Closing a job gives the credit back.
- If you're at your limit and try to publish, you get 402 and the job is not
created at all — the whole operation is rolled back, so you won't find a
stray draft afterwards.
How location credits work
- Every location row counts, whether or not a job uses it.
- Creating one over the limit returns 402 and creates nothing.
- There is no "find or create" — every POST /locations makes a brand-new
row. If your automation calls it on every sync, you will build up
duplicates and eventually hit 402. Store the location ID you get back and
reuse it.
How application view credits work
This one is genuinely useful to understand:
Call: GET /jobs/{job_id}/applications (the list)
Costs a credit? ............ No
Returns contact details? ... No — names and status only
Call: GET /applications/{application_id} (the detail)
Costs a credit? ............ Yes, first time only
Returns contact details? ... Yes — email, phone, resume, AI insights
Call: Same detail call again, later
Costs a credit? ............ No
Returns contact details? ... Yes
Call: Applications you submitted through the API
Costs a credit? ............ No, ever
Returns contact details? ... Yes
So you can poll the list endpoint as often as you like for free, and only
spend a credit when you actually open a candidate.
The last row is worth repeating: an application your own form submitted
through POST /jobs/{job_id}/applications is already marked as viewed, at no
charge. You already had the candidate's details when you sent them, so Recooty
doesn't charge you to read them back.
When credits run out:
{
"message": "You have reached your application view credit limit. Please upgrade your plan to continue."
}
Important: running out of view credits does not stop applications arriving.
Candidates can still apply, the applications are still stored, and the list
endpoint still works. You just can't open new ones until you upgrade or your
credits refresh.
JOBS API
Everything to do with job postings: reading the jobs you've published, looking
one up, and creating or updating jobs from another system.
When you'd use it
- Put your jobs on your own website — your web team renders the live list
from the feed, styled however they like.
- Feed a partner site or internal portal with your open roles.
- Keep Recooty in sync with your ATS or HR system — when a requisition opens
over there, the job appears in Recooty automatically; when it's filled,
the job closes.
- Avoid duplicates by checking whether a job code already exists before
creating it.
The endpoints
What it does: List published jobs
Request ...... GET /api/v1/jobs/<API_KEY>
Credential ... API key
Status ....... Stable
What it does: Retrieve one job
Request ...... GET /api/v1/jobs/<API_KEY>/<JOB_ID>
Credential ... API key
Status ....... Stable
What it does: Find a job by job code
Request ...... GET /api/v1/jobs/<API_KEY>?code=<JOB_CODE>
Credential ... API key
Status ....... Stable
What it does: Create a job
Request ...... POST /api/v1/jobs
Credential ... Access token
Status ....... Beta
What it does: Update a job
Request ...... PATCH /api/v1/jobs/<JOB_ID>
Credential ... Access token
Status ....... Beta
| About the Beta tag. Create and update work today and are used in
| production integrations, but the response shape may still gain fields.
| Nothing will be removed without notice. Build against them, and let us
| know what you're using them for.
How to use it
1. List your published jobs — no token needed, key already in the URL:
curl --request GET \
--url <API_BASE_URL>/api/v1/jobs/<API_KEY> \
--header 'Accept: application/json'
{
"company": "TechFlow Solutions",
"job_count": 3,
"jobs": [
{
"id": "7f8a9b2c3d4e5f6a7b8c9d0e1f2a3b4c",
"title": "Senior Frontend Developer",
"jobcode": "TF001",
"description": "<strong>Job Responsibilities</strong>…",
"city": "San Francisco",
"state": "California",
"country": "United States",
"zip": "94105",
"date": "2024-01-15 09:30:45",
"industry": "Technology",
"employment_type": "Full Time",
"remote": "Hybrid",
"experience": "Mid Level",
"details_url_external": "https://careers.example.com/techflow/senior-frontend",
"apply_url_external": "https://careers.example.com/techflow/senior-frontend/apply"
}
]
}
details_url_external and apply_url_external are ready-made links to your
Recooty careers page — the simplest way to build a job list on your own site
is to render the titles and point them at these URLs.
2. Check whether a job already exists, before creating it:
curl --request GET \
--url '<API_BASE_URL>/api/v1/jobs/<API_KEY>?code=REQ-10432' \
--header 'Accept: application/json'
job_count: 0 means it doesn't exist yet → create it. job_count: 1 means it
does → take jobs[0].id and update it instead.
3. Create a job:
curl --request POST \
--url <API_BASE_URL>/api/v1/jobs \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <ACCESS_TOKEN>' \
--header 'Content-Type: application/json' \
--data '{
"title": "Software Engineer",
"code": "ENG-001",
"description": "<p>We are hiring a software engineer…</p>",
"location_id": 1,
"department_id": 1,
"location_type": "REMOTE",
"experience_required": "MID_SENIOR",
"employment_type": "FULL_TIME",
"resume_required": true,
"status": "CAREER_PAGE"
}'
Returns 201 with the new job, including the id you'll use for every future
update.
Fields on create:
Field: title
Required ... Yes
Notes ...... Max 512 characters
Field: code
Required ... Yes
Notes ...... Max 24 characters, letters/numbers/hyphens/underscores only,
unique within your workspace
Field: description
Required ... Yes
Notes ...... Minimum 30 words. HTML allowed and cleaned server-side
Field: location_id
Required ... Yes
Notes ...... Must be one of your locations — get it from the Locations API
Field: location_type
Required ... Yes
Notes ...... ON_SITE, HYBRID, REMOTE
Field: experience_required
Required ... Yes
Notes ...... INTERNSHIP, ENTRY_LEVEL, ASSOCIATE, MID_SENIOR,
VICE_PRESIDENT, PRESIDENT
Field: employment_type
Required ... Yes
Notes ...... FULL_TIME, PART_TIME, CONTRACTOR, INTERN, OTHER
Field: resume_required
Required ... Yes
Notes ...... true / false
Field: status
Required ... Yes
Notes ...... DRAFT, CAREER_PAGE, JOB_BOARDS
Field: department_id
Required ... No
Notes ...... Must be one of your departments
Field: industry_type
Required ... No
Notes ...... Free text
Field: min_pay / max_pay
Required ... No
Notes ...... If you send one you must send both, and min_pay must be less
than max_pay
Field: pay_interval
Required ... With pay
Notes ...... HOUR, DAY, WEEK, MONTH, YEAR
Field: pay_currency
Required ... With pay
Notes ...... ISO code — USD, GBP, EUR, INR, …
Field: fields
Required ... No
Notes ...... Screening questions — see below
Publishing is controlled by status:
You send: DRAFT
What happens ... Saved privately in Recooty. Uses no job credit. Nobody
can see it
You send: CAREER_PAGE
What happens ... Goes live on your Recooty careers page. Uses one job
credit
You send: JOB_BOARDS
What happens ... Goes live and is submitted to job boards for approval.
Uses one job credit
4. Update a job — send only the fields you're changing:
curl --request PATCH \
--url <API_BASE_URL>/api/v1/jobs/<JOB_ID> \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <ACCESS_TOKEN>' \
--header 'Content-Type: application/json' \
--data '{ "status": "CLOSED" }'
Status changes follow strict rules. Anything not in this table returns 409:
Current status: Draft
Can change to ... CAREER_PAGE, JOB_BOARDS
Current status: Open
Can change to ... CLOSED, INTERNAL
Current status: Internal
Can change to ... CLOSED
Current status: Closed
Can change to ... (nothing — closed is final via the API)
Notes / what to keep in mind
- Job code is your anchor. It's unique per workspace and searchable, so use
your own system's reference number as the code. That single field is what
lets an automation decide "update the existing job" instead of creating a
duplicate every run. You don't need any custom fields to track the link.
- The single-job lookup only returns open jobs. GET /jobs/<API_KEY>/<JOB_ID>
returns 404 for a draft, internal, or closed job. If you need a job in any
status, use the job-code lookup instead — that one searches everything.
- The plain list only shows open jobs too. Drafts, internal and closed jobs
never appear in the public feed. That's deliberate: it's a public
endpoint.
- Job codes look different in different responses. The API-key lookups
return it as jobcode; create and update return it as code. Same value,
legacy naming.
- Descriptions must be at least 30 words. Short placeholder text will be
rejected with 422. This protects your job board distribution — most boards
reject thin descriptions.
- Editing an approved job's title or description queues a re-approval. If
the job is already approved on job boards, your edit is parked rather than
applied. The response includes "pending_reapproval": true, and the live
posting keeps the old text until an admin approves. Your API call still
succeeds.
- Screening questions can only be set when the job is created. Sending
fields or resume_required on an update is rejected with 422 and the
message "Screening questions cannot be updated through this endpoint."
Change them in the Recooty UI.
- Jobs are never deleted, only closed. There is no delete endpoint by design
— closing preserves the applications attached to the job.
- Closed jobs can't be reopened via the API in this version. Reopen it in
the Recooty UI, or create a new job.
- There are no job webhooks yet. To detect changes, use the job-code lookup
on a schedule rather than polling constantly.
Screening questions (fields, optional on create):
"fields": [
{ "field_name": "Years of React experience", "field_type": "NUMBER", "is_required": true },
{ "field_name": "Preferred location", "field_type": "DROP_DOWN", "is_required": true,
"data": ["London", "Remote"] },
{ "field_name": "Do you need visa sponsorship?", "field_type": "TOGGLE",
"is_required": true, "auto_reject": true }
]
field_type accepts TEXT, TEXT_AREA, DROP_DOWN, CHECK_BOX, RADIO, NUMBER, DATE,
FILE, URL, TOGGLE, BOOLEAN, INFORMATION. data (the list of options) is
required for DROP_DOWN, CHECK_BOX and RADIO. auto_reject only works on TOGGLE
fields, whose options are fixed to Yes/No.
Troubleshooting
Symptom: Job list is empty but you have jobs
Most likely cause ... None are Open — they're drafts, internal, or closed
Fix ................. Publish one, or use the ?code= lookup
Symptom: A job you can see in Recooty returns 404 from the single-job lookup
Most likely cause ... That endpoint only serves open jobs
Fix ................. Use ?code= instead
Symptom: 422 on code
Most likely cause ... A job with that code already exists in your
workspace
Fix ................. Look it up by code and update it instead of creating
Symptom: 422 on description
Most likely cause ... Fewer than 30 words
Fix ................. Expand the description
Symptom: 422 on location_id
Most likely cause ... The location belongs to a different workspace, or
was deleted
Fix ................. Call GET /locations and use an ID from there
Symptom: 422 on pay_interval/pay_currency
Most likely cause ... You sent min_pay/max_pay without them
Fix ................. Send all four, or none
Symptom: 409 on a status change
Most likely cause ... Not an allowed transition
Fix ................. Check the transition table above
Symptom: 402 when publishing
Most likely cause ... You're at your active-job limit
Fix ................. Close a job you don't need, or upgrade
Symptom: Job created but title change didn't appear
Most likely cause ... It's queued for job-board re-approval
Fix ................. Check pending_reapproval in the response; an admin
approves it
CANDIDATES API
What it is
The candidate side: sending applications into Recooty from your own form,
listing who applied, and opening a candidate's full profile including
Recooty's AI resume insights.
When you'd use it
- You built your own application form — on your website, in a chatbot,
inside your intranet — and want the submissions to land in Recooty's
pipeline exactly like careers-page applications.
- You want applicant counts on an internal dashboard without giving people
Recooty logins.
- You're piping candidate data into a BI tool, a background-check vendor, or
your HRIS.
The endpoints
What it does: Submit an application
Request ...... POST /api/v1/jobs/<JOB_ID>/applications
Credential ... Access token
What it does: List a job's applications
Request ...... GET /api/v1/jobs/<JOB_ID>/applications
Credential ... Access token
What it does: View one application in full
Request ...... GET /api/v1/applications/<APPLICATION_ID>
Credential ... Access token
| Note on the submit URL. Older integrations use the singular /application.
| It still works, but it's deprecated and its responses now carry a
| Deprecation: true header. Move to the plural /applications when you next
| touch the code.
How to use it
1. Submit an application. The body must be multipart/form-data because it
carries a CV file:
curl --request POST \
--url <API_BASE_URL>/api/v1/jobs/<JOB_ID>/applications \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <ACCESS_TOKEN>' \
--header 'Content-Type: multipart/form-data' \
--form 'first_name=John' \
--form 'last_name=Doe' \
--form '[email protected]' \
--form 'mobile_number=9999999999' \
--form 'resume=@/path/to/resume.pdf'
Field: first_name
Required ... Yes
Rules ...... 2–512 characters
Field: last_name
Required ... Yes
Rules ...... 2–512 characters
Field: email
Required ... Yes
Rules ...... Valid email address
Field: mobile_number
Required ... Yes
Rules ...... Also accepted as phone_number
Field: resume
Required ... Yes
Rules ...... PDF, DOC or DOCX only
The response returns the new application_id — store it if you want to fetch
the candidate later.
2. List a job's applications (free, no contact details):
curl --request GET \
--url <API_BASE_URL>/api/v1/jobs/<JOB_ID>/applications \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <ACCESS_TOKEN>'
Returns 10 per page, newest first. Follow next_page_url, or add ?page=2.
3. Open one candidate (costs one view credit the first time):
curl --request GET \
--url <API_BASE_URL>/api/v1/applications/<APPLICATION_ID> \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <ACCESS_TOKEN>'
This returns everything: contact details, resume link, answers to your
screening questions, current pipeline stage, and an ai_insights block:
"ai_insights": {
"parse_status": "PARSED",
"score": 78,
"pros": ["Relevant experience in a similar role."],
"cons": ["No exposure to one of the required reporting tools."],
"resume": { "personal_info": {}, "experience": [], "education": [], "skills": [] }
}
parse_status tells you what state the AI analysis is in:
Value: UNPARSED
Meaning .................. Not started
Should you poll again? ... Yes, shortly
Value: PROCESSING
Meaning .................. Being analysed right now
Should you poll again? ... Yes — this will change on its own
Value: PARSED
Meaning .................. Done — score, pros, cons, resume are populated
Should you poll again? ... No
Value: FAILED
Meaning .................. Couldn't be analysed (unreadable or image-only
CV)
Should you poll again? ... No — it won't change
Value: CANCELED
Meaning .................. Analysis was stopped
Should you poll again? ... No
resume is null — not an empty skeleton — until parsing finishes, so "no data
yet" is never confused with "a CV with nothing in it".
Notes / what to keep in mind
- Submitting an application doesn't spend a view credit, and never will for
that application. Recooty marks it as already seen, since you supplied the
data.
- The list endpoint is free and contains no personal contact details — just
names, dates and stage. Poll it as much as you like; spend a credit only
when you open someone.
- Once you've opened a candidate, opening them again is free. Credits are
charged per candidate, not per request.
- One application per email address per job. A second submission with the
same email returns 409. This is a deliberate anti-duplicate guard, not an
error in your code — treat 409 as "already applied" and move on.
- You can only apply to jobs that are open or internal. A closed or draft
job returns 410.
- CV file types are strictly PDF, DOC and DOCX. Images, ZIPs and plain text
are rejected with 422.
- AI insights are machine-generated. The score, pros and cons come from
Recooty's model, not a human recruiter. If you display them, label them as
AI output so your hiring managers don't mistake them for a colleague's
assessment.
- AI insights arrive after the application does. Right after submission the
status will usually be PROCESSING. Don't build a flow that assumes the
score is there immediately.
- A token can only reach its own workspace's jobs. Requesting a job from
another workspace returns 401.
Troubleshooting
Symptom: 409 Duplicate Application already in the system
Most likely cause ... That email already applied to this job
Fix ................. Expected behaviour — show the candidate a friendly
"you've already applied" message
Symptom: 410 The job is expired or no longer available
Most likely cause ... The job is closed or still a draft
Fix ................. Check the job's status before showing your form
Symptom: 422 on resume
Most likely cause ... Wrong file type, or the file didn't reach the server
Fix ................. Restrict your upload control to .pdf, .doc, .docx,
and confirm you're sending multipart/form-data
Symptom: 422 on mobile_number
Most likely cause ... Field missing entirely
Fix ................. Send mobile_number (or phone_number) — it's required
Symptom: 402 when opening a candidate
Most likely cause ... View credits exhausted
Fix ................. Upgrade, or wait for credits to refresh.
Applications are still arriving safely
Symptom: ai_insights.score is null
Most likely cause ... Analysis still running, or the CV couldn't be read
Fix ................. Check parse_status — poll on PROCESSING, stop on
FAILED
Symptom: 401 on every call
Most likely cause ... Token expired, revoked, or copied incorrectly
Fix ................. Tokens last one year. Create a new one and update
your integration
Symptom: Applications arrive but with no screening answers
Most likely cause ... The job's screening questions weren't included in
your submission
Fix ................. Screening answers are optional in the API; send them
if your own form collects them
LOCATIONS API
Locations are the addresses your jobs are attached to. Every job needs one.
This API lists them, creates new ones, and edits existing ones.
When you'd use it
Almost always as a supporting step for the Jobs API: before you can create a
job from another system, you need a location_id.
- Look up the ID of a location you already have in Recooty.
- Create a new office when you open one, so jobs can be filed against it.
- Correct an address that changed.
The endpoints
What it does: List your locations
Request ...... GET /api/v1/locations
Credential ... Access token
Status ....... Beta
What it does: Create a location
Request ...... POST /api/v1/locations
Credential ... Access token
Status ....... Beta
What it does: Update a location
Request ...... PATCH /api/v1/locations/<LOCATION_ID>
Credential ... Access token
Status ....... Beta
How to use it
1. List what you already have (25 per page):
curl --request GET \
--url <API_BASE_URL>/api/v1/locations \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <ACCESS_TOKEN>'
{
"locations": {
"data": [
{
"id": 1,
"name": "Headquarters",
"address": "123 Main Street",
"city": "San Francisco",
"state": "California",
"country": "United States",
"country_code": "US",
"zip_code": "94105",
"is_default": true
}
]
}
}
Take the id and use it as location_id when creating a job.
2. Create one only if it doesn't exist:
curl --request POST \
--url <API_BASE_URL>/api/v1/locations \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <ACCESS_TOKEN>' \
--header 'Content-Type: application/json' \
--data '{
"name": "Headquarters",
"address": "123 Main Street",
"city": "San Francisco",
"state": "California",
"country": "United States",
"country_code": "US",
"zip_code": "94105"
}'
Every field is individually optional, but you must send at least one of name,
city or address. If you leave name out, Recooty builds one from city, state
(or the address). country_code must be exactly two letters. zip_code is also
accepted as zip.
3. Update one:
curl --request PATCH \
--url <API_BASE_URL>/api/v1/locations/<LOCATION_ID> \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <ACCESS_TOKEN>' \
--header 'Content-Type: application/json' \
--data '{ "address": "500 Market Street", "zip_code": "94103" }'
Notes / what to keep in mind
- This is the number-one place automations go wrong. There is no "find or
create". Every POST /locations creates a brand-new row, even if an
identical address already exists. An automation that creates a location on
every sync will fill your account with duplicates and eventually hit your
location limit. The fix: call GET /locations first, match on your side,
and only create when there's genuinely no match. Store the ID you get back
and reuse it forever.
- Locations are shared. Every job pointing at a location shares the same
address record, so editing it changes the address on all of those jobs at
once. If only one job's address changed, create a new location for it
instead of editing the shared one.
- Every location counts towards your plan limit, whether or not a job uses
it. Housekeeping matters.
- Creating over the limit changes nothing. The operation is wrapped in a
transaction, so a 402 leaves no half-created row behind.
- There's no delete endpoint. Remove unused locations in the Recooty UI.
Troubleshooting
Symptom: Duplicate offices piling up
Most likely cause ... Your automation creates a location every run
Fix ................. Look up first, create only on no match, cache the ID
Symptom: 402 You have reached your location limit
Most likely cause ... Too many locations, usually from the above
Fix ................. Delete unused locations in the UI, or upgrade
Symptom: 422 on country_code
Most likely cause ... Not exactly two characters
Fix ................. Use the ISO code — US, GB, IN
Symptom: 422 — nothing identifies the location
Most likely cause ... You sent none of name, city, address
Fix ................. Send at least one
Symptom: 404 on update
Most likely cause ... The location belongs to another workspace, or was
deleted
Fix ................. Re-list and use a current ID
Symptom: Editing one job's address changed several jobs
Most likely cause ... They all share that location
Fix ................. Create a separate location for that job
DEPARTMENTS API
A read-only list of the departments in your workspace, so you can tag jobs to
the right team.
When you'd use it
To resolve a department_id before creating or updating a job. Department names
in your HR system rarely match Recooty's IDs, so you look them up here and map
them once.
The endpoint
What it does: List your departments
Request ...... GET /api/v1/departments
Credential ... Access token
Status ....... Beta
How to use it
curl --request GET \
--url <API_BASE_URL>/api/v1/departments \
--header 'Accept: application/json' \
--header 'Authorization: Bearer <ACCESS_TOKEN>'
{
"departments": {
"data": [
{ "id": 1, "name": "Engineering" },
{ "id": 2, "name": "Marketing" }
]
}
}
Use id as department_id on a job.
Notes / what to keep in mind
- Read-only. You cannot create, rename or delete departments through the API
— do that in the Recooty UI. This is intentional: departments drive
reporting and permissions, so they're managed by people, not machines.
- department_id is optional on a job. Leave it out if you don't use
departments.
- Departments don't consume any credit or limit.
- Returns 25 per page. With more than 25, follow next_page_url or add
?page=2 — a common cause of "our department is missing" is only reading
page one.
- Sorted alphabetically by name, not by ID.
Troubleshooting
Symptom: A department is missing from the response
Most likely cause ... It's on a later page
Fix ................. Follow next_page_url, or add ?page=2
Symptom: 422 on department_id when creating a job
Most likely cause ... The ID belongs to another workspace, or was deleted
Fix ................. Re-list and use a current ID
Symptom: You need a new department
Most likely cause ... Not possible via the API
Fix ................. Create it in Recooty, then re-list to get its ID
Symptom: The list is empty
Most likely cause ... Your workspace has no departments yet
Fix ................. Create one in Recooty, or omit department_id
ERROR CODES — THE COMPLETE REFERENCE
Every Recooty API error returns a JSON message. The HTTP status tells you what
kind of problem it is and, crucially, whether retrying will help.
Code: 200 / 201
Meaning ........... Success
Typical message ... —
Retry? ............ —
Code: 401
Meaning ........... Not authenticated, or the resource belongs to another
workspace
Typical message ... Unauthorized
Retry? ............ No — fix the token
Code: 402
Meaning ........... Plan or credit problem
Typical message ... Feature not available to your plan / You have reached
your active job limit… / You have reached your
location limit… / You have reached your application
view credit limit…
Retry? ............ No — upgrade or free up capacity
Code: 404
Meaning ........... Not found, or not visible to you
Typical message ... Job post not found, Location not found
Retry? ............ No
Code: 409
Meaning ........... Conflict with the current state
Typical message ... Duplicate Application already in the system, or an
invalid status transition
Retry? ............ No — it means "already done" or "not allowed from
here"
Code: 410
Meaning ........... The job is gone or closed
Typical message ... The job is expired or no longer available
Retry? ............ No
Code: 422
Meaning ........... A field failed validation
Typical message ... Field-by-field detail in errors
Retry? ............ No — fix the data
Code: 429
Meaning ........... Too many requests
Typical message ... —
Retry? ............ Yes — wait and retry
Code: 500
Meaning ........... Something broke on our side
Typical message ... Unable to save job, Unable to fetch applications, …
Retry? ............ Yes — retry with backoff; contact support if it
persists
The three codes people misread
402 is not one problem, it's two. Read the message:
- "Feature not available to your plan" → API Access isn't on your plan at
all. Nothing will work until you upgrade.
- "You have reached your … limit" → your plan is fine, you've simply used up
jobs, locations or view credits. Free some up or upgrade.
409 usually means "it already happened". A duplicate application isn't a
failure — the candidate is already in your pipeline. Handle it as a normal
outcome, not an alert.
401 on an integration that used to work almost always means an expired or
revoked token. Tokens last one year. Check the expiry date on the token card
in Recooty.
INTEGRATION RECIPES
Sync jobs from another system into Recooty
Run this whenever a requisition changes in your system:
1. Resolve the location. Reuse a stored location_id if you have one. Only if
you don't, call GET /locations, look for a match, and create one as a
last resort. Cache the ID.
2. Resolve the department (if you use them) with GET /departments. Map names
to IDs once and cache.
3. Check whether the job exists: GET /jobs/<API_KEY>?code=<YOUR_REFERENCE>.
- job_count: 0 → POST /jobs with code set to your reference. - job_count:
1 → take jobs[0].id and PATCH /jobs/{id} with only the changed fields.
4. Mirror the status. Requisition filled or cancelled → PATCH with status:
"CLOSED". Reopened → publish with CAREER_PAGE. The lookup response
includes the current status, so skip the call when nothing changed.
Because job code is unique per workspace, this loop is safe to run repeatedly
— it will never create a duplicate.
Put your live jobs on your own website
1. Copy the List published jobs URL from the API page (your key is already
in it).
2. Have your web team fetch it and render title, city, employment_type and
remote.
3. Link each entry to details_url_external, and any Apply button to
apply_url_external.
No token needed, so this is safe to run from browser JavaScript. If you'd
rather not write code at all, use the Jobs Widget instead — see the widget
article.
Accept applications from your own form
1. Create an access token named after the form.
2. Store it on your server, never in the page.
3. On submit, POST to /jobs/<JOB_ID>/applications as multipart/form-data
from your backend.
4. Handle 409 as "you've already applied", and 410 as "this role has
closed".
Pull applicants into a dashboard
1. Poll GET /jobs/<JOB_ID>/applications on a schedule — free, no contact
details.
2. Show counts, names and stages from that alone.
3. Call GET /applications/<APPLICATION_ID> only when someone actually clicks
into a candidate. That's when the view credit is spent.
FREQUENTLY ASKED QUESTIONS
Do I need a developer? For reading and displaying jobs, not necessarily — the
Jobs Widget covers that with no code. For anything that writes into Recooty or
reads candidates, yes.
What happens if my plan doesn't include API Access? The API page is still
visible and your API key still works for the three job-listing endpoints.
Everything else returns 402 until you upgrade.
What happens if I run out of application view credits? Applications keep
arriving normally and nothing is lost. You can still list them. Opening a
candidate you haven't opened before returns 402 until credits refresh or you
upgrade. Candidates you've already opened stay open.
What happens if I hit my job limit? Publishing another job returns 402 and
creates nothing. Close a job you no longer need — that frees the credit
immediately — or upgrade. Drafts are free, so you can stage work in advance.
What happens if I hit my location limit? Creating another location returns 402
and creates nothing. Delete unused locations in the UI. This limit is usually
hit by an automation creating duplicates — see the Locations notes.
Can I delete a job? No, and that's deliberate — deleting would take its
applications with it. Close it instead.
Can I reopen a closed job? Not through the API in this version. Reopen it in
the Recooty UI, or create a new job.
Can I get notified when something happens, instead of polling? Recooty has
webhooks for application events (Settings → Integrations → Webhooks). Job
events aren't covered yet — for those, use the job-code lookup on a schedule.
My token stopped working. Tokens expire one year after creation, and revoking
one takes effect immediately. Check the expiry on the token card, then create
a replacement.
Someone left the company — what should I do with their tokens? Revoke them.
Tokens are created by a person but act on the whole workspace, so treat them
like any other shared credential during offboarding.
Is my API key sensitive? Less so than a token. It only returns jobs you've
already published publicly, so it's safe in a website or widget. It is not a
password and can't be used to write anything or read candidates.
Can one workspace see another's data? No. Every token is bound to a single
workspace when it's created, and requests for another workspace's jobs or
locations return 401 or 404.
Which endpoints are Beta? Create/update job, all three location endpoints, and
list departments. They work in production today; their responses may gain
fields, but nothing will be removed without notice. They're marked with a Beta
tag on the API page.
GETTING HELP
1. Start on the API page — Settings → Integrations → API. Every endpoint has
a working example with your own key already filled in, plus a sample
response.
2. Read the error message. Recooty's API returns plain-English messages, and
the status code tells you whether retrying will help.
3. Still stuck? Contact Recooty support with: the endpoint you called, the
HTTP status, the full JSON error, and roughly when it happened. That's
usually enough to resolve it on the first reply.
Please never share an access token in a support ticket, email, or screenshot.
Support never needs it.
