# eFinancialCareers Jobs Scraper - Finance & Banking Leads (`scrapersdelight/efinancialcareers-jobs-scraper`) Actor

Scrape eFinancialCareers finance & banking jobs worldwide by keyword, city, sector, seniority, employer or date. Every row carries the full job description, employer, location and sector - plus the employer's own ATS apply link, turning a job ad into a company lead. 71,000+ live jobs.

- **URL**: https://apify.com/scrapersdelight/efinancialcareers-jobs-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Jobs, Lead generation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.90 / 1,000 per job returneds

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

## 💼 eFinancialCareers Jobs Scraper — Finance & Banking Job Leads

Every live job on [eFinancialCareers](https://www.efinancialcareers.co.uk) — the specialist job
board for banking, asset management, fintech, compliance and quant roles — as **one clean row per
job**, with the **full job description already on the row** and, uniquely, **the employer's own
applicant-tracking-system link**, so a job ad becomes a company lead.

**71,042 live jobs worldwide** the day this was measured (2026-09-04), across 31 locale domains:
London, Frankfurt, Zurich, Paris, Luxembourg, Dublin, Amsterdam, Singapore, Hong Kong, Tokyo,
Dubai, Sydney, Mumbai, New York.

No login. No API key. No CAPTCHA. Just the site's own public JSON, read the way the site reads it.

***

### 🎯 Who buys this data

| Buyer | What they do with a row |
|---|---|
| **Finance recruitment & executive search** | Every desk a bank is staffing, filtered to `companies: ["Citi"]` or `sectors: ["COMPLIANCE_LEGAL"]` + `seniority: ["DIRECTOR"]` — and `posted_by` tells you whether a rival agency already has the mandate. |
| **RecTech / HR-tech sales teams** | `external_apply_url` names the employer's ATS by domain (Workday, SmartRecruiters, iCIMS, Greenhouse…). That is a technographic signal: who runs which ATS, and who just switched. |
| **B2B sales into financial services** | A hiring bank is a spending bank. "Hired 12 compliance staff in Frankfurt this month" is a trigger event. |
| **Market & comp researchers** | Sector, seniority, position type and location on 100% of rows; salary bounds where the site carries them. |
| **Job boards & aggregators** | A clean, de-duplicated, filterable feed with a stable job id and a `onlyNewSince` delta mode. |

***

### ⚡ What you get on every row

```json
{
  "id": "eVTf7UDbNsksevHA",
  "job_id": "24741881",
  "job_url": "https://www.efinancialcareers.co.uk/jobs-United_Kingdom-London-Associate_Director_-_Institutional_NFR_UK__Europe.id24741881",
  "title": "Associate Director - Institutional NFR UK & Europe",
  "company_name": "ANZ",
  "company_brand_id": "227",
  "company_logo_url": "https://www.efinancialcareers.com/logo/…jpeg",
  "location_display": "London, United Kingdom",
  "location_city": "London",
  "location_state": "England",
  "location_country": "United Kingdom",
  "location_region": "Europe",
  "location_geoname_id": "2643743",
  "posted_date": "2026-09-03T13:45:25.000Z",
  "expiration_date": "2026-10-03T13:00:00.000Z",
  "salary_text": "£90k - £110k",
  "salary_min": 90000,
  "salary_max": 110000,
  "salary_currency": "GBP",
  "salary_is_numeric": true,
  "employment_type": "Full time",
  "position_type": "Permanent",
  "work_arrangement_type": "Hybrid",
  "sectors": ["FINTECH/CAPITAL_MARKETS", "FX_MONEY_MARKETS/TRADING"],
  "full_normalized_job_title": "Director of Sales Representative",
  "summary": "London, UK · Full-time · Hybrid remote · £90,000-£110,000 base salary…",
  "ai_summary": "Finteum, an FCA-registered fintech startup, seeks a Sales Director to lead…",
  "description": "…the complete plain-text job body, median 3,605 characters…",
  "external_apply_url": "https://lseg.wd3.myworkdayjobs.com/en-US/Careers/job/London-United-Kingdom/Senior-Associate--Compliance_R0121815-1?source=eFC",
  "is_external_application": false,
  "scraped_at": "2026-09-04T06:07:37.910Z"
}
```

Turn on deep enrichment and each row also carries:

```json
{
  "job_reference": "R0121815",
  "posted_by": "direct-employer",
  "recruiter_id": "wvaBH5xJovsTjCO7",
  "sub_sectors": ["COMPLIANCE_LEGAL__COMPLIANCE_REGULATORY"],
  "allocation_date": "2026-08-22T08:58:49Z",
  "is_anonymous": false,
  "canonical_url": "https://www.efinancialcareers.co.uk/jobs-UK-London-Senior_Associate_Compliance.id24700409",
  "brand_url": "https://www.efinancialcareers.co.uk/jobs/at-London-Stock-Exchange-Group",
  "location_lat": 51.50853,
  "location_long": -0.12574,
  "location_country_code": "GB",
  "seo_urls": { "en_GB": "…", "de_DE": "…", "en_SG": "…", "…31 locales": "…" },
  "company_id": "SQ0vmP9jxBOXHXZM",
  "company_industry": "Corporate Banking",
  "company_year_founded": 1835,
  "company_hq_city": "Melbourne",
  "company_hq_country": "Australia",
  "company_benefits": ["flexibleWorkingHours", "workFromHome", "paidMaternityPaternityLeave"],
  "company_recruiters": [{ "name": "…", "title": "…" }],
  "company_profile_url": "https://www.efinancialcareers.co.uk/companies/ANZ-SQ0vmP9jxBOXHXZM"
}
```

**72 columns** by default (flat, CSV-ready). Trim them with `outputFields`.

***

### 📏 Measured field fill

Real numbers from real runs, not estimates — and shown as a **range across four independent
slices**, because fill genuinely moves with the query, the country mix and the day:

- **A** — 1,200 rows over London, Frankfurt, Singapore, New York, Zurich, Hong Kong (200 each), 2026-09-04
- **B** — 250 rows, `locations: ["London"]`, 2026-09-05 (run `2Hb52IqdY4sSKCqPt`)
- **C** — 50 rows, empty input (whole corpus, newest-first — US-heavy that hour), 2026-09-05 (run `25zxcevGX7JOHpcbh`)
- **D** — 50 rows, prefill-only input, 2026-09-05, several hours after C (run `RiXZi8RV41Kh1dndi`)

Anything below quoted as a single number was 100% on all four. **Treat a one-slice percentage as
an estimate, not a guarantee:** every optional field below swings by tens of points between slices
— C and D are the *same* default input a few hours apart and still differ by up to 24 points.

| Field | A (1,200) | B (250) | C (50) | D (50) |
|---|---|---|---|---|
| `id`, `job_id`, `job_url`, `title` | **100%** | **100%** | **100%** | **100%** |
| `location_display` / `city` / `country` / `region` | **100%** | **100%** | **100%** | **100%** |
| `posted_date`, `expiration_date` | **100%** | **100%** | **100%** | **100%** |
| `employment_type`, `position_type`, `sectors`, `salary_text` | **100%** | **100%** | **100%** | **100%** |
| `description` — the full plain-text body, median 3,605 chars | **100%** | **100%** | **100%** | **100%** |
| `summary` | **100%** | **100%** | **100%** | **100%** |
| `salary_currency` | 99.9% | 100% | 100% | 100% |
| `company_name` | 99.3% | 98.8% | 100% | 98.0% |
| `full_normalized_job_title` | 97.6% | 98.4% | 96.0% | 92.0% |
| `ai_summary` (the site's own precis) | 91.6% | 92.8% | 92.0% | 96.0% |
| `company_logo_url` | 90.0% | 82.8% | 100% | 98.0% |
| `company_profile_image` | — | 70.8% | 70.0% | 60.0% |
| `cover_image` | — | 45.2% | 62.0% | 60.0% |
| `external_apply_url` (apply enrichment, on by default) | ~75% | 82.4% | 54.0% | 72.0% |
| `work_arrangement_type` (Remote / Hybrid / In-office) | 23.5% | 22.8% | 44.0% | 22.0% — the site often omits it |
| `salary_min` / `salary_max` | 17.3% | **16.4%** | 38.0% | 44.0% — read the salary note below |
| `job_reference` (deep enrichment) | ~83–88% | — | — | — |
| `recruiter_id` → `posted_by` (deep enrichment) | ~33–50% agency-placed | — | — | — |
| company-profile fields (deep enrichment) | ~38–50% — many employers publish no profile | — | — | — |

A default run emits all **72 columns**, of which **46 are ever non-empty** and **42.8–43.2 are
non-empty per row** (slices D, C and B). The 26 that come back empty by default are the
deep-enrichment columns — switch `fetchJobDetail` / `fetchCompanyProfile` on and they fill; none
is a dead field.

**Three column pairs agree on almost every row, and that is deliberate — they are separate source
fields, not copies.** `is_external_application` comes from the search feed and
`is_external_job_application` from the apply-information call, so with `fetchApplyUrl: false` the
first is filled on 100% of rows and the second is null on all of them (measured 0/30 agreement,
run `dfUuIWBUhn3sz94bf`); `location_geoname_id` (the ad's location id) and `city_geoname_id`
differed on 2 of 250 rows in slice B; `company_name` (which falls back through the feed's
`companyName` → `fullCompanyName` → brand name) matched `company_brand_name` on every row measured
so far. Drop whichever you do not need with `outputFields`.

The same run returned exactly 200 rows per city and **every row's country matched the city asked
for** — London → United Kingdom, Frankfurt → Germany, Singapore → Singapore, New York → United
States, Zurich → Switzerland, Hong Kong → Hong Kong. 256 distinct employers in 1,200 rows.

#### 💷 The salary note — read this before you buy

Salary on eFinancialCareers is **not** a reliable field, and this actor will not pretend otherwise.

- The displayed salary text is the literal word **"Competitive" on 82.2%** of the 1,200-row
  six-city sample, and **80.2% of rows carry no salary number anywhere at all**.
- Numeric `salary_min` / `salary_max` fill swings hard by geography, because pay-transparency law
  does. Measured: **17.3%** across the six-city spread, **16.4%** on a 250-row London slice,
  **38.0%** on a 50-row US-heavy slice, **45.6%** on a 1,000-row United-States-heavy slice,
  **9.1%** on an 1,800-row Europe/Asia recon sample. Expect single digits to high teens in
  Europe and roughly half in the US — and treat any single figure as slice-dependent.
- The site sometimes carries numeric bounds *even where the ad text says "Competitive"* — those are
  the site's own figures, not a quoted salary. Treat them as an estimate.

Two filters exist for this, and rows they remove are never billed:
`onlyWithNumericSalary` (strict — keeps rows with real numbers) and `includeUnspecifiedSalary: false`
(looser — also keeps day rates like `£500 - 550p/d`).

**This is why the title of this actor does not say "salary".** Some rivals sell it that way. It is
a refund waiting to happen.

***

### 🔍 Filters — every one tested against the live site

Values inside one filter are OR-ed; different filters are AND-ed. Live counts measured 2026-09-03.

| Filter | Live counts you can rely on |
|---|---|
| `locations` + `radius` | London 7,629 · Frankfurt 1,708 · Singapore 2,320 · New York 4,346 · Zurich 764 · Sydney 753 · Tokyo 436 · Mumbai 503 · Dubai 395. Frankfurt @ 40 km really does pull in Darmstadt and Bad Nauheim. |
| `locationPaths` | Europe 40,088 · North America 17,982 · Asia 10,696 · Australasia 1,380 · Gulf 877 |
| `sectors` | INFORMATION\_TECHNOLOGY 34,625 · ACCOUNTING\_FINANCE 14,783 · INSURANCE 3,640 · COMPLIANCE\_LEGAL 3,055 · OPERATIONS 2,731 · ASSET\_MANAGEMENT 2,708 · PRIVATE\_BANKING\_WEALTH\_MANAGEMENT 2,281 |
| `seniority` | ASSOCIATE\_MID\_LEVEL 38,021 · AVP\_SENIOR 14,332 · INTERN\_GRADUATE 3,709 · VP\_PRINCIPAL 3,614 · ANALYST 2,738 · SVP\_HEAD\_OF 2,144 · DIRECTOR 2,128 · JUNIOR 1,077 |
| `positionType` | PERMANENT 67,331 · CONTRACT 1,558 · INTERNSHIPS\_AND\_GRADUATE\_TRAINEE 1,074 · TEMPORARY 992 · SELF\_EMPLOYED 86 |
| `employmentType` | FULL\_TIME 70,127 · PART\_TIME 914 |
| `workArrangementType` | HYBRID 1,796 · IN\_OFFICE 834 · REMOTE 220 · FLEXIBLE 96 |
| `companies` | e.g. Deloitte 998 |
| `normalizedJobTitles` | Accountant 1,135 · Engineer 1,035 · Consultant 898 · Lawyer 793 · Account Manager 718 |
| `postedWithin` | ONE 4,172 · THREE 11,051 · SEVEN 19,033 |
| `salaryCurrency` + `minSalary` / `maxSalary` | GBP + min 100,000 → 310 jobs, every returned row ≥ £100k. Verified row by row. |

Combining works exactly as you would expect: `seniority: DIRECTOR` (2,128) **AND**
`sectors: COMPLIANCE_LEGAL` (3,055) returns **67**.

**The live vocabulary is written to your run's key-value store as `FILTER_VOCABULARY`** — the exact
sector, seniority, currency and title values the site offered for *your* search, read from the
response itself rather than hardcoded here. Use it to build the next run.

***

### 🧠 Things this actor gets right that a naive scraper does not

1. **An out-of-range page is "done", never an error.** Ask for page 2 of a 6-result search and a
   naive crawler records a failure. This one clamps every page against the site's own result count
   and ends that search cleanly. (A recon harness scored 58.3% on this site purely from this bug.)
2. **The ~60,000-row depth ceiling is guarded.** Bisected live: row offset 59,950 returns rows,
   62,450 returns HTTP 500. The actor stops at the guard and tells you to narrow the search. It
   never surfaces the site's 500 as a failed run. Slice by city or sector to go past it.
3. **De-duplication happens BEFORE billing.** Measured overlap: 0.13% within one contiguous band,
   1.00% across contiguous pages, 1.5% across overlapping keyword searches. You never pay twice.
4. **Retries are not optional and they are built in.** 53 sustained calls through the Apify
   datacenter proxy: 90.6% clean first try, **100% within 3 tries**. Every miss was a transient CDN
   fault (connection reset, truncated body, cold-cache timeout). Zero 403s, zero CAPTCHAs.
5. **A big page that times out shrinks instead of failing.** A 1,000-row request the CDN is slow to
   build is retried at the same offset with half the page size.
6. **A pasted URL is translated, not followed.** The site's own `/jobs-UK.London.s001` URLs 301 to
   `/jobs` and *silently drop the location filter*. This actor reads the filters out of the URL, so
   your London search stays a London search.
7. **A run that finds nothing exits clean and charges nothing**, and the status message tells you
   which of the three reasons it was.

***

### 🚀 Quick starts

**Everything posted in the last 3 days, newest first**

```json
{ "postedWithin": "THREE", "sortBy": "date", "maxItems": 2000 }
```

**Every compliance director in the City, with the employer's ATS link**

```json
{ "locations": ["London"], "sectors": ["COMPLIANCE_LEGAL"],
  "seniority": ["DIRECTOR", "SVP_HEAD_OF"], "fetchApplyUrl": true, "maxItems": 500 }
```

**Everything one bank is hiring for, globally, fully enriched**

```json
{ "companies": ["Deloitte"], "fetchJobDetail": true, "fetchCompanyProfile": true, "maxItems": 1000 }
```

**A weekly new-jobs feed for your CRM** (schedule it, and move the timestamp each run)

```json
{ "locations": ["Frankfurt", "Zurich", "Luxembourg"],
  "onlyNewSince": "2026-09-01T00:00:00Z", "maxItems": 0 }
```

**A wide sweep, four columns, cheapest possible**

```json
{ "pageSize": 1000, "maxItems": 0, "fetchApplyUrl": false, "descriptionMode": "none",
  "outputFields": ["title", "company_name", "location_city", "job_url"] }
```

***

### 💰 Pricing

Pay per event. **No run-start fee.** You pay for rows you actually receive.

| Event | Price | When it fires |
|---|---|---|
| **Per job returned** | **$0.0009** ($0.90 per 1,000) | Once per unique job delivered to your dataset — including the employer's ATS apply link. Duplicates and filtered-out rows are free. |
| **Per deep enrichment** | $0.0009 | Only for rows that deep enrichment actually returned data for, and only when you switch `fetchJobDetail` or `fetchCompanyProfile` on. |

At $0.0009 a row this is **below the cheapest price in this category** and below the current
category leader's $0.001 — and unlike several rivals there is no per-run start fee bolted on top.

Cost examples: 1,000 jobs **$0.90** · 10,000 jobs **$9.00** · the entire 71,042-job live corpus
**$63.94**. Set `maxCostUsd` and the run stops *before* it crosses your number, not after — on
**every** delivery path: search pages, the newest-first (`sortBy: "date"`) buffer and pasted job
URLs all go through the same pre-charge gate. Re-proven live on the current build 0.1.5:
`maxCostUsd 0.001` with 8 pasted job URLs billed **1** row (run `5G3ZSZwNFVoTpsMB2`), the same cap
on `sortBy: "date"` billed **1** row (run `sV0WLGfdkd5USc0Oo`), and `maxCostUsd 0.005` with deep
enrichment on ($0.0018 a row) billed **2** rows = $0.0036 (run `8Se1nt5NvcGY4u7lA`).

***

### ❓ FAQ

**Do I need an eFinancialCareers account, cookie or API key?**
No. Every request is the site's own public JSON endpoint with ordinary browser headers.

**Which countries does it cover?**
All of them. eFinancialCareers is one global index served on 31 locale domains — the same job
appears on `.co.uk`, `.de`, `.ch`, `.fr`, `.sg`, `.hk`, `.com.au`, `-gulf.com` and the rest. Pick
which domain the `job_url` column should point at with `localeDomain`; filter geography with
`locations` or `locationPaths`, not by domain.

**Can I get all 71,000 jobs in one run?**
Almost. The site stops serving results past a row offset of about 60,000 *per search*, so a true
full-corpus pull needs 2–3 slices (by region path or by sector). The actor tells you when it hits
that boundary and what to narrow.

**How fresh is the data?**
`posted_date` is filled on 100% of rows and the site's index with an empty keyword is strictly
newest-first. `postedWithin` gives you the site's own 24-hour / 3-day / 7-day windows;
`postedAfter` / `postedBefore` let you set any window you like, and `onlyNewSince` gives you a pure
delta feed for scheduling.

**What exactly is `external_apply_url`?**
The URL eFinancialCareers itself sends an applicant to. About three quarters of jobs have one, and
most point straight at the employer's own ATS or careers domain — `lseg.wd3.myworkdayjobs.com`,
`jobs.smartrecruiters.com/Wise`, `jobs-fishercareers.icims.com`, `portal.emagine.org`. A minority
are served through the site's ad-network click tracker (`click.appcast.io`) instead. **This actor
does not follow those redirects** — following an advertising click-tracker would register a paid
click against the employer's budget, which is not something a scraper should do on your behalf.

**Agency-posted or direct from the employer?**
Turn on `fetchJobDetail`. `posted_by` reads `agency` when the ad was placed through a recruiter
seat and `direct-employer` when it was not — which is exactly the question an executive-search
buyer asks first.

**Is the full job description really included at no extra cost?**
Yes. The site's listing response carries the complete plain-text body (median 3,605 characters),
so there is no per-row detail fetch to pay for. `descriptionMode: "html"` fetches the HTML version,
and *that* costs a deep-enrichment charge.

**Will it double-charge me for a job two searches both found?**
No. Rows are de-duplicated on the site's stable job hash *before* anything is pushed or billed. Set
`deduplicateAcrossQueries: false` if you deliberately want one copy per search.

**What happens if my filters match nothing?**
The run succeeds, returns zero rows, charges nothing, and the status message names the reason —
nothing matched, everything was filtered out, or your start page was past the end.

**Do I need residential proxies?**
No, and you should not use them. Measured on one identical URL: direct 766 ms, Apify datacenter
1,125 ms, residential GB 2,403 ms, residential US 4,749 ms — all returning identical rows.
Datacenter is the default and it is the right answer.

**How fast is it?**
1,000 rows in a single request in 23 seconds (`pageSize: 1000`). A 50-row run with apply-link
enrichment on finishes in about 20–40 seconds end to end.

**Can I paste URLs from the site instead of building a filter?**
Yes — `startUrls` accepts listing URLs *and* individual job URLs from any of the 31 locale domains,
and translates them into the right query. Job URLs are resolved directly to a single row.

**What does a pasted job URL cost?**
One **Per job returned** charge, the same as any other row. Resolving a pasted job URL has to go
through the job-detail endpoint — that call is the only way to turn a URL into a row — so the
deep-detail fields ride along **free** on that path. The **Per deep enrichment** charge still fires
only when you switch `fetchJobDetail` / `fetchCompanyProfile` on yourself (2 URLs with enrichment
off → `job-scraped: 2, job-detail-enriched: 0`, run `uuItiluIXcQibg03b`; the same 2 URLs with
`fetchJobDetail: true` → `2` and `2`, run `eNkifuKByejRMbuoP`). `maxItems` and `maxCostUsd` are
applied to the URL list *before* any request goes out, so a cap can never be overshot by pasting a
long list.

***

### ⚖️ Legality, fair use and personal data

- This actor reads **only public JSON endpoints that eFinancialCareers' own web pages call**, with
  ordinary browser headers. No login is used, no authentication is forged, no request signature is
  computed, and no CAPTCHA or anti-abuse control is touched or bypassed.
- `www.efinancialcareers.co.uk/robots.txt` reads, verbatim:
  `User-agent: *  Disallow: /secure  Disallow: /myefc  Disallow: /login  Disallow: /rememberMobilePreference  Disallow: /remote  Disallow: /profile  Disallow: /v1  Disallow: /v3  Disallow: /search … crawl-delay: 10`.
  Those directives govern the `www` origin. **This actor never fetches that host.** The API host it
  does use, `job-search-ui.efinancialcareers.com`, returns 404 for `robots.txt` — no directives at
  all. A `crawlDelaySecs` knob is provided anyway if you want to throttle.
- It does not follow advertising click-trackers, so it never spends an employer's ad budget.
- **Personal data:** with `fetchCompanyProfile` on, a row can include the names of recruiters an
  employer has published on its own eFinancialCareers company profile. Complying with GDPR /
  UK GDPR and any other applicable law when you store, enrich or contact people is **your**
  responsibility, not this actor's. Leave the toggle off if you do not need it.
- You are responsible for complying with eFinancialCareers' Terms of Service and for how you use
  what you collect.

***

*Not affiliated with or endorsed by eFinancialCareers or DHI Group.*

# Actor input Schema

## `searchQueries` (type: `array`):

Keyword searches to run, e.g. 'compliance', 'equity research', 'KYC analyst'. Each one is a separate search; results are merged and de-duplicated. Leave empty to sweep every live job (71,000+ worldwide) — with an empty keyword the site's index is already ordered newest-first.

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

Cities or regions to search, e.g. 'London', 'Frankfurt', 'Singapore', 'Zurich', 'Dubai'. VERIFIED to genuinely filter, not just relabel: London 7,629 jobs, Frankfurt 1,708, Singapore 2,320, New York 4,346, Zurich 764, Dubai 395, Sydney 753, Tokyo 436, Mumbai 503 (measured 2026-09-03).

## `radius` (type: `integer`):

Distance around each location to include. Real effect measured: Frankfurt at 40 km also pulls Darmstadt and Bad Nauheim. Ignored when no location is set.

## `radiusUnit` (type: `string`):

Unit for the search radius.

## `startUrls` (type: `array`):

Paste eFinancialCareers listing or job URLs from any of the 31 locale domains (.co.uk, .com, .de, .ch, .fr, .sg, .hk, .com.au, -gulf.com …). Listing URLs are TRANSLATED into API filters and job URLs (….id24700409) are resolved directly — the www site is never crawled. Note: the old /jobs-UK.London.s001 style URLs 301 to /jobs on the real site and silently lose their location, so this actor reads the filters out of the URL itself instead of following it. BILLING: a pasted JOB url is billed as ONE 'Per job returned' row. Resolving it goes through the job-detail endpoint (that is the only way to turn a URL into a row), so its deep-detail fields come back at no extra charge — 'Per deep enrichment' is still only charged when you switch deep enrichment on yourself.

## `locationPaths` (type: `array`):

Whole-region filter using the site's own location tree. Live counts measured 2026-09-04. For a deeper path such as 'Europe/United Kingdom/England/London', paste the listing URL into Start URLs instead — this picker holds the seven roots the site's facet returns.

## `sectors` (type: `array`):

Finance sectors, straight from the site's own facet (36 values, read live 2026-09-04). Live counts: INFORMATION\_TECHNOLOGY 34,625 · ACCOUNTING\_FINANCE 14,783 · INSURANCE 3,640 · COMPLIANCE\_LEGAL 3,055 · OPERATIONS 2,731 · ASSET\_MANAGEMENT 2,708 · PRIVATE\_BANKING\_WEALTH\_MANAGEMENT 2,281 · SALES\_MARKETING 2,244. The live list for your own search is also written to FILTER\_VOCABULARY in the key-value store on every run.

## `seniority` (type: `array`):

Seniority bands. Live counts: INTERN\_GRADUATE 3,709 · JUNIOR 1,077 · ANALYST 2,738 · ASSOCIATE\_MID\_LEVEL 38,021 · AVP\_SENIOR 14,332 · VP\_PRINCIPAL 3,614 · SVP\_HEAD\_OF 2,144 · DIRECTOR 2,128, plus MANAGING\_DIRECTOR and C\_SUITE.

## `experienceLevel` (type: `array`):

Years of experience the ad asks for, as the site bands it.

## `employmentType` (type: `array`):

Full-time or part-time, as stated on the ad.

## `positionType` (type: `array`):

Permanent, contract, temporary, self-employed or an internship / graduate trainee scheme.

## `workArrangementType` (type: `array`):

Remote, hybrid or in-office. Note this field is only stated on 9-27% of ads, so the filter is narrow by nature.

## `companies` (type: `array`):

Restrict to named employers, e.g. 'Deloitte' (998 live jobs), 'Citi', 'JPMorgan'. The lever for executive-search and competitive-intel buyers: every desk a bank is staffing, in one dataset.

## `normalizedJobTitles` (type: `array`):

The site's own normalised title taxonomy — cleaner than a keyword search. Values are EXACT, case-sensitive strings from that taxonomy (1,000 of them in the live facet, too many to list here); anything outside it returns zero rows, and every run writes the live list for your search to FILTER\_VOCABULARY in the key-value store. Live counts: Accountant 1,135 · Sales Representative Specialist 1,038 · Engineer 1,035 · Consultant 898 · Lawyer 793 · Account Manager 718 · Manager 702 · Internship 476.

## `postedWithin` (type: `string`):

Server-side freshness window. ONE = last 24h (4,172 live), THREE = last 3 days (11,051), SEVEN = last 7 days (19,033). These are the ONLY windows the site's API supports — for 14 or 30 days use the exact date fields below, which this actor applies itself.

## `postedAfter` (type: `string`):

ISO date or timestamp, e.g. 2026-08-01. Applied by this actor over the posted date, which is filled on 100% of rows — so you can express the 14- and 30-day windows the site's own API refuses.

## `postedBefore` (type: `string`):

ISO date or timestamp. Use with 'Posted on/after' for an exact date window.

## `salaryCurrency` (type: `array`):

The 23 currencies the site's own facet returns (read live 2026-09-04). Set this when using the salary bounds below — a bare number means nothing without its currency.

## `minSalary` (type: `integer`):

Server-side floor. Only meaningful together with a salary currency (verified: minSalary 100000 + GBP = 10,440 jobs).

## `maxSalary` (type: `integer`):

Server-side ceiling. Use with a salary currency.

## `onlyWithNumericSalary` (type: `boolean`):

Keep only rows carrying a real minimum/maximum salary number. Expect this to keep roughly 9 rows in 100. Rows removed here are never billed.

## `includeUnspecifiedSalary` (type: `boolean`):

Turn OFF to drop rows whose salary text is empty or the literal 'Competitive' — looser than the numeric filter above, because it keeps day-rate and text salaries like '£500 - 550p/d' (roughly 19 rows in 100 survive). Applied by this actor: the API parameter of the same name is inert, so exposing it as a server-side knob would be a lie.

## `fetchApplyUrl` (type: `boolean`):

Adds one request per job to the site's own apply-information endpoint and returns external\_apply\_url — the EMPLOYER'S OWN ATS link (lseg.wd3.myworkdayjobs.com, jobs.smartrecruiters.com/Wise, portal.emagine.org …). This is the field that turns a job ad into a company lead. INCLUDED in the per-job price, no surcharge.

## `fetchJobDetail` (type: `boolean`):

One extra request per job: the employer's real ATS requisition number (job\_reference, 83% fill), the deeper sub-sector taxonomy, recruiter\_id — which separates AGENCY-POSTED from DIRECT-EMPLOYER ads (33% fill) — the ad's allocation date, the blind-ad flag, lat/long, canonical and brand URLs, and the same job's URL on all 31 locale domains. Billed as 'Per deep enrichment'.

## `fetchCompanyProfile` (type: `boolean`):

Adds the hiring company's industry, year founded, HQ city/country, benefits list, profile URL and the named recruiters on its profile. Cached per company, so 50 jobs at one bank cost ONE profile request. Requires (and switches on) deep job detail, because the company id lives there. Billed under the same 'Per deep enrichment' event.

## `descriptionMode` (type: `string`):

'Plain text' is free — the full description is already on the listing response. 'HTML' comes from the detail endpoint and therefore costs a deep-enrichment charge per row. 'None' drops it, which is the right choice for wide CSV exports.

## `maxItems` (type: `integer`):

Hard cap on jobs returned — and therefore on what you are billed. Defaults to 50 for a fast, cheap first run. Set 0 for no cap (bounded at 200,000).

## `maxCostUsd` (type: `number`):

Budget guard. The run stops cleanly once the rows it has already delivered add up to this much, so it never charges past your number. 0 = no guard (the row cap above still applies).

## `maxPagesPerQuery` (type: `integer`):

Stop each individual search after this many pages. 0 = no per-search limit (the row cap still applies). Useful when you want a shallow slice of many searches rather than a deep dive into one.

## `pageSize` (type: `integer`):

Jobs fetched per API call. Verified working at 15, 25, 50, 100, 200, 500 and 1000 — a single call really does return 1,000 rows. Larger pages mean fewer requests but slower cold-cache responses; 100 is the measured sweet spot.

## `startPage` (type: `integer`):

Begin each search at this page instead of page 1 — for resuming an interrupted deep crawl.

## `resumeFromOffset` (type: `integer`):

Alternative to 'Start at page': resume at this row offset (converted using the page size above). Overrides 'Start at page' when greater than 0.

## `depthGuard` (type: `integer`):

The site itself stops serving results past a row offset of about 60,000 per search: offset 59,950 returns rows, 62,450 returns HTTP 500 (bisected live at two page sizes). This actor stops cleanly at the guard and tells you to narrow the search, instead of surfacing the site's 500 as a failed run. Lower it if you want a shallower crawl.

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

'Newest first' is applied by this actor: the site accepts and echoes a sort parameter but never actually reorders (16 spellings tested), so ordering it server-side would be fiction. Choosing it makes the run collect everything before writing, so the dataset comes out ordered.

## `onlyNewSince` (type: `string`):

ISO timestamp — rows posted at or before it are dropped and never billed. Set this to your last run's finish time and schedule the actor daily or weekly for a pure new-jobs feed.

## `dedupeBy` (type: `string`):

Which key identifies a job. 'id' is the site's stable job hash and is the safe default. Measured overlap on real crawls: 0.13% within one contiguous band, 1.00% across contiguous pages, 1.5% across overlapping keyword searches — all removed BEFORE anything is billed.

## `deduplicateAcrossQueries` (type: `boolean`):

On: a job found by two different searches is returned (and billed) once. Off: each search keeps its own copy, so you can see which search found what — duplicates are still charged only once per search.

## `flattenLocation` (type: `boolean`):

On: city / state / country / region as separate top-level columns, which is what CSV and Google Sheets want. Off: one nested location object.

## `descriptionMaxChars` (type: `integer`):

Cut description, summary and company-about text to this many characters — keeps rows small enough for spreadsheets. 0 = keep the full text.

## `outputFields` (type: `array`):

Whitelist of column names to keep, e.g. title, company\_name, location\_city, external\_apply\_url, posted\_date, job\_url. Leave empty for every field. The 'id' column is always kept so runs can be joined.

## `includeRawJson` (type: `boolean`):

Adds a 'raw' column holding the untouched object the site's API returned, for anyone who wants a field this actor does not map.

## `localeDomain` (type: `string`):

Which eFinancialCareers domain the job\_url column should point at. All 31 locale domains serve the same job. With deep enrichment on, the exact published URL for the chosen locale is used instead of a constructed one.

## `proxyConfiguration` (type: `object`):

Apify datacenter proxy is the right setting and the default. Measured on one identical URL: direct 766 ms, datacenter 1,125 ms, residential GB 2,403 ms, residential US 4,749 ms — all returning the same rows. Residential costs 2-4x the latency for zero benefit here.

## `proxyCountry` (type: `string`):

Two-letter country code to pin the proxy exit to, e.g. GB or US. Country pinning is only offered by proxy groups that have per-country exits - Apify's shared DATACENTER pool does not. If the group you picked cannot honour the code, the actor says so and carries on without the pin rather than losing your run; switch the proxy group to RESIDENTIAL if you truly need a fixed country. Leave empty for the fastest path.

## `maxConcurrency` (type: `integer`):

Parallelism for the enrichment requests. Search pages are always fetched one at a time, in order. Keep this modest: the site's CDN answers cold-cache requests in up to 32 seconds and hammering it buys nothing.

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

Per-request timeout. 60 is deliberate — a 45-second timeout tripped on genuinely-working cold-cache responses during testing.

## `maxRetriesPerRequest` (type: `integer`):

Not optional. Measured over 53 sustained calls: 90.6% succeeded first try and 100% within 3 tries. Every single miss was a transient CDN fault (connection reset, truncated body, cold-cache timeout) — there were no blocks, no 403s and no CAPTCHAs at any point.

## `crawlDelaySecs` (type: `number`):

Politeness pause between consecutive search pages. Default 0: the robots.txt asking for crawl-delay 10 belongs to www.efinancialcareers.\*, a host this actor never fetches — all data comes from the site's own JSON API host, whose robots.txt returns 404 with no directives. Raise it to throttle anyway.

## Actor input object example

```json
{
  "locations": [],
  "radius": 40,
  "radiusUnit": "km",
  "sectors": [],
  "seniority": [],
  "experienceLevel": [],
  "employmentType": [],
  "positionType": [],
  "workArrangementType": [],
  "companies": [],
  "normalizedJobTitles": [],
  "postedWithin": "ANY",
  "salaryCurrency": [],
  "onlyWithNumericSalary": false,
  "includeUnspecifiedSalary": true,
  "fetchApplyUrl": true,
  "fetchJobDetail": false,
  "fetchCompanyProfile": false,
  "descriptionMode": "text",
  "maxItems": 50,
  "maxCostUsd": 0,
  "maxPagesPerQuery": 0,
  "pageSize": 100,
  "startPage": 1,
  "resumeFromOffset": 0,
  "depthGuard": 59900,
  "sortBy": "relevance",
  "dedupeBy": "id",
  "deduplicateAcrossQueries": true,
  "flattenLocation": true,
  "descriptionMaxChars": 0,
  "includeRawJson": false,
  "localeDomain": "en_GB",
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "maxConcurrency": 5,
  "requestTimeoutSecs": 60,
  "maxRetriesPerRequest": 3,
  "crawlDelaySecs": 0
}
```

# Actor output Schema

## `items` (type: `string`):

The dataset of scraped eFinancialCareers jobs (one job per row).

# 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 = {
    "locations": [],
    "sectors": [],
    "seniority": [],
    "experienceLevel": [],
    "employmentType": [],
    "positionType": [],
    "workArrangementType": [],
    "companies": [],
    "normalizedJobTitles": [],
    "salaryCurrency": [],
    "fetchApplyUrl": true,
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/efinancialcareers-jobs-scraper").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 = {
    "locations": [],
    "sectors": [],
    "seniority": [],
    "experienceLevel": [],
    "employmentType": [],
    "positionType": [],
    "workArrangementType": [],
    "companies": [],
    "normalizedJobTitles": [],
    "salaryCurrency": [],
    "fetchApplyUrl": True,
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/efinancialcareers-jobs-scraper").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 '{
  "locations": [],
  "sectors": [],
  "seniority": [],
  "experienceLevel": [],
  "employmentType": [],
  "positionType": [],
  "workArrangementType": [],
  "companies": [],
  "normalizedJobTitles": [],
  "salaryCurrency": [],
  "fetchApplyUrl": true,
  "maxItems": 50
}' |
apify call scrapersdelight/efinancialcareers-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/efinancialcareers-jobs-scraper"
        }
    }
}

```

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/jFv6MraLGKdJdzcx3/builds/L5iZ8H3WFnmoqLKrQ/openapi.json
