Skip to main content

Overview

JobSpy JS provides two functions for fetching detailed information about a single job posting:
  • fetchJobDetails() — Universal function that works with all supported sites
  • fetchLinkedInJob() — LinkedIn-specific function with enhanced metadata
Use these functions when you have a job ID (from search results or a URL) and need the complete job description and metadata.

fetchJobDetails()

Fetch full details for a single job by ID on any supported provider.
import { fetchJobDetails } from "jobspy-js";

const job = await fetchJobDetails("indeed", "fdde406379455a1e");
console.log(job?.title);        // job title
console.log(job?.company);      // company name
console.log(job?.description);  // full description

Function Signature

async function fetchJobDetails(
  site: string,
  jobId: string,
  options?: {
    format?: string;
    proxies?: string | string[];
    country?: string;
    credentials?: ProviderCredentials;
    useCreds?: boolean;
  }
): Promise<FlatJobRecord | null>

Parameters

site
string
required
Site name. Must be one of the supported sites.Valid values: indeed, linkedin, glassdoor, zip_recruiter, bayt, naukri, bdjobs
site: "indeed"
site: "linkedin"
site: "glassdoor"
google and google_careers do not support single job fetching.
jobId
string
required
Provider-specific job ID. The ID format varies by site:
SiteID FormatExample
IndeedJob key"fdde406379455a1e"
LinkedInNumeric job ID"4127292817"
GlassdoorListing ID"123456789"
ZipRecruiterListing key"some-listing-key"
BaytJob path"/en/job-title-1234567"
NaukriNumeric ID"123456789"
BDJobsNumeric ID"123456"
jobId: "fdde406379455a1e"  // Indeed
jobId: "4127292817"        // LinkedIn
options.format
string
default:"markdown"
Description format. Same as description_format in scrapeJobs().Options: markdown, html, plain
format: "markdown"  // Convert HTML to Markdown
format: "html"      // Keep original HTML
format: "plain"     // Strip all markup
options.proxies
string | string[]
Proxy server(s) for the request. See Proxies for details.
proxies: "user:[email protected]:8080"
proxies: ["proxy1.com:8080", "proxy2.com:8080"]
options.country
string
default:"usa"
Country code for Indeed/Glassdoor regional domains.
country: "usa"
country: "uk"
country: "germany"
options.credentials
ProviderCredentials
Authentication credentials. See Authentication for details.
credentials: {
  linkedin: {
    username: "[email protected]",
    password: "secret"
  }
}
options.useCreds
boolean
default:"false"
Enable credential fallback when anonymous scraping is blocked.
useCreds: true

Return Value

Returns a FlatJobRecord (same shape as search results) or null if the job wasn’t found.
interface FlatJobRecord {
  id?: string;
  site: string;
  title: string;
  company?: string;
  location?: string;
  description?: string;
  job_url: string;
  job_url_direct?: string;
  date_posted?: string;
  job_type?: string;
  min_amount?: number;
  max_amount?: number;
  interval?: string;
  currency?: string;
  salary_source?: string;
  is_remote?: boolean;
  // ... and more fields
}

Examples

Fetch Indeed Job

import { fetchJobDetails } from "jobspy-js";

const job = await fetchJobDetails("indeed", "fdde406379455a1e");

if (job) {
  console.log(job.title);
  console.log(job.company);
  console.log(job.location);
  console.log(job.description);
  console.log(job.salary_source);
} else {
  console.log("Job not found");
}

Fetch Glassdoor Job with HTML Format

const job = await fetchJobDetails("glassdoor", "123456789", {
  format: "html",
  country: "uk",
});

console.log(job?.description); // HTML format

Fetch ZipRecruiter Job via Proxy

const job = await fetchJobDetails("zip_recruiter", "listing-key", {
  proxies: "user:[email protected]:8080",
});

With Authentication

const job = await fetchJobDetails("linkedin", "4127292817", {
  useCreds: true,
  credentials: {
    linkedin: {
      username: process.env.LINKEDIN_USERNAME!,
      password: process.env.LINKEDIN_PASSWORD!,
    },
  },
});

fetchLinkedInJob()

LinkedIn-specific function for fetching job details. Returns enhanced metadata like seniority level, job function, and company industry.
import { fetchLinkedInJob } from "jobspy-js";

const job = await fetchLinkedInJob("4127292817");
console.log(job.description);
console.log(job.job_level);          // "mid-senior level"
console.log(job.company_industry);   // "Software Development"

Function Signature

interface LinkedInJobDetails {
  job_url: string;
  description?: string;
  job_level?: string;
  job_type?: string[];
  job_function?: string;
  company_industry?: string;
  company_logo?: string;
  job_url_direct?: string;
}

async function fetchLinkedInJob(
  jobIdOrUrl: string,
  options?: {
    format?: string;
    proxies?: string | string[];
  }
): Promise<LinkedInJobDetails>

Parameters

jobIdOrUrl
string
required
LinkedIn job ID or full LinkedIn job URL.
// By job ID
jobIdOrUrl: "4127292817"

// By full URL
jobIdOrUrl: "https://www.linkedin.com/jobs/view/4127292817"
The function automatically extracts the job ID from URLs.
options.format
string
default:"markdown"
Description format.Options: markdown, html, plain
format: "plain"
options.proxies
string | string[]
Proxy server(s) for the request.
proxies: "user:[email protected]:8080"

Return Value

Returns a LinkedInJobDetails object:
FieldTypeDescription
job_urlstringCanonical LinkedIn job URL
descriptionstringFull job description (formatted per format option)
job_levelstringSeniority level (e.g. "mid-senior level", "entry level")
job_typestring[]Employment types (e.g. ["fulltime"], ["contract", "parttime"])
job_functionstringJob function category (e.g. "Engineering", "Sales")
company_industrystringCompany industry classification (e.g. "Software Development")
company_logostringCompany logo image URL
job_url_directstringDirect employer/ATS application URL (if available)

Examples

Fetch by Job ID

import { fetchLinkedInJob } from "jobspy-js";

const job = await fetchLinkedInJob("4127292817");

console.log(job.job_url);           // "https://www.linkedin.com/jobs/view/4127292817"
console.log(job.description);       // Full job description (markdown)
console.log(job.job_level);         // "mid-senior level"
console.log(job.job_type);          // ["fulltime"]
console.log(job.job_function);      // "Engineering"
console.log(job.company_industry);  // "Software Development"
console.log(job.job_url_direct);    // Direct application URL

Fetch by URL

const job = await fetchLinkedInJob(
  "https://www.linkedin.com/jobs/view/4127292817"
);

console.log(job.description);

Plain Text Format

const job = await fetchLinkedInJob("4127292817", {
  format: "plain",
});

console.log(job.description); // Plain text, no formatting

With Proxy

const job = await fetchLinkedInJob("4127292817", {
  format: "plain",
  proxies: "user:[email protected]:8080",
});

Extracting Job IDs from Search Results

When using scrapeJobs(), each job record includes an id field with a site-prefixed ID:
import { scrapeJobs, fetchJobDetails } from "jobspy-js";

// Search for jobs
const { jobs } = await scrapeJobs({
  site_name: ["indeed", "linkedin"],
  search_term: "software engineer",
  results_wanted: 5,
});

// Fetch full details for the first job
const firstJob = jobs[0];
console.log(firstJob.id);    // "in-fdde406379455a1e" (Indeed)
console.log(firstJob.site);  // "indeed"

// Strip the site prefix to get the raw ID
const rawId = firstJob.id?.replace(/^[a-z]+-/, "");

// Fetch full details
const details = await fetchJobDetails(firstJob.site, rawId!);
console.log(details?.description);
Job IDs are prefixed with the site code in search results (e.g. "in-" for Indeed, "li-" for LinkedIn). Strip the prefix when passing to fetchJobDetails().

Extracting Job IDs from URLs

LinkedIn

LinkedIn job URLs have this format:
https://www.linkedin.com/jobs/view/4127292817
The job ID is the last segment: 4127292817 fetchLinkedInJob() automatically extracts the ID from URLs, so you can pass either format.

Indeed

Indeed job URLs include a jk query parameter:
https://www.indeed.com/viewjob?jk=fdde406379455a1e
The job ID is the jk value: fdde406379455a1e
const url = new URL("https://www.indeed.com/viewjob?jk=fdde406379455a1e");
const jobId = url.searchParams.get("jk");

const job = await fetchJobDetails("indeed", jobId!);

Glassdoor

Glassdoor job URLs include the listing ID in the path:
https://www.glassdoor.com/job-listing/software-engineer-company-JV_IC1234567_KO0,17_KE18,25.htm?jl=123456789
The job ID is the jl query parameter: 123456789
const url = new URL("https://www.glassdoor.com/job-listing/...");
const jobId = url.searchParams.get("jl");

const job = await fetchJobDetails("glassdoor", jobId!);

Error Handling

Both functions return null (or throw) when the job cannot be fetched:
try {
  const job = await fetchJobDetails("indeed", "invalid-id");
  
  if (!job) {
    console.log("Job not found or has been removed");
  }
} catch (err) {
  console.error("Failed to fetch job:", err);
}
try {
  const job = await fetchLinkedInJob("invalid-id");
  console.log(job.description);
} catch (err) {
  console.error("Failed to fetch LinkedIn job:", err);
}

When to Use Each Function

Use CaseFunction to Use
Fetch job from any sitefetchJobDetails()
Need LinkedIn-specific metadata (seniority, function, industry)fetchLinkedInJob()
Have a LinkedIn URLfetchLinkedInJob() (auto-extracts ID)
Need unified output format across sitesfetchJobDetails()
Building a multi-site job aggregatorfetchJobDetails()

See Also

Build docs developers (and LLMs) love