# Voice Over Jobs Monitor (`pradio/vo-jobs-pipeline`) Actor

One scheduled sweep for voice-over work: paid casting listings from four public boards, the state of the talent doors you watch, and companies that buy VO but publish no door. Returns only what is new since the last run. Never guesses an email or writes outreach.

- **URL**: https://apify.com/pradio/vo-jobs-pipeline.md
- **Developed by:** [E A](https://apify.com/pradio) (community)
- **Categories:** Jobs, Automation, Agents
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.54 / 1,000 listing founds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Voice Over Jobs Monitor

Finds voice-over work on a schedule and returns only what is new since the last run.

Three kinds of row:

- **Casting listings** from four public boards, paid work only by default.
- **Talent doors** — the submission forms and standing audition pages at studios you
  want to be on file with, and whether each one is open, closed or newly reopened.
- **Companies that buy voice-over** but have no submission page at all.

Every value is copied from a page. Nothing is guessed, no email is invented, and it never
writes an approach for you.

### Why

Voice-over work is scattered across boards that have no feed and no export. The studios
worth being on file with open their submission form quietly and close it just as quietly.
And the companies that buy the most voice-over never post a casting at all.

Checking all that by hand is the same twenty tabs every morning. This does the sweep and
hands you the difference.

### Who it is for

**A working voice actor** gets the most from Casting Call Club (paid Voice Actor projects
only), Casting Callback UK, and the talent doors you name. Those rows carry a rate, a
deadline, and what the page says about AI.

**Someone building a reel** will also want Bluesky. It is the community lane: indie games,
fan dubs, audio dramas and webcomics, usually with no rate stated. Real work, rarely
professional rates. If you only want paid professional listings, leave `bluesky` out of
`sourceAllowlist` and set a pay floor.

The boards are English-language and the pay is mostly in USD and GBP.

### The three lanes

Every row carries a `lane`. Lanes are never mixed on one row.

#### `board` — casting listings

| Source | What it reads |
| --- | --- |
| Bluesky | The community lane: indie and fan projects, most with no rate stated. A casting feed plus the accounts you name, through the public ATProto API. It ships with a starter list of ten accounts that post voice castings; `blueskyHandles` replaces it. A feed post is kept only when it names voice work, links a Casting Call Club project or tags itself `#voiceacting`, and has somewhere to apply. Posts over 14 days old are dropped. The default feed is one another Bluesky user publishes, so set `blueskyFeedUri` if you would rather not depend on it |
| Casting Call Club | The site's public listing data, paid Voice Actor projects only, at the crawl delay it asks for |
| ytjobs.co | Voice Over roles from the public jobs API, with an RSS fallback |
| Casting Callback UK | The voice-over auditions index. Title, link, pay and a short summary only |

#### `on_file` / `reopen_watch` — talent doors

Give it the roster or "work with us" URLs you care about. For each one you get:

- what kind of door it is: a form, a Typeform, a Google Form, an email address or a
  vendor portal;
- whether it is open, closed or paused, plus the sentence on the page that says so;
- whether it is free, fee-paying or commission-only;
- whether it asks for a home studio;
- whether the page carries exclusivity language;
- what the page says about AI, and which way round.

A door that was open and has since closed comes back as `reopen_watch` rather than
disappearing, so you find out when it opens again.

#### `cold_prospect` — companies with no door

Give it company homepages, as a list or a CSV. It returns the ones with a published sign
that they buy voice work, what sector they are in, whether they say they record remotely,
and a contact email **if the site publishes one**.

If a homepage turns out to have a talent form after all, the row moves to the Direct lane
instead. A published door always wins.

This lane checks the homepages you give it. It does not go looking for companies.

### Setting it up for your voice

Nothing here is preset to anyone's profile. The defaults run wide open and you narrow
them:

| To | Set |
| --- | --- |
| Only see listings that name your accent | `accentAllowlist`, but read the warning below first. Leave empty to see everything |
| Only see roles for your gender | `genderFilter`: `any`, `male` or `female`. Listings that do not say, or that want both, always pass |
| Only professional-rate listings | `sourceAllowlist` without `bluesky`, and a `minPayAmount`. A listing with no rate stated still passes a pay floor, because a blank is not a no |
| Set a pay floor | `unpaidSkip`, `minPayAmount`, `minPayCurrency`, `minPayUnit` |
| Filter by place | `locations` and `regions`, e.g. `Remote`, `US`, `EMEA` |
| Choose your sources | `sourceAllowlist` |
| Watch your own studios | `rosterWatchUrls` |

**Before you set `accentAllowlist`, know what it does.** It keeps only listings that
name an accent **and** name one of yours — so a listing that mentions no accent at all is
excluded, and most listings mention none. It is a narrow tool, not a general one.

It understands six words, because those are the only accent words the Actor reads off a
page: **Neutral, General, Standard, GenAm, American English, International English**. The
input is a picker rather than a text box, so nothing else can be passed.

**If you are not in the UK:** three of the four board sources are international, but
Casting Callback UK is a UK site. Leave it out of `sourceAllowlist`.

For example, a US-based performer might run `sourceAllowlist` of `bluesky`, `ytjobs` and
`casting_call_club`, `genderFilter` of `female`, a `minPayAmount` of 100 in `USD`, and
`locations` of `Remote` and `US`.

### The AI clause, and which way it points

Board and Direct rows carry `aiUse`, and it is the one field here worth reading first:

| `aiUse` | What the page said |
| --- | --- |
| `prohibited` | Every mention of AI is negated — "no AI training", "will never be cloned". A protective clause |
| `required` | The page asks to use the voice for AI: training, cloning, a synthetic replica |
| `mentioned` | AI language is on the page but it does not say which way |
| `none` | The page was read and says nothing about AI |
| empty | There was no text to read. Not the same answer as `none` |

`aiEvidence` carries the phrase that decided it, so you can check the reading rather than
trust it.

Every yes-or-no field here works the same way: **true**, **false** when the page was read
and the answer is no, and **empty** when there was nothing to read. A blank is never a no.

The same goes for a whole source. If one is blocked, or answers with a "checking your
browser" page, the run names it in the summary under `unreadableSources` instead of
quietly reporting nothing new. Zero listings because a board was unreachable and zero
because nobody posted are different days.

### Requirement flags

Board and Direct rows carry `requirementFlags`: the accent, language or location
requirements written on the page. "RP", "Scottish", "UK native only", "voiceover in
Spanish", and so on.

**They are a signal, not a filter.** No row is ever dropped for carrying one. They are
there so a listing asking for something you are not can be spotted without opening it.

The list of words it recognises is fixed. It currently leans towards British and Irish
accent requests: RP, regional British and Irish accents, Australian, New Zealand, "UK
native only", and six non-English voice languages. If you are an
American or neutral-English voice, that is useful — those flags mark work you cannot
take. **If you are a British or Irish voice, the flags will fire on exactly the work you
want**, so read them as "this listing names an accent" and ignore the rest. Widening that
vocabulary is the next thing on the list.

### What one row looks like

```json
{
  "lane": "board",
  "source": "bluesky",
  "url": "https://www.example-casting.test/jobs/explainer",
  "title": "CASTING CALL! Neutral-accent voiceover for an explainer",
  "accentTags": ["Neutral"],
  "payRaw": "$400",
  "isPaid": true,
  "deadline": "2026-10-02",
  "requirementFlags": [],
  "aiUse": "none"
}
```

```json
{
  "lane": "on_file",
  "doorType": "form",
  "url": "https://example-studio.test/submit-demo",
  "company": "Example Studio",
  "status": "open",
  "statusEvidence": "We are currently accepting new voice submissions",
  "contactEmail": "casting@examplestudio.test",
  "freeVsFee": "free",
  "homeStudioAsked": true,
  "aiTrainingRisk": false,
  "aiUse": "prohibited",
  "aiEvidence": "no AI training or voice cloning of any recording"
}
```

```json
{
  "lane": "cold_prospect",
  "company": "Northbeam Explainers",
  "url": "https://northbeam-explainers.test/",
  "buyer_type": "explainer",
  "email": "hello@northbeam-explainers.test",
  "vo_buy_signal": "hire professional voice-over talent for client films",
  "has_talent_form": false,
  "records_remote": "Y"
}
```

Download any run as JSON, CSV or Excel from the Output tab, or read it through the Apify
API. Each run also writes a `SUMMARY` record with counts per lane and per source.

### Only what is new

Name a Key-Value store in `dedupeKvStoreName` and leave `onlyNew` on. Every run then
returns the difference instead of the whole board again.

- Listings and doors are remembered by URL or listing id.
- Companies are remembered by domain, so `studio.co.uk` and `www.studio.co.uk` are one.
- A door still comes back when its **status** changes, because that is the news.

Use the same store name for all your scheduled tasks and the three lanes share one memory.

### Input

Everything is optional. The defaults run all three lanes.

| Input | What it does |
| --- | --- |
| `lanesEnabled` | Which of `board`, `on_file`, `cold_prospect` to run |
| `onlyNew` | Return only what is new. On by default |
| `dedupeKvStoreName` | The store that remembers what you have seen |
| `maxItemsPerSource` | Cap per source. Keep it low on your first run |
| `sourceAllowlist` | Run only the sources you name |
| `rosterWatchUrls` | The talent doors to watch |
| `seedHomepageUrls` / `seedCsvPath` | The company homepages to check |
| `blueskyFeedUri` / `blueskyHandles` | The casting feed and accounts to read |
| `unpaidSkip`, `minPayAmount`, `minPayCurrency`, `minPayUnit` | Pay filters |
| `genderFilter`, `accentAllowlist`, `locations`, `regions` | Board filters, all off by default |
| `buyerTypes`, `genres` | Company filters |
| `proxyConfiguration` | Off by default. These sources answer a plain HTTP request |

### What it will not do

- **Invent a value.** An accent tag is set only when the page writes the word. Pay is
  parsed only when a figure is on the page. A contact name appears only when published.
- **Guess an email.** No `firstname@company.com` built from a pattern. Only an address
  the site has published.
- **Write outreach.** No suggested angle, no draft message. It returns facts and stops.
- **Apply for anything.** You decide what to do with a row.
- **Read Discord, Voices.com or LinkedIn.** It refuses those hosts.
- **Copy job text wholesale.** Casting Callback UK rows carry a title, link, pay and a
  short summary, deliberately.

### Limits

- ytjobs Voice Over listings are often empty. It tries the jobs API by category, then by
  role title, then RSS, and returns nothing rather than something else.
- Casting Callback UK detail pages are not fetched, by design.
- No Voices.com, Spotlight, Equity, Backstage or Mandy — paywalled or off limits.
- The Casting Call Club browser fallback is optional. The plain HTTP path is the default.
- The requirement-flag vocabulary is fixed, as described above.

### Personal data

Some rows contain personal data, such as a published contact name or a casting address.
This Actor returns only what a site has chosen to publish, and never builds an address
that is not on the page.

Anything you collect is yours to look after: have a lawful reason to hold it, keep it no
longer than you need it, and honour any request to remove it. Do not use it for
unsolicited bulk mail.

### FAQ

**Do I need an account on these sites?** No. Every source is read as a member of the
public.

**Will it apply for me?** No, and it never will.

**How often should I run it?** Daily for listings, weekly for doors and companies. With
`onlyNew` on, a daily run usually returns a handful of rows or none.

**Why has a row I already saw come back?** Either the store name changed, or a door
changed status — an open one closed, or a closed one reopened.

**Can I use it for another language?** The board sources are English-language. It will
run, but it is not built for that yet.

**Something is wrong or missing?** Use the Issues tab on the Actor page.

# Actor input Schema

## `lanesEnabled` (type: `array`):

Which product lanes to scrape this run. on\_file also emits reopen\_watch when a door is closed or paused. Never mix lanes on a single row.

## `onlyNew` (type: `boolean`):

When true, skip rows whose dedupe key was already stored in dedupeKvStoreName. Board/Direct keys are url or listingId; cold\_prospect keys are the registrable domain. Direct rows re-emit if status/lane changed.

## `dryRun` (type: `boolean`):

Skip the network and emit fixture rows through the same routing, filters, and KV dedupe path. Use this for apify run on a laptop.

## `dedupeKvStoreName` (type: `string`):

Persists across scheduled Task runs so onlyNew can emit deltas. Create once and reuse the same name on every Task.

## `maxItemsPerSource` (type: `integer`):

Cap listings or homepages processed for each source in this snapshot.

## `sourceAllowlist` (type: `array`):

If empty, every source for the enabled lanes runs. Otherwise only these run. The board sources are bluesky, ytjobs, casting\_call\_club and casting\_callback\_uk; roster\_watch is the Direct lane and cold\_prospect the Prospects lane.

## `blueskyFeedUri` (type: `string`):

Public AppView feed. Fetched over HTTPS from public.api.bsky.app — no residential proxy required.

## `blueskyHandles` (type: `array`):

Accounts whose public posts are read for castings, on top of the feed. The default is a starter list of ten accounts that post voice-over castings; replace it with your own. One repost account (vacastingrt) is always skipped because it duplicates what the feed already carries.

## `blueskyAppViewUrl` (type: `string`):

Override only if you pin a different public AppView.

## `rosterWatchUrls` (type: `array`):

Published talent doors (form, Typeform, Google Form, casting@ mailto, vendor portal). Closed or paused pages emit lane=reopen\_watch.

## `seedHomepageUrls` (type: `array`):

Company homepages. If a talent door is found, the row is emitted on Direct — never as cold\_prospect.

## `seedCsvPath` (type: `string`):

HTTP URL, local path, or default KV-store key. Columns: url|homepage|website and optional company.

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

Off by default. Enable Apify Datacenter proxy only if a source starts blocking. Prefer not to use residential.

## `genderFilter` (type: `string`):

Drop board rows that clearly conflict. any keeps unknown gender.

## `unpaidSkip` (type: `boolean`):

When true, board rows with isPaid=false are not emitted. Default true (sensible for paid-board Tasks); set false to keep unpaid.

## `minPayAmount` (type: `number`):

Board only. If set, drop rows whose parsed amount is below this floor when currency is compatible. Missing pay does not drop. Different known pay units vs minPayUnit are not compared.

## `minPayCurrency` (type: `string`):

ISO code such as USD or GBP. Empty applies the amount floor to any parsed currency. Missing/different currency does not force a drop.

## `enablePlaywrightFallback` (type: `boolean`):

CCC HTTP uses projects\_kick with search=voice actor and payment=paid (nc:voice\_actor is not a role filter). Playwright is only used if that API returns no Voice Actor rows.

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

Soft place hints matched against board title/description/company (e.g. Remote, UK, US, London). Empty = no location filter. Unknown/blank location listings still pass.

## `regions` (type: `array`):

Complementary to locations (e.g. EMEA, North America, EU). Empty = no filter. Combined with locations as an OR token list.

## `accentAllowlist` (type: `array`):

Keep only board listings whose page NAMES one of these accents. These six are the only accent words this Actor reads off a page, so nothing else can match. Leaving it empty is the usual choice: a listing that names no accent at all is excluded by any allowlist, and most listings name none.

## `minPayUnit` (type: `string`):

When set with minPayAmount, only enforce the floor when the row unit matches or is unknown (hour/word/project/day/finished\_hour). Prevents comparing e.g. per-word rates against an hourly floor.

## `buyerTypes` (type: `array`):

Cold lane only. If non-empty, drop prospects whose buyer\_type is set and not in this list. unknown always passes. Empty = no filter.

## `genres` (type: `array`):

Cold lane only. Soft match against client\_sectors when both are non-empty. Empty = no filter.

## Actor input object example

```json
{
  "lanesEnabled": [
    "board",
    "on_file",
    "cold_prospect"
  ],
  "onlyNew": true,
  "dryRun": false,
  "dedupeKvStoreName": "vo-jobs-seen",
  "maxItemsPerSource": 25,
  "sourceAllowlist": [],
  "blueskyFeedUri": "at://did:plc:5otr6wywnayoshmgypbodhyl/app.bsky.feed.generator/aaakequc66hv2",
  "blueskyHandles": [
    "nsipstudio",
    "wadjeteyegames.com",
    "soughtaftercast",
    "soundswilde",
    "veryberrystudios",
    "castingcalls",
    "audioafauditions",
    "audiodramahub",
    "starrygstudios",
    "campcryptidshow"
  ],
  "blueskyAppViewUrl": "https://public.api.bsky.app",
  "rosterWatchUrls": [],
  "seedHomepageUrls": [],
  "seedCsvPath": "",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "genderFilter": "any",
  "unpaidSkip": true,
  "enablePlaywrightFallback": true,
  "locations": [],
  "regions": [],
  "accentAllowlist": [],
  "buyerTypes": [],
  "genres": []
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset items tagged with lane (board, on\_file, reopen\_watch, cold\_prospect).

## `run` (type: `string`):

Open this run in Apify Console.

## `summary` (type: `string`):

Per-lane and per-source counts written to the default Key-Value store as SUMMARY.

# 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 = {
    "lanesEnabled": [
        "board",
        "on_file",
        "cold_prospect"
    ],
    "dedupeKvStoreName": "vo-jobs-seen"
};

// Run the Actor and wait for it to finish
const run = await client.actor("pradio/vo-jobs-pipeline").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 = {
    "lanesEnabled": [
        "board",
        "on_file",
        "cold_prospect",
    ],
    "dedupeKvStoreName": "vo-jobs-seen",
}

# Run the Actor and wait for it to finish
run = client.actor("pradio/vo-jobs-pipeline").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 '{
  "lanesEnabled": [
    "board",
    "on_file",
    "cold_prospect"
  ],
  "dedupeKvStoreName": "vo-jobs-seen"
}' |
apify call pradio/vo-jobs-pipeline --silent --output-dataset

```

## MCP server setup

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

```

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/z5RkFLJdJocah2UPd/builds/MEYf0EguQLiHfCa5e/openapi.json
