Jobs
The Jobs endpoint lets you search and retrieve job listings with powerful filtering options.
List Jobs
GET /v1/public/jobs
Returns a paginated list of jobs matching your filters.
curl -H "X-API-Key: $JOBVEN_API_KEY" \
"https://api.jobven.com/v1/public/jobs?skills%5B%5D=react&remoteType%5B%5D=remote&limit=20"
const params = new URLSearchParams({ limit: '20' });
params.append('skills[]', 'react');
params.append('remoteType[]', 'remote');
const response = await fetch(
`https://api.jobven.com/v1/public/jobs?${params}`,
{ headers: { 'X-API-Key': process.env.JOBVEN_API_KEY } }
);
const { data, meta } = await response.json();
import requests
import os
response = requests.get(
'https://api.jobven.com/v1/public/jobs',
headers={'X-API-Key': os.environ['JOBVEN_API_KEY']},
params={'skills[]': 'react', 'remoteType[]': 'remote', 'limit': 20}
)
data = response.json()
Query Parameters
| Parameter | Type | Description |
|---|---|---|
q | string | Search in title, summary, and description |
skills[] | string | Filter by skills — OR logic (e.g., ?skills[]=react&skills[]=typescript) |
industry[] | string | Filter by industry — OR logic (e.g., ?industry[]=technology_software) |
language | string | Required language (ISO 639-1, e.g., fr) |
location | string | City, region, or country name |
country | string | Country code (ISO 3166-1, e.g., US) |
state | string | State codes, comma-separated (e.g., CA,NY,TX) |
city | string | City names, comma-separated (e.g., San Francisco,Austin) |
remoteType[] | enum | onsite, remote, hybrid, flexible — OR logic |
experienceLevel[] | enum | entry, mid, senior, lead, executive — OR logic |
employmentType[] | enum | full_time, part_time, contract, internship, seasonal, volunteer, temporary, per_diem, other — OR logic |
minSalary | number | Minimum salary |
maxSalary | number | Maximum salary |
hasVisaSponsorship | boolean | Jobs with visa sponsorship |
hasRelocation | boolean | Jobs with relocation assistance |
status | enum | active (default), closed |
postedAfter | number | Jobs posted after timestamp (Unix seconds) |
postedBefore | number | Jobs posted before timestamp (Unix seconds) |
cursor | string | Pagination cursor from previous response |
limit | number | Results per page (max varies by tier) |
includeTotal | boolean | Return meta.total, the exact number of matching jobs. Off by default, and slower when on. See Getting a total count |
sortBy | enum | postedAt (default), title, company |
order | enum | ASC, DESC (default) |
descriptionFormat | enum | html (default), plain, markdown, all — see Description Formats |
postedAfter for incremental syncing. Store the last sync timestamp and only fetch new jobs.Array Parameters
To filter by multiple values, use bracket notation and repeat the parameter:
GET /v1/public/jobs?skills[]=react&skills[]=typescript&remoteType[]=remote&remoteType[]=hybrid
Description Formats
Job descriptions are stored as sanitized HTML. Use descriptionFormat to request the format your pipeline needs — HTML, plain text, markdown, or all three. All formats are precomputed at ingest time, so responses stay fast even when you request all.
| Value | Fields populated | Use case |
|---|---|---|
html (default) | description | Rendering in web UIs; backwards-compatible for existing integrations |
plain | descriptionPlain | Search indexing, email digests, previews |
markdown | descriptionMarkdown | Markdown-native UIs, LLM / RAG pipelines, static site generators |
all | description, descriptionPlain, descriptionMarkdown | Dual rendering, A/B comparisons |
When you request plain or markdown, the HTML description field is omitted — you only get the format you asked for. all returns every format.
GET /v1/public/jobs?descriptionFormat=markdown
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Senior React Developer",
"descriptionMarkdown": "## About the role\n\nWe are looking for...\n\n- 5+ years React\n- TypeScript fluency"
}
]
}
descriptionPlain uses html-to-text; descriptionMarkdown uses Turndown with ATX headings, - bullets, and fenced code blocks. Output is stable across requests.Response
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Senior React Developer",
"description": "<p>We are looking for...</p>",
"summary": "Build modern web applications with React",
"locations": [
{
"addressLocality": "San Francisco",
"addressRegion": "California",
"addressCountry": "US",
"workLocation": "hybrid"
}
],
"remoteType": "hybrid",
"skills": {
"primary_skills": ["React", "TypeScript"],
"secondary_skills": ["Node.js", "PostgreSQL"],
"soft_skills": ["Communication", "Teamwork"]
},
"salary": {
"min": 150000,
"max": 200000,
"currency": "USD",
"period": "annual"
},
"benefits": ["health_insurance", "pensions_401k", "remote_work_hybrid_work_options"],
"experienceLevel": "senior",
"employmentType": "full_time",
"minYearsExperience": 5,
"educationRequirement": {
"required": ["Bachelor's degree"],
"preferred": ["Master's degree"]
},
"industry": ["technology_software"],
"inLanguage": ["en"],
"hasVisaSponsorship": true,
"hasRelocation": false,
"requiresTravel": false,
"postedAt": 1705392000,
"expiresAt": 1708070400,
"applyUrl": "https://company.com/apply/123",
"status": "active",
"companies": [
{
"name": "TechCorp Inc.",
"website": "https://techcorp.com"
}
]
}
],
"meta": {
"count": 20,
"nextCursor": "eyJpZCI6IjEyMyIsInZhbHVlIjoxNzA1MzkyMDAwMDAwfQ==",
"hasMore": true,
"requestTimeMs": 45,
"lastUpdatedAt": "2025-01-15T12:00:00Z"
}
}
Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique job identifier (UUID) |
title | string | Job title |
description | string | Full job description (sanitized HTML). Omitted when descriptionFormat=plain or descriptionFormat=markdown |
descriptionPlain | string | Plain-text description. Returned when descriptionFormat=plain or descriptionFormat=all |
descriptionMarkdown | string | Markdown description. Returned when descriptionFormat=markdown or descriptionFormat=all |
summary | string | Brief summary |
locations | array | Location objects (see below) |
remoteType | string | onsite, remote, hybrid, flexible |
skills | object | { primary_skills, secondary_skills, soft_skills } |
salary | object | { min, max, currency, period } |
benefits | string | List of benefits |
experienceLevel | string | entry, mid, senior, lead, executive |
employmentType | string | full_time, part_time, contract, etc. |
minYearsExperience | number | Minimum years required |
educationRequirement | object | { required, preferred } |
industry | string | Industry categories |
inLanguage | string | Required languages (ISO 639-1) |
hasVisaSponsorship | boolean | Visa sponsorship available |
hasRelocation | boolean | Relocation assistance available |
requiresTravel | boolean | Travel required |
travelPercentage | number | Required travel as percentage (0-100) |
postedAt | number | Posted timestamp (Unix seconds) |
expiresAt | number | Expiration timestamp (Unix seconds) |
applyUrl | string | URL to apply |
status | string | active — the role is being advertised; closed — the employer's board no longer lists it |
companies | array | [{ name, website }] |
Location Object
| Field | Type | Description |
|---|---|---|
streetAddress | string | Street address |
addressLocality | string | City |
addressRegion | string | State/Province |
postalCode | string | Postal/ZIP code |
addressCountry | string | Country (ISO 3166-1 alpha-2), or XX. May be absent. |
workLocation | string | onsite, remote, hybrid, flexible |
XX means the job carries no location information at all — typically "work from anywhere" positions. A location with a city but no addressCountry is one whose country we couldn't identify, and it matches no country filter. See Data Types for details.Getting a total count
meta.total is not returned by default. Pass includeTotal=true when you need it:
GET /v1/public/jobs?country=US&includeTotal=true
Counting every matching job is the slowest part of a request, and it gets slower as the dataset grows, so it is opt-in rather than a cost every caller pays. Most integrations never need it: to page through results you use hasMore and nextCursor, and to show progress you count what you have received.
When you do request it, the number is exact for your filters, and may be up to five minutes old.
Meta Object
| Field | Type | Description |
|---|---|---|
total | number | Exact number of matching jobs. Only present when you pass includeTotal=true |
count | number | Jobs in this response |
nextCursor | string | Cursor for next page (null if no more) |
hasMore | boolean | More results exist |
requestTimeMs | number | Response time in milliseconds |
lastUpdatedAt | string | When dataset was last updated (ISO 8601) |
Handling Null Values
Most job fields are optional and may be null. Guaranteed fields: id, title, status, companies, and the description field matching your requested format — description when descriptionFormat=html (default) or all, descriptionPlain when plain, descriptionMarkdown when markdown.
// Safe access pattern
const salary = job.salary?.min
? `$${job.salary.min.toLocaleString()}+`
: 'Not specified';
const skills = job.skills?.primary_skills?.join(', ') || 'Not listed';
See Data Types - Handling Null Values for complete guidance.
Get Job by ID
GET /v1/public/jobs/{id}
Retrieve a single job by its UUID.
curl -H "X-API-Key: $JOBVEN_API_KEY" \
"https://api.jobven.com/v1/public/jobs/550e8400-e29b-41d4-a716-446655440000"
const response = await fetch(
'https://api.jobven.com/v1/public/jobs/550e8400-e29b-41d4-a716-446655440000',
{ headers: { 'X-API-Key': process.env.JOBVEN_API_KEY } }
);
const job = await response.json();
response = requests.get(
'https://api.jobven.com/v1/public/jobs/550e8400-e29b-41d4-a716-446655440000',
headers={'X-API-Key': os.environ['JOBVEN_API_KEY']}
)
job = response.json()
Path Parameters
| Parameter | Type | Description |
|---|---|---|
id | string | Job UUID |
Response
Returns a single job object (same schema as list response items).
Errors
| Code | Description |
|---|---|
404 | Job not found |
Pagination
Jobs use cursor-based pagination for efficient traversal of large datasets.
How It Works
- Make your first request without a
cursor - Use
meta.nextCursorfrom the response for the next page - Continue until
meta.hasMoreisfalse
let cursor = null;
const allJobs = [];
do {
const url = new URL('https://api.jobven.com/v1/public/jobs');
url.searchParams.set('limit', '100');
if (cursor) url.searchParams.set('cursor', cursor);
const response = await fetch(url, {
headers: { 'X-API-Key': process.env.JOBVEN_API_KEY }
});
const { data, meta } = await response.json();
allJobs.push(...data);
cursor = meta.nextCursor;
} while (cursor);
console.log(`Fetched ${allJobs.length} jobs`);
Page Size Limits
| Tier | Max per page | Default |
|---|---|---|
| Free | 25 | 10 |
| Starter | 50 | 10 |
| Growth+ | 100 | 10 |
See also
- Webhooks — push the same job lifecycle events (
job.created,job.updated,job.closed,job.deleted) to your endpoint in real time instead of polling.