Jobs
Create and manage jobs with the same fields as Dashboard → Jobs → Create job. All JSON uses snake_case. Timestamps are ISO 8601 strings in UTC.
Jobs do not embed applicant lists — use the Applications API to apply candidates, list pipeline rows (GET /applications?job_id=...), and update status. The applicant_count field on each job reflects how many candidate_jobs rows exist and updates automatically when applications are created or deleted. You can also apply inline when creating a candidate with a nested application object.
Internal apply via API is allowed regardless of job status (draft, paused, closed) — useful for ATS record-keeping. Public self-apply through the candidate portal still requires an active job.
List query parameters (GET)
| Field | Type | Required | Description |
|---|---|---|---|
| search | string | No | Search job title |
| status | "draft" | "active" | "paused" | "closed" | "filled" | No | Filter by status |
| client_id | string (uuid) | No | Filter by client |
| limit | integer | No | Page size (1–100, default 50) |
| offset | integer | No | Skip N records (default 0) |
Request body (POST / PUT)
| Field | Type | Required | Description |
|---|---|---|---|
| title | string | POST | Job title (required on create) |
| jd_text | string | null | No | HTML job description; may trigger AI enrichment |
| description | string | null | No | Plain-text description (optional if jd_text set) |
| client_id | string (uuid) | null | No | null = direct hire (your org); UUID = client you recruit for |
| client_name_not_disclosed | boolean | No | When client_id set: hide client on public job page |
| client_name | string | null | No | Legacy free-text client name |
| locations | string[] | No | City names (e.g. Bangalore, Mumbai) |
| work_mode | "onsite" | "remote" | "hybrid" | No | Default hybrid on dashboard |
| job_type | "full_time" | "contract" | No | Default full_time |
| openings_count | integer | No | Open positions (min 1, default 1) |
| status | "draft" | "active" | "paused" | "closed" | "filled" | No | Default active on create |
| min_experience_years | number | null | No | Minimum experience (years) |
| max_experience_years | number | null | No | Maximum experience (years) |
| currency | "INR" | "USD" | "EUR" | "GBP" | No | Salary/rate currency (default INR) |
| min_ctc | number | null | No | CTC minimum in LPA (full_time) |
| max_ctc | number | null | No | CTC maximum in LPA (full_time) |
| ctc_not_disclosed | boolean | No | Hide salary on public job page |
| rate | number | null | No | Contract rate (when job_type is contract) |
| required_skills | string[] | No | Required skill tags |
| preferred_skills | string[] | No | Preferred skill tags |
| screening_questions | object[] | No | WhatsApp/email/web screening questions (see nested fields below) |
| screening_questions[].id | string | No | Question id |
| screening_questions[].question | string | Yes* | Question text (*per item) |
| screening_questions[].type | "text" | "number" | "single_choice" | "multiple_choice" | Yes* | Answer type (*per item) |
| screening_questions[].options | string[] | No | Choices for choice types |
| screening_questions[].required | boolean | Yes* | Whether required (*per item) |
| screening_questions[].order | integer | Yes* | Display order (*per item) |
| recruiter_id | string (uuid) | null | No | Assigned recruiter user id |
| skip_ai_enrichment | boolean | No | true skips JD parsing, embedding, and video question generation |
| metadata | object | No | Custom JSON (e.g. external_id) |
Response body
Single-resource responses return { "data": { ... } }. Lists return { "data": [ ... ], "meta": { ... } }.
| Field | Type | Required | Description |
|---|---|---|---|
| id | string (uuid) | — | Job id |
| title | string | — | Job title |
| jd_text | string | null | — | HTML job description |
| description | string | null | — | Plain description |
| client_id | string (uuid) | null | — | Client id or null for direct hire |
| client_name | string | null | — | Resolved client name |
| client_name_not_disclosed | boolean | — | Client hidden on public surfaces |
| is_direct_hire | boolean | — | true when client_id is null |
| public_company_name | string | — | Name shown to candidates on public job page |
| locations | string[] | — | City names |
| work_mode | string | null | — | onsite, remote, or hybrid |
| job_type | string | null | — | full_time or contract |
| openings_count | integer | — | Number of openings |
| filled_count | integer | — | Positions filled |
| status | string | — | draft | active | paused | closed | filled |
| min_experience_years | number | null | — | Min experience (years) |
| max_experience_years | number | null | — | Max experience (years) |
| currency | string | null | — | INR, USD, EUR, or GBP |
| min_ctc | number | null | — | Min CTC (LPA) |
| max_ctc | number | null | — | Max CTC (LPA) |
| ctc_not_disclosed | boolean | — | Salary hidden publicly |
| rate | number | null | — | Contract rate |
| required_skills | string[] | — | Required skills |
| preferred_skills | string[] | — | Preferred skills |
| required_skills_weighted | object[] | — | AI-weighted required skills |
| preferred_skills_weighted | object[] | — | AI-weighted preferred skills |
| screening_questions | object[] | null | — | Screening question config |
| video_interview_questions | object[] | — | Generated video interview questions |
| applicant_count | integer | — | Count of candidate_jobs rows for this job; updates when applications are created or deleted via Applications API or Dashboard |
| recruiter_id | string (uuid) | null | — | Recruiter user id |
| metadata | object | null | — | Custom metadata |
| created_at | string (ISO 8601 date-time) | — | Created timestamp |
| updated_at | string (ISO 8601 date-time) | — | Updated timestamp |
| Field | Type | Required | Description |
|---|---|---|---|
| meta.total | integer | — | Total matching records |
| meta.limit | integer | — | Page size used |
| meta.offset | integer | — | Offset used |
Endpoints
/api/v1/jobsList jobs.
/api/v1/jobs/{id}Get one job, including applicant_count.
/api/v1/jobsCreate a job. Returns 201 with the job object in data.
{
"title": "Senior React Developer",
"jd_text": "<p>Build product features with React...</p>",
"client_id": null,
"client_name_not_disclosed": false,
"locations": ["Bangalore", "Mumbai"],
"work_mode": "hybrid",
"job_type": "full_time",
"openings_count": 1,
"status": "active",
"min_experience_years": 4,
"max_experience_years": 8,
"currency": "INR",
"min_ctc": 18,
"max_ctc": 28,
"ctc_not_disclosed": false,
"required_skills": ["React", "TypeScript"],
"preferred_skills": ["Next.js"],
"skip_ai_enrichment": false,
"metadata": { "external_id": "ATS-JOB-4421" }
}{
"title": "DevOps Consultant",
"job_type": "contract",
"currency": "INR",
"rate": 2500,
"work_mode": "remote",
"locations": ["Hyderabad"],
"status": "draft"
}/api/v1/jobs/{id}Partial update — send only fields to change.
Related: applications on a job
After creating a job, use POST /applications or POST /applications/bulk to link candidates. List pipeline rows with GET /applications?job_id={id}. See the Applications API for idempotency, pipeline status side effects, and bulk import details.
# List all applications for a job (pipeline rows)
GET /api/v1/applications?job_id=660e8400-e29b-41d4-a716-446655440001
# Filter by pipeline status
GET /api/v1/applications?job_id=660e8400-...&status=Applied
# Search applicants by name, email, or job title
GET /api/v1/applications?job_id=660e8400-...&search=Jane# Typical ATS sync: create job, then apply candidates
# 1. Create or upsert the job (dedupe via metadata.external_id in your system)
POST /api/v1/jobs
{ "title": "Senior Engineer", "status": "active", "metadata": { "external_id": "ATS-99" } }
# → 201; note job id from response
# 2. Create or upsert candidates
POST /api/v1/candidates { "full_name": "Jane Doe", "email": "[email protected]", ... }
# 3. Apply candidates to the job (idempotent)
POST /api/v1/applications { "candidate_id": "...", "job_id": "660e8400-..." }
# Or bulk apply up to 100 at once:
POST /api/v1/applications/bulk { "job_id": "660e8400-...", "candidate_ids": ["...", "..."] }
# 4. Verify applicant_count on the job increased
GET /api/v1/jobs/660e8400-e29b-41d4-a716-446655440001
# → data.applicant_count reflects candidate_jobs rows