Vodora

Recruiter API documentation

Use these APIs to power jobs, applicants, and candidate profiles on your own website. Each endpoint includes the request fields and example responses you need to integrate.

Getting started

Think of this like connecting your careers site to Vodora’s backend. You will: create a key, allow your domain, then call HTTPS endpoints with that key.

  1. Sign in as a recruiter and open Settings → API & Integrations.
  2. Create an API key and register your website domain (one key, one domain).
  3. Copy the secret immediately — Vodora stores only a hash and never shows the full key again.
  4. Call APIs from your backend (Node, Python, Go, PHP, etc.). Do not put the secret in public browser JavaScript.
  5. Replace YOUR_VODORA_HOST, vod_live_YOUR_SECRET, and your domain in the examples.

Authentication

Every request needs two things: your API key, and proof that the call is for your registered website domain.

  • Header: Authorization: Bearer vod_live_…
  • Server calls: also send X-Vodora-Origin: https://your-site.com matching the domain on the key.
  • Browser calls: the browser sends Origin automatically. CORS only allows your registered domain.

How every request works

Use JSON. Most write endpoints need Content-Type: application/json. Successful responses usually look like { success: true, ... }. Failures look like { success: false, error: "…" }.

Headers you almost always need

Headers
Authorization: Bearer vod_live_YOUR_SECRET
X-Vodora-Origin: https://careers.example.com
Content-Type: application/json

Suggested build order for your website

  1. Load catalog (category → subcategory → profession → skills) for your create-job form
  2. List jobs → show them on your careers page
  3. Create / update / close jobs from your admin UI
  4. List applications for a job
  5. Open applicant detail + update status
  6. Open candidate profile with vodoraId

Endpoints overview (16)

Click a row to jump to the full request/response explanation.

MethodPathScopePurpose
GET/api/v1/job-categoriesjobs:readList job categories for the create-job form
GET/api/v1/job-subcategoriesjobs:readList subcategories for a category
GET/api/v1/professionsjobs:readList professions for a subcategory
GET/api/v1/professions/{professionId}/skillsjobs:readSuggested skills for a profession
GET/api/v1/skillsjobs:readSearch the skills catalog
GET/api/v1/work-typesjobs:readList work types (Full Time, Contract, …)
GET/api/v1/jobsjobs:readList jobs created by your recruiter account
POST/api/v1/jobsjobs:writeCreate a job posting
GET/api/v1/jobs/{jobId}jobs:readGet one job by id
PATCH/api/v1/jobs/{jobId}jobs:writeUpdate a job posting
DELETE/api/v1/jobs/{jobId}jobs:writeClose a job posting
POST/api/v1/jobs/{jobId}/repostjobs:writeRepost a job with a new expiry date
GET/api/v1/jobs/{jobId}/applicationsapplications:readList applicants for a job
GET/api/v1/jobs/{jobId}/applications/{applicationId}applications:readGet applicant detail
PATCH/api/v1/jobs/{jobId}/applications/{applicationId}applications:writeUpdate application status or mark as read
GET/api/v1/candidates/{vodoraId}candidates:readView a candidate profile

Job catalog APIs

Same cascade as the Vodora job form: category → subcategory → profession → suggested skills. Use these ids when creating or updating a job.

GET/api/v1/job-categories

List job categories

Returns every active job category (id, name, slug).

When to use: Populate the Category dropdown on your create-job form.

Required scope: jobs:read

Example success response

JSON
{
  "success": true,
  "categories": [
    {
      "id": "CATEGORY_UUID",
      "name": "Information Technology",
      "slug": "information-technology"
    }
  ]
}

Tips

  • Send category as the slug (or id) when loading subcategories and when creating a job.
GET/api/v1/job-subcategories

List subcategories

Returns subcategories for one category. Same pagination as the in-app search.

When to use: After the user picks a category.

Required scope: jobs:read

Query parameters

FieldTypeRequiredNotes
categorystringYesCategory slug or id from GET /api/v1/job-categories.
qstringNoOptional name search.
offsetnumberNoPagination offset. Default 0.
limitnumberNoPage size, max 50. Default 30.

Example success response

JSON
{
  "success": true,
  "items": [
    {
      "id": "SUBCATEGORY_UUID",
      "name": "Software Development",
      "slug": "software-development"
    }
  ],
  "nextOffset": 30
}

Example error response

JSON
{
  "success": false,
  "error": "Query category is required (slug or id from GET /api/v1/job-categories)."
}

Tips

  • nextOffset is null when there are no more pages.
  • Use item.id as subcategoryId on POST /api/v1/jobs.
GET/api/v1/professions

List professions

Returns professions (job titles) for one subcategory.

When to use: After the user picks a subcategory.

Required scope: jobs:read

Query parameters

FieldTypeRequiredNotes
subcategorystring (uuid)YesSubcategory id from GET /api/v1/job-subcategories.
qstringNoOptional name search.
offsetnumberNoPagination offset. Default 0.
limitnumberNoPage size, max 50. Default 30.

Example success response

JSON
{
  "success": true,
  "items": [
    {
      "id": "PROFESSION_UUID",
      "name": "Software Engineer",
      "slug": "software-engineer"
    }
  ],
  "nextOffset": null
}

Example error response

JSON
{
  "success": false,
  "error": "Query subcategory is required (id from GET /api/v1/job-subcategories)."
}

Tips

  • Use item.id as professionId on POST /api/v1/jobs.
GET/api/v1/professions/{professionId}/skills

Suggested skills for a profession

Returns catalog skills linked to that profession. Core skills default to Required; Recommended default to Preferred.

When to use: Prefill the Skills field after the user picks a profession.

Required scope: jobs:read

Path parameters

FieldTypeRequiredNotes
professionIdstring (uuid)YesProfession id from GET /api/v1/professions.

Example success response

JSON
{
  "success": true,
  "items": [
    {
      "id": "SKILL_UUID",
      "name": "TypeScript",
      "slug": "typescript",
      "relevance": "Core",
      "defaultLevel": "Required"
    }
  ]
}

Tips

  • Map defaultLevel onto requirementLevel when posting a job.
  • Users can still add extra skills from GET /api/v1/skills.
GET/api/v1/skills

Search the skills catalog

Search all active skills. Use this for “Add skill” beyond the profession suggestions.

When to use: Let the user search and add extra skills to a job.

Required scope: jobs:read

Query parameters

FieldTypeRequiredNotes
qstringNoName search. Empty returns the first page of all skills.
offsetnumberNoPagination offset. Default 0.
limitnumberNoPage size, max 50. Default 30.

Example success response

JSON
{
  "success": true,
  "items": [
    { "id": "SKILL_UUID", "name": "Figma", "slug": "figma" }
  ],
  "nextOffset": 30
}

Tips

  • Use item.id as skills[].skillId on POST /api/v1/jobs.
GET/api/v1/work-types

List work types

Returns active work types with ids needed on create/update.

When to use: Populate the Work type dropdown without listing jobs first.

Required scope: jobs:read

Example success response

JSON
{
  "success": true,
  "workTypes": [
    { "id": "WORK_TYPE_UUID", "code": "full_time", "name": "Full Time" }
  ]
}

Tips

  • Use workTypes[].id as workTypeId on POST /api/v1/jobs.

Jobs APIs

Create and manage job ads for your recruiter account.

GET/api/v1/jobs

List your jobs

Returns all jobs for your recruiter account, plus work type options and basic stats.

When to use: Build a careers / jobs list page, or an admin dashboard of your postings.

Required scope: jobs:read

Example success response

JSON
{
  "success": true,
  "jobs": [
    {
      "id": "JOB_UUID",
      "title": "Senior Designer",
      "company": "Acme",
      "type": "Full-time",
      "location": "Remote",
      "salary": "80000-100000 AUD",
      "applicants": 12,
      "status": "open",
      "urgent": false,
      "isExpired": false,
      "expiryDate": "2026-12-31",
      "newApplicantCount": 2
    }
  ],
  "workTypes": [
    { "id": "WORK_TYPE_UUID", "code": "full_time", "name": "Full-time" }
  ],
  "stats": {
    "totalPlacements": 4,
    "activeRoles": 3,
    "candidatesWorkedWith": 18,
    "avgTimeToHireDays": 21,
    "hiringFasterPercent": null,
    "hoursSavedThisMonth": 12
  }
}

Example error response

JSON
{
  "success": false,
  "error": "Could not load job postings."
}

Tips

  • Save workTypes from this response — you need workTypeId when creating a job.
  • Use job.id later for get/update/close/applicants calls.
POST/api/v1/jobs

Create a job

Creates a new job posting. On success you get the new jobId.

When to use: Your admin form posts a new role to Vodora from your website.

Required scope: jobs:write

Request body

Send a JSON object. At least one skill is required.

FieldTypeRequiredNotes
titlestringYesJob title from Vodora’s allowed title list (e.g. Senior Designer).
companyDisplayNamestringYesCompany name shown on the job ad.
companyIdstringNoOptional. Use if posting for a linked company.
categorystringYesCategory slug from GET /api/v1/job-categories (e.g. information-technology).
subcategoryIdstring (uuid)YesSubcategory id from GET /api/v1/job-subcategories?category=…
professionIdstring (uuid)YesProfession id from GET /api/v1/professions?subcategory=…
workplaceTypestringYesOne of: remote, onsite, hybrid.
locationCountrystringNoRequired for onsite/hybrid. Leave empty for remote.
locationCitystringNoRequired for onsite/hybrid. Leave empty for remote.
workTypeIdstring (uuid)YesWork type id from GET /api/v1/work-types (also returned on GET /jobs).
salaryDisplaystringNoHuman-readable salary text (validated by Vodora).
descriptionstringYesJob description (20–5000 characters).
aboutCompanystringYesAbout the company text for this job ad.
responsibilitiesstring[]NoBullet list of responsibilities.
requirementsstring[]NoBullet list of requirements.
benefitsstring[]NoBullet list of benefits.
employerQuestionsstring[]NoUp to 8 screening questions for applicants.
skillsobject[]YesAt least one skill. Shape: { skillId, requirementLevel: "Required" | "Preferred" }. Get skillId from GET /api/v1/professions/{id}/skills or GET /api/v1/skills.
isUrgentbooleanNoMarks the job as urgent. Default false.
publishbooleanNoPublish immediately. Defaults to true.
expiryDatestring (YYYY-MM-DD)YesDate the posting should close.

Example request body

JSON
{
  "title": "Senior Designer",
  "companyDisplayName": "Acme",
  "category": "design",
  "subcategoryId": "SUBCATEGORY_UUID",
  "professionId": "PROFESSION_UUID",
  "workplaceType": "remote",
  "locationCountry": "",
  "locationCity": "",
  "workTypeId": "WORK_TYPE_UUID",
  "salaryDisplay": "80000-100000 AUD",
  "description": "We are hiring a senior designer to own product UI systems and mentoring.",
  "aboutCompany": "Acme builds hiring tools for growing teams.",
  "responsibilities": ["Lead design critiques", "Ship UI systems"],
  "requirements": ["5+ years product design"],
  "benefits": ["Remote-friendly", "Learning budget"],
  "employerQuestions": ["Why Vodora?"],
  "isUrgent": false,
  "publish": true,
  "expiryDate": "2026-12-31",
  "skills": [{ "skillId": "SKILL_UUID", "requirementLevel": "Required" }]
}

Example success response

JSON
{
  "success": true,
  "jobId": "NEW_JOB_UUID"
}

Example error response

JSON
{
  "success": false,
  "error": "Add at least one skill."
}

Tips

  • HTTP status is 201 on success.
  • Load category, subcategory, profession, skills, and workTypeId from the catalog APIs first.
  • For remote jobs, leave locationCountry and locationCity empty.
  • For onsite/hybrid, send a valid catalog city and country.
GET/api/v1/jobs/{jobId}

Get one job

Returns full details for a single job you own.

When to use: Job detail page or edit form prefill.

Required scope: jobs:read

Path parameters

FieldTypeRequiredNotes
jobIdstring (uuid)YesJob id from list or create response.

Example success response

JSON
{
  "success": true,
  "job": {
    "id": "JOB_UUID",
    "title": "Senior Designer",
    "companyDisplayName": "Acme",
    "category": "design",
    "subcategoryId": "SUBCATEGORY_UUID",
    "professionId": "PROFESSION_UUID",
    "workplaceType": "remote",
    "locationCountry": "",
    "locationCity": "",
    "workTypeId": "WORK_TYPE_UUID",
    "salaryDisplay": "80000-100000 AUD",
    "description": "...",
    "aboutCompany": "...",
    "responsibilities": ["..."],
    "requirements": ["..."],
    "benefits": ["..."],
    "employerQuestions": ["..."],
    "isUrgent": false,
    "status": "open",
    "expiryDate": "2026-12-31",
    "isExpired": false,
    "skills": [
      {
        "skillId": "SKILL_UUID",
        "name": "Figma",
        "requirementLevel": "Required"
      }
    ]
  }
}

Example error response

JSON
{
  "success": false,
  "error": "Job not found."
}

Tips

  • 404 means the job does not exist or is not yours.
PATCH/api/v1/jobs/{jobId}

Update a job

Updates an existing job. Body uses the same fields as create.

When to use: Save changes from your edit-job form.

Required scope: jobs:write

Path parameters

FieldTypeRequiredNotes
jobIdstring (uuid)YesJob to update.

Request body

Send the full job payload (same shape as create).

FieldTypeRequiredNotes
titlestringYesJob title from Vodora’s allowed title list (e.g. Senior Designer).
companyDisplayNamestringYesCompany name shown on the job ad.
companyIdstringNoOptional. Use if posting for a linked company.
categorystringYesCategory slug from GET /api/v1/job-categories (e.g. information-technology).
subcategoryIdstring (uuid)YesSubcategory id from GET /api/v1/job-subcategories?category=…
professionIdstring (uuid)YesProfession id from GET /api/v1/professions?subcategory=…
workplaceTypestringYesOne of: remote, onsite, hybrid.
locationCountrystringNoRequired for onsite/hybrid. Leave empty for remote.
locationCitystringNoRequired for onsite/hybrid. Leave empty for remote.
workTypeIdstring (uuid)YesWork type id from GET /api/v1/work-types (also returned on GET /jobs).
salaryDisplaystringNoHuman-readable salary text (validated by Vodora).
descriptionstringYesJob description (20–5000 characters).
aboutCompanystringYesAbout the company text for this job ad.
responsibilitiesstring[]NoBullet list of responsibilities.
requirementsstring[]NoBullet list of requirements.
benefitsstring[]NoBullet list of benefits.
employerQuestionsstring[]NoUp to 8 screening questions for applicants.
skillsobject[]YesAt least one skill. Shape: { skillId, requirementLevel: "Required" | "Preferred" }. Get skillId from GET /api/v1/professions/{id}/skills or GET /api/v1/skills.
isUrgentbooleanNoMarks the job as urgent. Default false.
publishbooleanNoPublish immediately. Defaults to true.
expiryDatestring (YYYY-MM-DD)YesDate the posting should close.

Example request body

JSON
{
  "title": "Senior Designer",
  "companyDisplayName": "Acme",
  "category": "design",
  "subcategoryId": "SUBCATEGORY_UUID",
  "professionId": "PROFESSION_UUID",
  "workplaceType": "remote",
  "locationCountry": "",
  "locationCity": "",
  "workTypeId": "WORK_TYPE_UUID",
  "salaryDisplay": "80000-100000 AUD",
  "description": "We are hiring a senior designer to own product UI systems and mentoring.",
  "aboutCompany": "Acme builds hiring tools for growing teams.",
  "responsibilities": ["Lead design critiques", "Ship UI systems"],
  "requirements": ["5+ years product design"],
  "benefits": ["Remote-friendly", "Learning budget"],
  "employerQuestions": ["Why Vodora?"],
  "isUrgent": false,
  "publish": true,
  "expiryDate": "2026-12-31",
  "skills": [{ "skillId": "SKILL_UUID", "requirementLevel": "Required" }]
}

Example success response

JSON
{
  "success": true,
  "jobId": "JOB_UUID"
}

Example error response

JSON
{
  "success": false,
  "error": "Select a valid work type."
}

Tips

  • Easiest approach: GET the job, change fields in your UI, PATCH the full object back.
DELETE/api/v1/jobs/{jobId}

Close a job

Closes a posting so it is no longer open for applicants.

When to use: Admin clicks “Close role” on your site.

Required scope: jobs:write

Path parameters

FieldTypeRequiredNotes
jobIdstring (uuid)YesJob to close.

Example success response

JSON
{
  "success": true,
  "jobId": "JOB_UUID"
}

Example error response

JSON
{
  "success": false,
  "error": "Job not found."
}

Tips

  • This is a soft close of the posting, not a permanent database wipe for your records.
POST/api/v1/jobs/{jobId}/repost

Repost a job

Re-opens / reposts a job with a new expiry date.

When to use: A closed or expired role should go live again.

Required scope: jobs:write

Path parameters

FieldTypeRequiredNotes
jobIdstring (uuid)YesJob to repost.

Request body

FieldTypeRequiredNotes
expiryDatestring (YYYY-MM-DD)YesNew closing date for the reposted job.

Example request body

JSON
{
  "expiryDate": "2026-12-31"
}

Example success response

JSON
{
  "success": true,
  "jobId": "JOB_UUID"
}

Example error response

JSON
{
  "success": false,
  "error": "Job not found or cannot be re-posted."
}

Applications APIs

Review who applied and move them through your hiring pipeline.

Application status values: applied, shortlisted, interview, offer, unsuccessful. Responses often return title-case labels like Shortlisted.

GET/api/v1/jobs/{jobId}/applications

List applicants for a job

Returns the job summary plus every applicant for that job.

When to use: Applicants table / inbox for one role.

Required scope: applications:read

Path parameters

FieldTypeRequiredNotes
jobIdstring (uuid)YesJob whose applicants you want.

Example success response

JSON
{
  "success": true,
  "job": {
    "id": "JOB_UUID",
    "title": "Senior Designer",
    "company": "Acme",
    "location": "Remote",
    "salary": "80000-100000 AUD",
    "type": "Full-time",
    "urgent": false,
    "status": "open",
    "applicantCount": 2
  },
  "applicants": [
    {
      "applicationId": "APPLICATION_UUID",
      "candidateId": "CANDIDATE_UUID",
      "vodoraId": "VODORA_PUBLIC_ID",
      "name": "Alex Rivera",
      "title": "Product Designer",
      "email": "alex@example.com",
      "status": "Applied",
      "isNew": true,
      "appliedAt": "2026-08-01T10:00:00.000Z",
      "coverLetter": "...",
      "resume": {
        "id": "DOC_UUID",
        "name": "alex-resume.pdf",
        "type": "resume",
        "url": "https://...",
        "uploadedAt": "2026-08-01T10:00:00.000Z"
      }
    }
  ]
}

Tips

  • Save applicationId for detail and status updates.
  • Use vodoraId with the Candidates API to open the full profile.
GET/api/v1/jobs/{jobId}/applications/{applicationId}

Get applicant detail

Returns one applicant with richer profile fields (experience, education, skills, references).

When to use: Applicant detail drawer / page.

Required scope: applications:read

Path parameters

FieldTypeRequiredNotes
jobIdstring (uuid)YesParent job id.
applicationIdstring (uuid)YesApplication id from the list response.

Example success response

JSON
{
  "success": true,
  "applicant": {
    "applicationId": "APPLICATION_UUID",
    "vodoraId": "VODORA_PUBLIC_ID",
    "name": "Alex Rivera",
    "email": "alex@example.com",
    "status": "Applied",
    "coverLetter": "...",
    "about": "...",
    "website": "https://portfolio.example",
    "experience": [],
    "education": [],
    "skills": [],
    "references": []
  }
}

Example error response

JSON
{
  "success": false,
  "error": "Application not found."
}
PATCH/api/v1/jobs/{jobId}/applications/{applicationId}

Update application status (or mark as read)

Either change hiring status, or mark the application as read.

When to use: Pipeline actions like Shortlist / Interview / Offer, or clearing the “new” badge.

Required scope: applications:write

Path parameters

FieldTypeRequiredNotes
jobIdstring (uuid)YesParent job id.
applicationIdstring (uuid)YesApplication to update.

Request body

FieldTypeRequiredNotes
statusstringNoapplied | shortlisted | interview | offer | unsuccessful (required unless markAsRead is true).
markAsReadbooleanNoIf true, only marks the application as read (isNew becomes false).

Example request body

JSON
{
  "status": "shortlisted"
}

Example success response

JSON
{
  "success": true,
  "status": "Shortlisted"
}

Example error response

JSON
{
  "success": false,
  "error": "Invalid application status."
}

Tips

  • To mark as read only, send { "markAsRead": true } and expect { "success": true, "isNew": false }.
  • Do not send an empty body — you need status or markAsRead.

Candidates API

Open a candidate’s Vodora profile from your site.

GET/api/v1/candidates/{vodoraId}

Get candidate profile

Returns a recruiter-visible profile for a candidate, with privacy rules applied.

When to use: Candidate profile page linked from an applicant.

Required scope: candidates:read

Path parameters

FieldTypeRequiredNotes
vodoraIdstringYesPublic Vodora id from the applicant object (not always the same as candidateId).

Example success response

JSON
{
  "success": true,
  "profile": {
    "vodoraId": "VODORA_PUBLIC_ID",
    "fullName": "Alex Rivera",
    "headline": "Product Designer",
    "about": "..."
  },
  "hasReferenceAccess": false,
  "isSaved": false,
  "lockTrustDetails": true
}

Example error response

JSON
{
  "success": false,
  "error": "Candidate not found."
}

Tips

  • Some contact or trust fields may be hidden based on connection and subscription rules.
  • lockTrustDetails: true means detailed trust score breakdown is locked for your plan.

Code examples

Copy-paste starters in cURL, Node.js, Python, Go, PHP, Ruby, Java, and C#. Use these on your backend.

List jobs

GET /api/v1/jobs

curl -X GET "https://YOUR_VODORA_HOST/api/v1/jobs" \
  -H "Authorization: Bearer vod_live_YOUR_SECRET" \
  -H "X-Vodora-Origin: https://careers.example.com"

Create job

POST /api/v1/jobs

const res = await fetch("https://YOUR_VODORA_HOST/api/v1/jobs", {
  method: "POST",
  headers: {
    Authorization: "Bearer vod_live_YOUR_SECRET",
    "Content-Type": "application/json",
    "X-Vodora-Origin": "https://careers.example.com",
  },
  body: JSON.stringify({
    title: "Senior Designer",
    companyDisplayName: "Acme",
    category: "design",
    subcategoryId: "SUBCATEGORY_UUID",
    professionId: "PROFESSION_UUID",
    workplaceType: "remote",
    locationCountry: "",
    locationCity: "",
    workTypeId: "WORK_TYPE_UUID",
    salaryDisplay: "80000-100000 AUD",
    description:
      "We are hiring a senior designer to own product UI systems and mentoring.",
    aboutCompany: "Acme builds hiring tools for growing teams.",
    responsibilities: ["Lead design critiques", "Ship UI systems"],
    requirements: ["5+ years product design"],
    benefits: ["Remote-friendly"],
    employerQuestions: ["Why Vodora?"],
    isUrgent: false,
    publish: true,
    expiryDate: "2026-12-31",
    skills: [{ skillId: "SKILL_UUID", requirementLevel: "Required" }],
  }),
});

const data = await res.json();
console.log(data);

Update applicant status

PATCH /api/v1/jobs/{jobId}/applications/{applicationId}

import requests

job_id = "JOB_ID"
application_id = "APPLICATION_ID"

res = requests.patch(
    f"https://YOUR_VODORA_HOST/api/v1/jobs/{job_id}/applications/{application_id}",
    headers={
        "Authorization": "Bearer vod_live_YOUR_SECRET",
        "Content-Type": "application/json",
        "X-Vodora-Origin": "https://careers.example.com",
    },
    json={"status": "shortlisted"},
)

print(res.json())

Jobs embed widget (no custom UI)

Prefer a ready-made UI? Generate an embed script in Settings → API & Integrations. The script loads an iframe with job list, create/edit/close, applicants, and candidate profile — powered by the same product features as these APIs.

For a full HTML demo of those same pages (jobs form, all applicants for a job, and view profile), open /sample.html from the domain registered on your API key so CORS allows the applications and candidates calls.

Embed snippet

HTML
<script
  src="https://YOUR_VODORA_HOST/embed.js"
  data-embed-token="YOUR_EMBED_TOKEN"
  data-vodora-origin="https://YOUR_VODORA_HOST"
  async
></script>

ATS connections (JobAdder, Greenhouse, Lever)

Connecting an ATS is not the same as a Vodora API key or the embed script. In Settings → API & Integrations → ATS, paste a Greenhouse or Lever API key, or click Connect for JobAdder (OAuth). After that, Vodora pushes published jobs and applicants to the ATS and imports ATS jobs (drafts if the Vodora catalog cannot map title or location).

  • JobAdder: OAuth app credentials on the Vodora server. Recruiter only clicks Connect.
  • Greenhouse: Harvest API key. The account needs an office, department, and job board. A user id is only needed if Greenhouse asks for it.
  • Lever: API key with posting and opportunity access. Paste the webhook URL if Lever asks for it.

If a connection is wrong, Settings shows the error. Vodora will not silently skip sync.

Errors

Always check HTTP status and the JSON error string. Common cases:

  • 400 — invalid JSON or validation failed (missing fields, bad status, bad expiry date)
  • 401 — missing, invalid, or revoked API key
  • 403 — origin not on the key’s allowed domain, or missing scope
  • 404 — job, application, or candidate not found
  • 429 — rate limit (about 120 requests / minute per key)
  • 500 — unexpected server error; retry later and log the response body
JSON
{
  "success": false,
  "error": "Origin not allowed for this API key."
}

Manage API keys and embed scripts in Settings → API & Integrations.