> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pubrio.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Job Search

> Find open job postings by company, title, function, seniority, country and launch date — and read the classification Pubrio adds to every posting.

[Job Search](/en/api-reference/endpoint/companies/job_search) returns open job postings Pubrio has captured, one row per posting, each linked to the company that published it. Every row carries the raw posting (title, location, URL, publisher date) plus three fields Pubrio derives from the title: `functions`, `seniority_rank` and `location_id`.

```bash theme={null}
curl -X POST https://api.pubrio.com/companies/jobs/search \
  -H "pubrio-api-key: $PUBRIO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domains": ["stripe.com"],
    "seniority_ranks": [4, 5],
    "launch_dates": ["2026-08-01", "2026-08-31"],
    "per_page": 25
  }'
```

## When to use it

* **Hiring signals for one account** — pass the company and read what they are hiring for, where, and at what level.
* **Prospecting by role** — search a country for companies opening senior engineering or sales roles, then jump to [People Search](/en/api-reference/endpoint/people/search) with the `domain_search_id` from each row.
* **Incremental sync** — poll with `created_at` set to the newest ingestion time you have stored.

If you want a running tally rather than rows, use [Job Insights](/en/api-reference/endpoint/companies/job_insights). If you want to be told when a company posts, create a [Monitor](/en/developer-guides/examples/tracking-job-postings) with `signal_types: ["jobs"]`.

## Scope to a company

The fastest searches name the company. All three identifiers resolve to the same record and can be mixed:

| Key             | Value                                         | Notes                                                                   |
| --------------- | --------------------------------------------- | ----------------------------------------------------------------------- |
| `domains`       | `["stripe.com"]`                              | URLs such as `https://www.stripe.com/jobs` are normalised to the domain |
| `linkedin_urls` | `["https://www.linkedin.com/company/stripe"]` | Company pages only                                                      |
| `companies`     | `["5378845d-7726-4817-aba9-ced8c5f41dee"]`    | `domain_search_id` from any earlier response                            |

Searches without a company are allowed — `{"locations": ["SG"], "seniority_ranks": [5]}` works — but they count against the whole index. `total_entries` is then an estimate and `is_timeout` may be `true` on very broad filters.

## Filters

### Text

| Key            | Type       | Matches                                                                                                    |
| -------------- | ---------- | ---------------------------------------------------------------------------------------------------------- |
| `titles`       | `string[]` | Job titles. Word-level match, so `["engineer"]` also returns `Senior Software Engineer`. Any listed title. |
| `search_term`  | `string`   | Free text against the posting title.                                                                       |
| `search_terms` | `string[]` | Several free-text terms, any of which may match.                                                           |

### Classification

| Key               | Type        | Matches                                                                                                                                                                                     |
| ----------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `functions`       | `string[]`  | Function slugs derived from the title: `engineering`, `sales`, `marketing`, `finance`, `product_management`, … Any listed slug. See [Job functions](/en/api-reference/enums#job-functions). |
| `seniority_ranks` | `integer[]` | `1` junior · `2` senior · `3` manager · `4` director · `5` executive. See [Seniority ranks](/en/api-reference/enums#seniority-ranks).                                                       |

<Warning>
  The two behave differently on bad input. An unknown `functions` slug returns **zero** rows. A `seniority_ranks` value outside 1–5 is **ignored** and returns every row. Neither raises an error.
</Warning>

### Dates

| Key            | Type                  | Matches                                                                                                                                                                                       |
| -------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `launch_dates` | `[from, to]`          | The posting's launch date — `posting_date` when the publisher gives one, otherwise the day Pubrio first saw it. UTC day boundaries; reconciles with Job Insights, so **use this by default.** |
| `posted_dates` | `[from, to]`          | `posting_date` only. Day boundaries follow your workspace timezone.                                                                                                                           |
| `created_at`   | date or ISO timestamp | Ingested at or after this instant (UTC). Pass the exact `created_at` of your newest stored row for incremental polling.                                                                       |

Both windows are inclusive. `launch_dates` with one element matches that single day.

### Location

| Key                 | Type        | Matches                                                                                                                                                                 |
| ------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `locations`         | `string[]`  | Posting country, ISO alpha-2.                                                                                                                                           |
| `exclude_locations` | `string[]`  | Posting country to leave out.                                                                                                                                           |
| `location_ids`      | `integer[]` | Posting country by Pubrio `location_id`, the same numbers the [Locations](/en/api-reference/endpoint/locations/locations) endpoint and every row's `location_id` carry. |
| `company_locations` | `string[]`  | Country of the **company headquarters**, which can differ from where the job is.                                                                                        |

### Paging and order

| Key                  | Default | Notes                                                                                    |
| -------------------- | ------- | ---------------------------------------------------------------------------------------- |
| `per_page`           | 25      | Capped by your plan's `max_search_per_page`.                                             |
| `page`               | 1       | Capped by `max_search_page`; the response's `total_display_pages` tells you the ceiling. |
| `is_ascending_order` | `false` | Rows are ordered by `created_at`, newest first. `true` flips to oldest first.            |

## What a row looks like

```json theme={null}
{
  "job_id": "4418496a-6a4f-4432-8910-50153e9ba27c",
  "job_search_id": "4418496a-6a4f-4432-8910-50153e9ba27c",
  "created_at": "2026-08-30T00:02:52.531Z",
  "last_modified": "2026-09-01T16:08:09.324Z",
  "title": "Finance and Strategy Partner",
  "job_url": "https://www.linkedin.com/jobs/view/4460586030",
  "location": "United States",
  "location_id": 357,
  "country": "United States",
  "country_code": "US",
  "posting_date": "2026-08-29",
  "source_type": "linkedin",
  "functions": ["consulting"],
  "seniority_rank": 5,
  "base_salary": null,
  "experience_requirement": null,
  "education_requirement": null,
  "employment_type": null,
  "companies": {
    "domain_search_id": "5378845d-7726-4817-aba9-ced8c5f41dee",
    "company_name": "Stripe",
    "linkedin_name": "stripe",
    "country_code": "US",
    "company_url": "https://stripe.com/",
    "domain": "stripe.com",
    "logo_url": "https://buckets.pubrio.com/company-logo/....jpg"
  }
}
```

* `functions` is `null` and `seniority_rank` is `0` when the title could not be classified. Filter on them anyway — unclassified rows simply do not match.
* `posting_date` is the publisher's date; `created_at` is when Pubrio first saw the posting and is the default sort key.
* `job_id` and `job_search_id` are the same value; pass either to [Job Lookup](/en/api-reference/endpoint/companies/job_lookup).

### Read `metadata` before trusting a result

```json theme={null}
"metadata": {
  "profile": null,
  "filters": { "domains": ["stripe.com"], "seniority_ranks": [4, 5], "per_page": 25, "language": "en" },
  "ignored_fields": []
}
```

`ignored_fields` lists any body key the endpoint did not recognise. A typo such as `"seniorty_ranks"` does not fail the request — it silently widens it. Check this array is empty in anything automated.

## Recipes

<Tabs>
  <Tab title="Senior engineering hires, last 30 days">
    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST https://api.pubrio.com/companies/jobs/search \
        -H "pubrio-api-key: $PUBRIO_API_KEY" \
        -H "Content-Type: application/json" \
        -d '{
          "domains": ["stripe.com", "adyen.com", "checkout.com"],
          "functions": ["engineering", "data_science"],
          "seniority_ranks": [3, 4, 5],
          "launch_dates": ["2026-08-05", "2026-09-04"],
          "per_page": 25
        }'
      ```

      ```python Python theme={null}
      import os, requests

      r = requests.post(
          "https://api.pubrio.com/companies/jobs/search",
          headers={"pubrio-api-key": os.environ["PUBRIO_API_KEY"], "Content-Type": "application/json"},
          json={
              "domains": ["stripe.com", "adyen.com", "checkout.com"],
              "functions": ["engineering", "data_science"],
              "seniority_ranks": [3, 4, 5],
              "launch_dates": ["2026-08-05", "2026-09-04"],
              "per_page": 25,
          },
      )
      body = r.json()
      assert body["metadata"]["ignored_fields"] == []
      for job in body["data"]["jobs"]:
          print(job["companies"]["company_name"], "-", job["title"], job["seniority_rank"])
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Who is hiring sales leaders in Singapore">
    ```bash cURL theme={null}
    curl -X POST https://api.pubrio.com/companies/jobs/search \
      -H "pubrio-api-key: $PUBRIO_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "locations": ["SG"],
        "functions": ["sales", "business_development"],
        "seniority_ranks": [4, 5],
        "launch_dates": ["2026-08-01", "2026-09-04"],
        "per_page": 25
      }'
    ```

    Group the rows by `companies.domain_search_id` — each distinct company is a prospect, and the id goes straight into `companies` on [People Search](/en/api-reference/endpoint/people/search).
  </Tab>

  <Tab title="Incremental sync">
    ```python Python theme={null}
    import os, requests

    API = "https://api.pubrio.com/companies/jobs/search"
    HEADERS = {"pubrio-api-key": os.environ["PUBRIO_API_KEY"]}

    def sync(domains, since):
        """Fetch every posting ingested at or after `since` (ISO timestamp). Returns the new cursor."""
        newest = since
        page = 1
        while True:
            body = requests.post(API, headers=HEADERS, json={
                "domains": domains, "created_at": since, "page": page, "per_page": 25,
            }).json()
            rows = body["data"]["jobs"]
            if not rows:
                break
            for job in rows:  # newest first
                newest = max(newest, job["created_at"])
                print(job["job_id"], job["title"])  # upsert into your store here
            if page >= body["data"]["pagination"]["total_display_pages"]:
                break
            page += 1
        return newest

    cursor = sync(["stripe.com"], "2026-09-01T00:00:00Z")
    # persist `cursor` and pass it as `since` on the next run
    ```

    The cursor is the full `created_at` of the newest row you stored, so only that one row is re-delivered on the next run. Upsert on `job_id`. Rows arrive newest-first, so a busy day never pushes the cursor past your plan's page cap.
  </Tab>
</Tabs>

## Related

<CardGroup cols={2}>
  <Card title="Job Search reference" icon="code" href="/en/api-reference/endpoint/companies/job_search">
    Every parameter and response field.
  </Card>

  <Card title="Job Insights" icon="chart-column" href="/en/api-reference/endpoint/companies/job_insights">
    Counts by function, seniority, country and week for one company.
  </Card>

  <Card title="Enums & Constants" icon="list" href="/en/api-reference/enums">
    Seniority ranks and the full job-function vocabulary.
  </Card>

  <Card title="Tracking job postings with Monitors" icon="bell" href="/en/developer-guides/examples/tracking-job-postings">
    Get a webhook instead of polling.
  </Card>
</CardGroup>
