# LinkedIn Job Search via OpenWeb Ninja

> LinkedIn Job Search via OpenWeb Ninja is a paid API for AI agents from agents.openwebninja.com, paid per call via MPP, price unknown, status unknown (last checked 2026-10-14).

Search LinkedIn job postings by keyword, location, and filters, returning paginated structured job data including descriptions, salary, and applicant counts.

## Facts

- Endpoint: GET https://agents.openwebninja.com/realtime-linkedin-data/search?utm_source=zero.xyz
- Price: price unknown
- Payment: MPP
- Status: unknown
- Last checked: 2026-10-14
- Activations on Zero: 0
- Provider: agents.openwebninja.com
- Website: https://agents.openwebninja.com
- Canonical page: https://www.zero.xyz/c/agents-openwebninja-com-linkedin-job-search-via-openweb-ninja-cfb74f9d
- Structured record (JSON): https://api.zero.xyz/v1/capabilities/cap_ewxed4c21GYvWVRKlsJ-7

Status and success rate cover calls made through Zero and Zero's own probes. Third-party monitors may report differently.

## How to call it through Zero

Zero handles the 402 payment challenge and records the run. With the Zero CLI installed (`npm i -g @zeroxyz/cli`):

```sh
zero fetch --capability agents-openwebninja-com-linkedin-job-search-via-openweb-ninja-cfb74f9d
```

Example prompt: Search LinkedIn for senior software engineer jobs in New York City — show me up to 20 listings posted in the past week, sorted by date, and include full job descriptions and salary info where available.

## When to prefer this

Choose this endpoint when you need structured LinkedIn job data — especially rich fields like job descriptions, salary, seniority level, applicant counts, and employment type — in a JSearch-compatible envelope. Prefer it over generic job search APIs when LinkedIn-specific data quality (unique posting IDs, no cross-page duplicates, LinkedIn company IDs) matters. Use include_details=false for fast card-only scanning and include_details=true when you need full job details for summarization or matching.

## Known failure modes

- 400 if query parameter is missing or duplicated
- 400 if radius is provided without geo_id
- 400 if an unknown field name is passed in the fields projection
- 400 if page + num_pages would exceed LinkedIn's 1000-result (page 100) ceiling
- Fewer results than requested if the result set is exhausted — no has_more flag; detect by count < num_pages × 10
- Slower response (~3x) when include_details=true due to per-job detail fetches
- Over-billing when employment_types, seniority_levels, or remote_jobs_only filters trigger up to 3x page over-fetch

## How this service works

Search LinkedIn job postings.

**Pagination** is offset-based via `page` / `num_pages`. Page size is fixed at 10 by LinkedIn. Unlike the Bing jobs source, LinkedIn does not repeat a posting across consecutive pages (measured: 300/300 unique ids over 30 contiguous pages), so `num_pages=N` returns exactly `N x 10` jobs up to the end of the result set.

**`include_details` (default `true`)** issues one extra request per job, bounded to 10 concurrent and capped at 100 jobs per request. It is what fills `job_description`, `job_employment_type(s)`, salary, `job_seniority_level`, `job_function`, `job_industries`, `job_applicants_count` and `job_linkedin_company_id` — none of which exist on a search card. Set it to `false` for a ~3x faster, card-only response.

**Knowing when to stop:** the response is a flat array, matching the JSearch `/search` envelope, so there is no `has_more` flag. A page that returns fewer than `num_pages x 10` jobs is the last one, as is `page` 100.

**Billing:** `Requests` counts one per upstream page of 10 actually fetched. Using `employment_types`, `seniority_levels` or `remote_jobs_only` over-fetches up to 3x the requested pages (capped at 10) to refill the page after local filtering, and those extra pages are billed.

## Output

A flat JSON array of job objects (up to num_pages × 10 per request) each containing job_id, job_title, employer_name, employer_logo, job_location, job_city, job_state, job_country, job_apply_link, job_posted_at, job_posted_at_datetime_utc, and (when include_details=true) job_description, job_employment_type, job_min_salary, job_max_salary, job_salary_currency, job_seniority_level, job_function, job_industries, job_applicants_count, and job_linkedin_company_id.

## Request schema (JSON Schema)

```json
{
 "type": "object",
 "required": [
  "query"
 ],
 "properties": {
  "page": {
   "type": "integer",
   "default": 1,
   "maximum": 100,
   "minimum": 1,
   "description": "1-based page number, 10 jobs per page. Capped at 100 by LinkedIn's 1000-result ceiling; `page + num_pages` may not cross it either."
  },
  "query": {
   "type": "string",
   "example": "software engineer",
   "description": "Free-form job search query (LinkedIn's `keywords`). Only one `query` is accepted; a second one is rejected with a 400 rather than silently ignored."
  },
  "fields": {
   "type": "string",
   "description": "Comma-separated projection over the job object. `job_id` and `job_title` are always included. An unknown field is a 400."
  },
  "geo_id": {
   "type": "string",
   "description": "LinkedIn numeric geo id (e.g. `102571732` = New York City, `103644278` = United States). Required if you want `radius` to take effect. There is no geo-id lookup endpoint in this API yet — take the value from a `geoId=` parameter on a linkedin.com/jobs URL."
  },
  "radius": {
   "type": "integer",
   "minimum": 1,
   "description": "Search radius in **kilometres**, converted to the miles LinkedIn expects. Passed through continuously, not as a coarse menu. **Requires `geo_id`.** LinkedIn ignores `distance` with a free-text location, so `radius` without `geo_id` is rejected with a 400 rather than silently doing nothing."
  },
  "country": {
   "type": "string",
   "default": "us",
   "description": "2-letter country code. Selects the proxy exit market and is the fallback for `job_country` when the posting's own location does not name a country. Geography is driven primarily by `location` / `geo_id`, not by this."
  },
  "sort_by": {
   "enum": [
    "relevance",
    "date"
   ],
   "type": "string",
   "default": "relevance",
   "description": "`relevance` leaves LinkedIn's own ordering alone. `date` sorts **the jobs in this response** newest-first — it does not re-rank the result set upstream, because LinkedIn ignores its own `sortBy` on the logged-out surface. Combine with a larger `num_pages` for a more meaningful ordering, or with `date_posted` for real freshness control."
  },
  "location": {
   "type": "string",
   "description": "Free-text location. Works internationally with no geo id (\"London, United Kingdom\", \"Berlin, Germany\"). Mutually usable with `geo_id`, but `radius` only applies to `geo_id`."
  },
  "num_pages": {
   "type": "integer",
   "default": 1,
   "maximum":
… (truncated)
```

## Response schema (JSON Schema)

```json
{
 "type": "object",
 "required": [
  "status",
  "request_id",
  "parameters",
  "data"
 ],
 "properties": {
  "data": {
   "type": "array",
   "items": {
    "type": "object",
    "required": [
     "job_id",
     "job_title",
     "employer_name",
     "employer_logo",
     "employer_website",
     "job_publisher",
     "job_employment_type",
     "job_employment_types",
     "job_apply_link",
     "job_apply_is_direct",
     "apply_options",
     "job_description",
     "job_is_remote",
     "job_posted_at",
     "job_posted_at_timestamp",
     "job_posted_at_datetime_utc",
     "job_location",
     "job_city",
     "job_state",
     "job_country",
     "job_latitude",
     "job_longitude",
     "job_benefits",
     "job_benefits_strings",
     "job_google_link",
     "job_salary",
     "job_salary_string",
     "job_min_salary",
     "job_max_salary",
     "job_salary_period",
     "job_highlights",
     "job_onet_soc",
     "job_onet_job_zone",
     "employer_reviews",
     "job_uid",
     "employer_linkedin_url",
     "job_linkedin_company_id",
     "job_seniority_level",
     "job_function",
     "job_industries",
     "job_applicants_count",
     "job_applicants_label",
     "job_salary_currency",
     "job_posted_at_date",
     "job_posted_at_linkedin_label",
     "job_linkedin_badges",
     "job_position"
    ],
    "properties": {
     "job_id": {
      "type": "string",
      "example": "bGlua2VkaW46NDQ1MzY3MDk3Mw",
      "description": "Opaque public id: `base64url(\"linkedin:\" + <posting id>)`. Pass it back to `/job-details`. Stable for the life of the posting."
     },
     "job_uid": {
      "type": "string",
      "example": "4453670973",
      "description": "The raw numeric LinkedIn posting id. Permanent, and accepted directly by `/job-details`."
     },
     "job_city": {
      "type": "string",
      "example": "New York",
      "nullable": true
     },
     "job_state": {
      "type": "string",
      "example": "New York",
      "nullable": true,
      "description": "US 2-letter codes are expanded to the full state name, matching JSearch."
     },
     "job_title": {
      "type": "string",
      "example": "Software Engineer III (Java/AWS)"
     },
     "job_salary": {
      "type": "number",
      "example": null,
      "nullable": true,
      "description": "Set only when a posting states a single figure rather than a range; a range populates `job_min_salary` / `job_max_salary` instead."
     },
     "job_country": {
      "typ
… (truncated)
```

## More

- Live health (JSON, refreshed every minute): https://www.zero.xyz/c/agents-openwebninja-com-linkedin-job-search-via-openweb-ninja-cfb74f9d/health.json
- [Zero catalog index](https://www.zero.xyz/llms.txt)
- [Other services from agents.openwebninja.com](https://www.zero.xyz/host/agents.openwebninja.com/llms.txt)
