# Norway Jobs Search Api (`yearly_register/norway-jobs-search-api`) Actor

Search, normalize and deduplicate Norwegian jobs from LinkedIn, Jobbnorge and NAV/Arbeidsplassen. Strict filters, ranking, geo search and monitoring.

- **URL**: https://apify.com/yearly\_register/norway-jobs-search-api.md
- **Developed by:** [Automation Tech](https://apify.com/yearly_register) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 unique jobs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Norway Jobs Search API — Multi-Source Job Aggregator

Search Norwegian vacancies across Jobbnorge, LinkedIn Jobs, and the supported NAV Arbeidsplassen feed in one Actor run. Results are converted to one canonical schema, filtered locally, conservatively deduplicated across sources, ranked, and emitted with complete source provenance.

This Actor is designed for job seekers, recruiters, job boards, analysts, automation workflows, and AI agents that need structured Norwegian job data rather than three incompatible source payloads.

### What it does

One input search runs the selected source adapters in parallel and processes their records through a deterministic pipeline:

```text
input and Norwegian query expansion
  -> source-specific HTTP collection
  -> canonical normalization
  -> cheap safe exclusions
  -> conservative cross-source clustering
  -> local geo enrichment and distance
  -> strict local filters
  -> explainable relevance score
  -> monitor/change classification
  -> final unique maxResults
  -> Dataset + unique-job event
```

`maxResults` means final unique canonical vacancies. It never means rows per source or raw pre-deduplication candidates.

### Supported sources

| Source | Method | Authentication | Failure behavior |
|---|---|---|---|
| Jobbnorge | Public v3 search API with bounded v2 detail enrichment | None | Keeps search summaries if detail enrichment fails |
| LinkedIn Jobs | No-login guest search and job-detail HTTP endpoints | None | Preserves collected cards on 429/later-page failures; rejects non-authoritative HTTP-200 challenge or malformed pages |
| NAV Arbeidsplassen | Official `pam-stilling-feed` change feed and detail endpoint | `NAV_JOB_FEED_TOKEN` secret | Skips only NAV when the token is absent; cached state survives partial syncs |

FINN.no is intentionally not implemented. NAV feed records from FINN are not part of the supported feed, and no source adapter depends on FINN.

#### NAV consumer token

NAV’s old public search feed was discontinued on 1 May 2025. The supported replacement is a consumer change feed that requires a bearer token and local filtering. Register free of charge by accepting NAV’s API terms and contacting `nav.team.arbeidsplassen@nav.no` with the consumer information described in the [NAV Job Vacancy Feed documentation](https://navikt.github.io/pam-stilling-feed/).

Store the token as the secret environment variable `NAV_JOB_FEED_TOKEN`. Do not place it in Actor input, source code, logs, screenshots, or Git.

The first NAV run starts no earlier than six months back, upserts active feed events, removes inactive events, and stores a versioned cursor plus compact active summaries in the named key-value store `norway-jobs-search-api-state`. If the pagination limit is reached, a later run resumes from the saved cursor. Details are fetched only for bounded plausible matches.

### Why use this Actor

- Search multiple Norwegian job sources with one input.
- Receive one stable output shape instead of source-specific HTML and JSON.
- Apply filters locally instead of trusting each source search engine’s semantics.
- Consolidate the same vacancy found on several portals into one result.
- Keep every contributing source ID and URL for auditability.
- Rank results with deterministic, explainable `matchScore` and `matchReasons`.
- Run incrementally with `NEW`, `UPDATED`, `REAPPEARED`, conservative `REPOSTED`, and optional `UNCHANGED` states.
- Use Apify Datasets, API, schedules, and webhooks without an external database.

### Multi-source aggregation and canonical output

Each adapter retains its own raw type. Canonicalization happens only at the adapter boundary, preventing useful source-specific fields from being forced into a lossy fake common payload.

The canonical record includes:

- normalized and original title/company
- description as safe normalized plain text
- structured location and optional distance
- publication/update/deadline dates
- employment type and work arrangement
- cautiously parsed salary
- source and application URLs
- bounded source-specific metadata on every provenance entry
- relevance and data-quality scores
- canonical ID, merge confidence, and full provenance
- monitoring timestamps and change classification

Missing fields remain `null`. The Actor does not invent coordinates, salary amounts, deadlines, employer identifiers, or timestamp precision.

### Cross-source deduplication

Deduplication is conservative and deterministic, not perfect. False merges are treated as more harmful than remaining duplicates.

Strong signals include shared application endpoints, stable external vacancy IDs, direct cross-links, and identical source records. Records without a strong identifier are admitted to comparison through bounded blocking keys and scored using corroborating title, company, organization, location, description, publication date, deadline, and application-domain signals.

The clusterer validates every new member against the existing cluster. A weak transitive bridge cannot merge candidates with conflicting organizations, seniority, distant locations, materially different rich descriptions, or clearly separate application endpoints. Records from one source with different IDs remain separate unless they share a strong vacancy identity.

For a single-source record, `deduplicationConfidence` is `0`, meaning no cross-source merge was performed. For a merged cluster it is a deterministic merge-strength indicator from 0 to 1, not a statistical probability.

Canonical source selection is based on record completeness—description, organization number, structured geo, dates, employment metadata, application URL, and stable identifiers—not a hardcoded source priority. Complementary fields are merged safely, and all source provenance remains visible.

### Strict filtering

Source engines are discovery mechanisms. The Actor enforces these rules after normalization:

- query or expanded synonym match
- `includeKeywords` and `excludeKeywords`
- `titleMustIncludeAny` and `titleExclude`
- `descriptionMustIncludeAny`
- `companyInclude` and `companyExclude`
- normalized employment type
- remote/hybrid/on-site arrangement
- publication freshness when known
- location and radius rules

Matching is token-aware. A short query such as `IT` matches an `IT-konsulent` token, not arbitrary words such as `kvalitetssikring`. Norwegian `æ`, `ø`, and `å`, Unicode forms, dash variants, punctuation, and whitespace are normalized consistently.

Arrays named `...IncludeAny` require at least one listed term. Any exclusion term rejects the record. If a requested structured field such as employment type is missing, that record cannot satisfy the explicit structured filter.

### Norwegian query semantics

The Actor contains a deliberately small, extensible dictionary for common Norwegian/English occupation variants. For example, `reservedeler` can discover and rank `reservedelsmedarbeider`, `delelager`, `parts advisor`, and related variants; `bilmekaniker` can match `kjøretøymekaniker` and `automotive technician`.

Expansion helps source discovery and ranking. Strict local rules remain authoritative. No LLM, embedding service, paid taxonomy, or external search API is used.

### Geo and radius search

Requested origins are resolved through a bounded local list of common Norwegian places. Source-provided coordinates are preferred. When a source supplies only a recognized city or municipality, the Actor uses the local city centroid and labels the location precision internally. Distance is the Haversine distance to the closest requested origin.

If a job has no reliable or locally resolvable coordinate, `location.distanceKm` remains `null`. Such jobs are retained by default because excluding weak source data can hide valid vacancies. Set `requireCoordinatesForRadius: true` to exclude them when `radiusKm` is active.

### Relevance ranking

`matchScore` is an explainable 0–100 relevance score, not a probability. Centralized stable weights reward exact title phrases, title tokens, expanded synonyms, description relevance, requested keywords, location match, proximity, freshness, and requested structured attributes.

Example reasons:

```json
[
  "Strong title match",
  "Matched requested keyword",
  "Published 2 days ago",
  "28 km from requested location"
]
```

The same normalized input and job data produce the same score and ordering.

### Search mode

`mode: "search"` returns current matching canonical jobs. `changeType: "NEW"` in search mode identifies a current result row and does not claim comparison with persisted history.

### Monitor mode

`mode: "monitor"` compares jobs after cross-source deduplication. Provide `stateKey`, or let the Actor derive one from the normalized search configuration. Different queries, locations, filters, sources, result limits, and sorting configurations derive different keys.

Monitor state is versioned and stored in the named key-value store. Version-1 state is migrated to the compact version-2 representation; a deeply corrupt or incompatible value is reset with a warning rather than crashing mysteriously. Content values are stored as field digests, so descriptions do not inflate the state store.

- First observation: `NEW`
- Meaningful normalized content changed: `UPDATED` with `changedFields`
- Previously observed, reliably absent, then returned: `REAPPEARED`
- New source identity plus later publication and identical normalized description: conservative `REPOSTED`
- No meaningful change: `UNCHANGED`

Formatting-only description changes are ignored. Source-count changes alone do not make a canonical vacancy new. If any selected source is partial, failed, or skipped, missing jobs are not marked absent because source coverage is not reliable.

`UNCHANGED` jobs are not emitted by default. Set `emitUnchanged: true` if the full current monitor result set is required.

Monitor changes use acknowledgement-based delivery. Detection can queue more changed vacancies than `maxResults`, but a change is acknowledged only after its canonical row is successfully pushed to the Dataset. Unemitted changes remain pending and are prioritized on later runs, preventing the final result limit or a PPE budget limit from silently consuming monitor history. A crash after a Dataset push but before state persistence can cause at-least-once redelivery; downstream consumers should use `canonicalJobId`, `changeType`, and timestamps idempotently.

Do not overlap runs that use the same `stateKey`. The Apify key-value store does not provide a compare-and-swap operation for this state document, so concurrent writers could overwrite newer history. Separate schedules with different search configurations derive different keys automatically.

### Input example

```json
{
  "query": "reservedeler",
  "locations": [
    "Jessheim",
    "Oslo",
    "Lillestrøm"
  ],
  "radiusKm": 60,
  "publishedWithinDays": 7,
  "sources": [
    "arbeidsplassen",
    "jobbnorge",
    "linkedin"
  ],
  "includeKeywords": [
    "reservedeler",
    "parts",
    "lager"
  ],
  "titleExclude": [
    "manager"
  ],
  "maxResults": 50,
  "mode": "search",
  "sortBy": "match"
}
```

The no-secret default selects Jobbnorge and LinkedIn. Add `arbeidsplassen` after configuring `NAV_JOB_FEED_TOKEN`.

### Output example

Three listings that point to one vacancy become one Dataset item and one `unique-job` event:

```json
{
  "canonicalJobId": "job_2bd71d...",
  "title": "Reservedelsmedarbeider",
  "normalizedTitle": "reservedelsmedarbeider",
  "company": "Eksempel Bil AS",
  "normalizedCompany": "eksempel bil",
  "employerOrgNumber": "999888777",
  "description": "Vi søker en reservedelsmedarbeider ...",
  "location": {
    "displayName": "Jessheim",
    "city": "Jessheim",
    "municipality": "Ullensaker",
    "county": "Akershus",
    "postalCode": "2050",
    "countryCode": "NO",
    "latitude": 60.1415,
    "longitude": 11.1752,
    "distanceKm": 0
  },
  "publishedAt": "2026-08-29T08:00:00.000Z",
  "deadline": "2026-09-15",
  "employmentType": "FULL_TIME",
  "workArrangement": "ON_SITE",
  "salary": null,
  "applyUrl": "https://jobs.example.no/vacancy/123",
  "matchScore": 92,
  "matchReasons": [
    "Strong title match",
    "Matched requested keyword",
    "Matched location: Jessheim"
  ],
  "sourceCount": 3,
  "canonicalSource": "arbeidsplassen",
  "sources": [
    {
      "source": "arbeidsplassen",
      "sourceId": "nav-uuid",
      "url": "https://arbeidsplassen.nav.no/stillinger/stilling/nav-uuid",
      "applyUrl": "https://jobs.example.no/vacancy/123",
      "publishedAt": "2026-08-29T08:00:00.000Z",
      "sourceMetadata": {
        "occupationCategory": "Bilmekaniker"
      }
    },
    {
      "source": "jobbnorge",
      "sourceId": "306169",
      "url": "https://www.jobbnorge.no/ledige-stillinger/stilling/306169",
      "applyUrl": "https://jobs.example.no/vacancy/123",
      "publishedAt": "2026-08-29",
      "sourceMetadata": {
        "teaser": "Arbeid med deleflyt og kundeservice"
      }
    },
    {
      "source": "linkedin",
      "sourceId": "4448056298",
      "url": "https://www.linkedin.com/jobs/view/4448056298",
      "applyUrl": "https://jobs.example.no/vacancy/123",
      "publishedAt": "2026-08-29",
      "sourceMetadata": {
        "seniority": "Associate",
        "industries": "Automotive"
      }
    }
  ],
  "deduplicationConfidence": 0.99,
  "firstSeenAt": "2026-08-31T12:00:00.000Z",
  "lastSeenAt": "2026-08-31T12:00:00.000Z",
  "lastChangedAt": null,
  "changeType": "NEW",
  "dataQualityScore": 96
}
```

### Source and pipeline statistics

The `OUTPUT` key in the default key-value store contains per-source status, requests, raw/normalized jobs, retries, pages, `coverageComplete`, warnings, elapsed time, and pipeline counts. `coverageComplete` is conservative: it is false whenever a source stopped at a result/page bound or NAV did not evaluate its full active cache. LinkedIn always reports incomplete absence coverage because the guest endpoint has no stable authoritative exhaustion contract. Monitor mode marks unseen jobs absent only when every selected source reports both `SUCCESS` and complete coverage; therefore searches that include LinkedIn detect new/updated rows but never overclaim disappearance or reappearance.

```text
rawCandidates -> normalizedCandidates -> afterCheapFilters
-> duplicateRowsMerged / canonicalUniqueJobs
-> afterStrictFilters -> finalResults
```

Large upstream payloads and credentials are never logged.

### Pricing architecture

The intended Store model is pay per unique final canonical job. Each final Dataset row is written through:

```ts
await Actor.pushData(job, 'unique-job');
```

The Apify SDK applies the event to the same pushed unit and respects the run’s charge limit. Raw source rows, failed requests, filtered records, and duplicate source listings are not custom-charged.

When configuring PAY\_PER\_EVENT in Apify Console, create the `unique-job` event and set its price. Apify can also synthesize an `apify-default-dataset-item` event for default Dataset writes. Disable or price that synthetic event appropriately if billing must be exclusively one `unique-job` charge per emitted vacancy.

The repository deliberately does not contain an event price or production monetization settings; those are account-level deployment decisions.

### API, schedules, and webhooks

After deployment, start a run with the standard Apify API:

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/USERNAME~norway-jobs-search-api/runs?token=APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query":"sykepleier","locations":["Bergen"],"maxResults":25}'
```

Use Apify schedules for recurring monitor searches and webhooks to forward completed run or Dataset events to another workflow. Monitor mode uses the stable named state store, so scheduled runs share history when their `stateKey` or derived configuration is the same.

Do not place API or NAV tokens directly in scripts committed to Git. Use Apify secrets or your automation platform’s secret store.

### Local development

Requires Node.js 22 or later.

```bash
npm ci
npm run typecheck
npm run lint
npm test
npm run build
```

Opt-in live sanity tests are run separately:

```bash
npm run test:live
```

Build the same production image with Docker or Podman:

```bash
podman build -t norway-jobs-search-api .
```

No Apify account, token, or production Actor is needed for deterministic tests.

### Known limitations

- NAV is a supported change feed, not a server-side keyword API. The first token-authenticated run may need several feed pages and later runs resume from persisted state. Description-only discovery outside fetched candidates is inherently limited by the official interface.
- LinkedIn’s guest HTML and traffic controls can change. The connector uses bounded retries, adaptive backoff with jitter, `Retry-After`, low concurrency, and partial-result preservation, but sustained 429 responses can reduce coverage.
- LinkedIn coverage is deliberately conservative: non-empty HTTP-200 responses without wholly valid recognized job cards, mixed malformed cards, and redirects away from the exact guest-search endpoint are treated as incomplete failures. A whitespace-only response is the currently recognized empty terminal form; upstream changes to no-result markup will appear as an actionable source warning until a fixture-backed form is added.
- Jobbnorge’s API currently caps practical page size and exposes richer descriptions through separate detail requests; detail failures retain the summary record.
- City-centroid geo enrichment covers a bounded local list. Unknown or ambiguous places keep null coordinates; radius behavior is explicit and configurable.
- Different vacancies can legitimately look nearly identical, and the same vacancy can differ materially across portals. Deduplication is conservative, so some duplicates can remain.
- Salary parsing recognizes confident NOK ranges/hourly forms and retains nonnumeric text such as salary grades, tariff, or “by agreement.” It does not map salary grades to currency amounts.
- `REPOSTED` detection is deliberately rare. Ambiguous returns are classified as `REAPPEARED` or `NEW` rather than overclaimed.
- Runs sharing one monitor `stateKey` must not overlap. Monitor delivery is at least once around a crash between Dataset push and state acknowledgement.

### Security and data handling

- No login cookies, browser sessions, LLM APIs, paid data services, proxies, or external database are required.
- Network operations use bounded timeouts, retries, pagination, and a 32 MiB response-body ceiling.
- NAV bearer-token requests are restricted to the exact supported HTTPS API origin/path and reject redirects, URL credentials, unsafe upstream links, and unsafe persisted cursors before a token is sent.
- Source HTML is converted to plain text; scripts, styles, templates, and SVG content are removed.
- Normalized descriptions are capped at 200,000 characters, free-form input strings are bounded before normalization, and persisted source/monitor state is deeply validated.
- Contact-person data from NAV is intentionally not retained.
- Tokens, cookies, raw payloads, and full credential-bearing URLs are not logged.
- `.env`, credentials, private keys, local storage, coverage, and build artifacts are excluded from Git.

### Deployment handoff

1. Import or connect this GitHub repository in Apify and select the `main` branch.
2. Add `NAV_JOB_FEED_TOKEN` as a secret environment variable only if Arbeidsplassen is enabled.
3. Build the Actor with 256 MB memory initially; increase to 512 MB only if real NAV cache volume justifies it.
4. Run a no-secret QA search against Jobbnorge and LinkedIn, then a NAV-only QA run after the initial feed sync.
5. Enable PAY\_PER\_EVENT, define `unique-job`, and review the synthetic default-Dataset event so one emitted vacancy has the intended single price.
6. Verify Dataset view, `OUTPUT` summary, monitor second-run/backlog behavior, source warnings, and non-overlapping schedules per `stateKey` before publishing.

This repository is deployment-ready but intentionally does not create, deploy, or publish an Actor in any Apify account.

### Practical API examples

The scripts in `examples/` start bounded runs of the published Actor through the Apify REST API and print the default Dataset. They use Node’s built-in `fetch`, so they add no production dependency.

- `examples/basic-search.mjs` — query Jobbnorge + LinkedIn, then receive final normalized, deduplicated job records.
- `examples/monitor-jobs.mjs` — keep a stable `WATCH_KEY` and receive `NEW` or changed matches on later runs.

Run an example with an environment variable; never hard-code an Apify token:

```bash
`APIFY_TOKEN=... node examples/basic-search.mjs "data analyst"`
`APIFY_TOKEN=... WATCH_KEY=my-oslo-watch node examples/monitor-jobs.mjs`
```

The examples intentionally omit NAV Arbeidsplassen because it requires the separately configured `NAV_JOB_FEED_TOKEN`. Set a small `maxResults` while you validate your own integration.

The examples call the published [Apify Actor](https://apify.com/yearly_register/norway-jobs-search-api). A matching public Example Task link will be added here only after its URL has been verified from Apify Console.

# Actor input Schema

## `query` (type: `string`):

Job title, occupation, skill or keyword. Norwegian synonym expansion is deterministic.

## `locations` (type: `array`):

Norwegian cities or places. Add several origins to search around all of them.

## `radiusKm` (type: `number`):

Maximum straight-line distance from the closest resolved location. Jobs without coordinates are retained unless strict geo is enabled.

## `publishedWithinDays` (type: `integer`):

Keep jobs published within this many days when the source provides a publication date.

## `sources` (type: `array`):

Jobbnorge and LinkedIn need no secret. Arbeidsplassen requires NAV\_JOB\_FEED\_TOKEN.

## `maxResults` (type: `integer`):

Maximum final canonical vacancies after normalization, filtering and cross-source deduplication.

## `sortBy` (type: `string`):

Primary ordering of final results.

## `employmentTypes` (type: `array`):

If selected, jobs with missing or different employment type are excluded.

## `remote` (type: `string`):

Optionally require a normalized remote, hybrid, or on-site arrangement.

## `includeKeywords` (type: `array`):

Require at least one term in title, company or description.

## `excludeKeywords` (type: `array`):

Exclude a job when any term occurs in title, company or description.

## `titleMustIncludeAny` (type: `array`):

Require at least one listed token or phrase in the normalized title.

## `titleExclude` (type: `array`):

Exclude a job when any listed token or phrase occurs in its title.

## `descriptionMustIncludeAny` (type: `array`):

Require at least one listed token or phrase in the normalized plain-text description.

## `companyInclude` (type: `array`):

Require at least one listed company token or phrase.

## `companyExclude` (type: `array`):

Exclude a job when any listed company token or phrase matches.

## `mode` (type: `string`):

Search returns current matches; monitor compares canonical jobs with persistent history.

## `stateKey` (type: `string`):

Optional stable history key. If omitted, one is derived from the normalized search configuration.

## `emitUnchanged` (type: `boolean`):

Monitor mode normally emits only NEW, UPDATED, REAPPEARED and conservative REPOSTED jobs.

## `requireCoordinatesForRadius` (type: `boolean`):

Exclude jobs whose source lacks coordinates when radiusKm is set.

## `requestTimeoutSecs` (type: `integer`):

Per-request timeout. Retries remain separately bounded.

## `maxSourcePages` (type: `integer`):

Hard pagination bound. NAV feed bootstrap resumes from its persisted cursor on a later run.

## Actor input object example

```json
{
  "query": "ingeniør",
  "locations": [
    "Oslo"
  ],
  "publishedWithinDays": 7,
  "sources": [
    "jobbnorge",
    "linkedin"
  ],
  "maxResults": 10,
  "sortBy": "match",
  "employmentTypes": [],
  "remote": "ANY",
  "includeKeywords": [],
  "excludeKeywords": [],
  "titleMustIncludeAny": [],
  "titleExclude": [],
  "descriptionMustIncludeAny": [],
  "companyInclude": [],
  "companyExclude": [],
  "mode": "search",
  "emitUnchanged": false,
  "requireCoordinatesForRadius": false,
  "requestTimeoutSecs": 30,
  "maxSourcePages": 20
}
```

# Actor output Schema

## `jobs` (type: `string`):

Final normalized, filtered, deduplicated and ranked vacancies.

## `runSummary` (type: `string`):

Per-source statuses, warnings and pipeline counts.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("yearly_register/norway-jobs-search-api").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("yearly_register/norway-jobs-search-api").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call yearly_register/norway-jobs-search-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yearly_register/norway-jobs-search-api"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/VOdIEoMyZWScAZdc2/builds/iQWpnfvi7zYQAWD1c/openapi.json
