Skip to main content

Connect Recooty to your own systems with the Recooty API

The Recooty API lets your own website, HR system, or automation tool talk to Recooty directly — no manual copy-paste, no CSV imports.

Written by Khushi Punjabi

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 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.

Did this answer your question?