Jobs

Search and retrieve job listings. Filter by skills, location, remote type, salary, and more.

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"

Query Parameters

ParameterTypeDescription
qstringSearch in title, summary, and description
skills[]stringFilter by skills — OR logic (e.g., ?skills[]=react&skills[]=typescript)
industry[]stringFilter by industry — OR logic (e.g., ?industry[]=technology_software)
languagestringRequired language (ISO 639-1, e.g., fr)
locationstringCity, region, or country name
countrystringCountry code (ISO 3166-1, e.g., US)
statestringState codes, comma-separated (e.g., CA,NY,TX)
citystringCity names, comma-separated (e.g., San Francisco,Austin)
remoteType[]enumonsite, remote, hybrid, flexible — OR logic
experienceLevel[]enumentry, mid, senior, lead, executive — OR logic
employmentType[]enumfull_time, part_time, contract, internship, seasonal, volunteer, temporary, per_diem, other — OR logic
minSalarynumberMinimum salary
maxSalarynumberMaximum salary
hasVisaSponsorshipbooleanJobs with visa sponsorship
hasRelocationbooleanJobs with relocation assistance
statusenumactive (default), closed
postedAfternumberJobs posted after timestamp (Unix seconds)
postedBeforenumberJobs posted before timestamp (Unix seconds)
cursorstringPagination cursor from previous response
limitnumberResults per page (max varies by tier)
includeTotalbooleanReturn meta.total, the exact number of matching jobs. Off by default, and slower when on. See Getting a total count
sortByenumpostedAt (default), title, company
orderenumASC, DESC (default)
descriptionFormatenumhtml (default), plain, markdown, all — see Description Formats
See Also: For complete lists of all enum values (industries, experience levels, employment types, etc.), see the Data Types Reference.
Tip: Use 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.

ValueFields populatedUse case
html (default)descriptionRendering in web UIs; backwards-compatible for existing integrations
plaindescriptionPlainSearch indexing, email digests, previews
markdowndescriptionMarkdownMarkdown-native UIs, LLM / RAG pipelines, static site generators
alldescription, descriptionPlain, descriptionMarkdownDual 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"
    }
  ]
}
Formats are deterministic.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

FieldTypeDescription
idstringUnique job identifier (UUID)
titlestringJob title
descriptionstringFull job description (sanitized HTML). Omitted when descriptionFormat=plain or descriptionFormat=markdown
descriptionPlainstringPlain-text description. Returned when descriptionFormat=plain or descriptionFormat=all
descriptionMarkdownstringMarkdown description. Returned when descriptionFormat=markdown or descriptionFormat=all
summarystringBrief summary
locationsarrayLocation objects (see below)
remoteTypestringonsite, remote, hybrid, flexible
skillsobject{ primary_skills, secondary_skills, soft_skills }
salaryobject{ min, max, currency, period }
benefitsstringList of benefits
experienceLevelstringentry, mid, senior, lead, executive
employmentTypestringfull_time, part_time, contract, etc.
minYearsExperiencenumberMinimum years required
educationRequirementobject{ required, preferred }
industrystringIndustry categories
inLanguagestringRequired languages (ISO 639-1)
hasVisaSponsorshipbooleanVisa sponsorship available
hasRelocationbooleanRelocation assistance available
requiresTravelbooleanTravel required
travelPercentagenumberRequired travel as percentage (0-100)
postedAtnumberPosted timestamp (Unix seconds)
expiresAtnumberExpiration timestamp (Unix seconds)
applyUrlstringURL to apply
statusstringactive — the role is being advertised; closed — the employer's board no longer lists it
companiesarray[{ name, website }]

Location Object

FieldTypeDescription
streetAddressstringStreet address
addressLocalitystringCity
addressRegionstringState/Province
postalCodestringPostal/ZIP code
addressCountrystringCountry (ISO 3166-1 alpha-2), or XX. May be absent.
workLocationstringonsite, remote, hybrid, flexible
Special Value: Country code 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

FieldTypeDescription
totalnumberExact number of matching jobs. Only present when you pass includeTotal=true
countnumberJobs in this response
nextCursorstringCursor for next page (null if no more)
hasMorebooleanMore results exist
requestTimeMsnumberResponse time in milliseconds
lastUpdatedAtstringWhen 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"

Path Parameters

ParameterTypeDescription
idstringJob UUID

Response

Returns a single job object (same schema as list response items).

Errors

CodeDescription
404Job not found

Pagination

Jobs use cursor-based pagination for efficient traversal of large datasets.

Cursor pagination is more efficient than offset pagination for large datasets. The cursor encodes the position in the result set.

How It Works

  1. Make your first request without a cursor
  2. Use meta.nextCursor from the response for the next page
  3. Continue until meta.hasMore is false
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

TierMax per pageDefault
Free2510
Starter5010
Growth+10010

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.