Automated Media Lists
Medialyst can turn a campaign brief into a researched journalist list in two ways: hand the brief to a person with a browser deep link, or run the entire workflow from your system with the asynchronous REST API.
Both methods create the same organization-scoped media-list table used by the Medialyst app.
Choose A Method
| Method | Best for | Human approval | When list creation starts |
|---|---|---|---|
| Browser deep link | Buttons, CRM links, internal tools, and handoffs to a person | Required | After the person reviews and approves the plan |
| Asynchronous REST API | Agents, backend services, scheduled jobs, and unattended workflows | Not required | Immediately after the API accepts the request |
Use the API as the primary method when the workflow should complete without a person. Use a deep link when you want Medialyst to prepare the workflow but keep a person in control of approval and credit spend.
Approval-Gated Browser Deep Link
Send a signed-in user to this URL with a URL-encoded campaign brief:
https://medialyst.ai/app/_/workflow/campaign?prompt=[URL-ENCODED-PROMPT]The _ segment resolves to the user's current organization. Medialyst preserves the destination through sign-in or onboarding, automatically submits the prompt to the campaign agent, and presents the proposed plan for review. No credits are spent until the user approves the plan. Normal media-list credit usage begins after approval.
Build the link with a URL API instead of concatenating unescaped text:
const prompt =
"Find Canadian journalists covering PR technology and AI media tools.";
const url = new URL("https://medialyst.ai/app/_/workflow/campaign");
url.searchParams.set("prompt", prompt);
console.log(url.toString());The resulting link can be placed behind a Build in Medialyst button, returned by another agent, or added to a CRM record. Because query strings can appear in browser history and server logs, do not put credentials or other secrets in the prompt.
Fully Automated REST API
Use the asynchronous media-list API when an agent or backend service should create and retrieve the list without waiting for a person to approve it. The create request returns immediately with a job ID while discovery and enrichment run in the background.
The API bypasses the browser approval step and starts list creation as soon as the request is accepted. Normal media-list credits and plan limits apply.
An API list gets the same treatment as a list approved in the app: after the initial search and Journalist Profile enrichment, a Beat Sweep researches every angle in the plan and keeps adding the journalists it finds until your credit budget is spent or the angles run dry. See How a list is built below.
After the background job creates the table, it appears on the Media Lists page for members of the API key's organization. If that page is already open, refresh it to load lists created outside the browser session.
Before You Start
Create an API key from the Medialyst Developers page with the media_lists:manage scope. Store the key in secret storage or an environment variable such as MEDIALYST_API_KEY.
All examples below use https://medialyst.ai/api as the API base URL.
1. Create A List Job
Send a campaign brief and the most credits the list may use:
curl --request POST \
'https://medialyst.ai/api/v1/media-lists:create-async' \
--header "Authorization: Bearer $MEDIALYST_API_KEY" \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: launch-brief-2026-07' \
--data '{
"prompt": "Find US journalists covering enterprise AI infrastructure and cloud spending for our product launch.",
"target_list_size": 50
}'| Field | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Campaign idea, press release, or targeting brief. Maximum 2,000 characters. |
target_list_size | integer | Yes | The maximum credits the list may consume, from 1 to 1,000 (the ceiling on paid plans; lower plan limits apply). One credit buys one row that delivers a verified, deliverable email. The finished list can hold more rows than this number. |
The response is accepted immediately:
{
"job_id": "job_01HT...",
"status": "pending",
"target_list_size": 50,
"status_url": "/api/v1/jobs/job_01HT..."
}Use the returned target_list_size as the effective budget. It can be lower than the requested value when the organization's plan has a lower limit.
Target size is a credit budget, not a journalist count
target_list_size is the most credits the list may consume. Each row
that delivers a verified, deliverable email costs one credit; rows that
never resolve to a journalist are left blank, are not backfilled, and
cost nothing, so they do not count against the budget. A journalist with
several matching articles appears once per article, and each of those
rows that delivers an email costs a credit. Rule of thumb: ask for
roughly twice the number of named journalists you want.
How A List Is Built
- Planning. The prompt is turned into a story idea, search keywords, and up to five named angles.
- Initial search. Articles are collected per angle, up to
target_list_sizerows, and the table is created with one row per article. Rows show Via API in the pinned Sources column. - Enrichment. Every row runs Journalist Profile enrichment (byline, outlet, verified email, fit score). Each row reserves one credit from the budget while it enriches; a row that delivers a verified, deliverable email keeps its credit as spend, and any other outcome releases it.
- Beat Sweep. One Beat Sweep Agent per angle researches the beat and adds further article rows, tagged with the angle name, until the budget is spent or the angles run dry. Because blank rows give their credit back, the sweep keeps going past
target_list_sizerows when it has to, soresult.total_rowscan exceedtarget_list_size. The sweep runs alongside enrichment and never bills past the budget. It is skipped when the plan has no angles or when the organization has no credits left; the job'sbeat_sweepobject says which.
A list stops growing at twice target_list_size rows, whatever the budget says, and the sweep also ends on its own time and search limits. A job with an unspent budget at that point has simply run out of things to find.
Billing
Each row that delivers a verified, deliverable email address costs one credit, whether it came from the initial search or from the Beat Sweep, and the list never bills more than target_list_size credits in total. Blank rows and rows without a verified email cost nothing and do not count against the budget. A journalist with several matching articles appears once per article, and each billable row costs a credit. See the credits guide for balances and top-ups.
Idempotency
Send a stable, unique Idempotency-Key for every logical creation request. Keys are scoped to your account and never expire. Repeating the same key with the same prompt and effective target returns the existing job, whatever its current state, as a 202 Accepted with the same job_id and its current status. Reusing a key with different input returns 409 Conflict. Use a new key to create a genuinely new list.
For compatibility, existing integrations may continue sending topic instead of prompt and max_articles instead of target_list_size. New integrations should use the canonical fields shown above. If both size fields are supplied, their values must match.
2. Poll The Job
Poll the returned status URL every 10 to 15 seconds until status is complete, failed, or cancelled. Jobs complete on their own: the worker marks a job complete once every Journalist Profile row has finished and the Beat Sweep (if one ran) has ended, whether or not anyone polls. There are no webhooks yet.
curl \
'https://medialyst.ai/api/v1/jobs/job_01HT...' \
--header "Authorization: Bearer $MEDIALYST_API_KEY"While the list is being built, the response includes the coarse stage, progress, row counts, the Beat Sweep state, and the credit budget:
{
"job_id": "job_01HT...",
"status": "processing",
"stage": "beat_sweep",
"target_list_size": 50,
"progress": {
"stage": "beat_sweep",
"percent": 72,
"message": "Beat Sweep researching angles"
},
"result": {
"workflow_id": "tbl_01HS...",
"total_rows": 57,
"ready_rows": 41,
"failed_rows": 2
},
"beat_sweep": {
"status": "dispatched",
"rows_added": 21,
"run_id": "run_01HV..."
},
"budget": {
"credits": 50,
"spent": 31,
"reserved": 14
}
}Once the job is complete, reserved is 0 and spent is what the list billed:
{
"job_id": "job_01HT...",
"status": "complete",
"stage": "done",
"target_list_size": 50,
"progress": {
"stage": "complete",
"percent": 100,
"message": "Job completed (2 rows could not be enriched)"
},
"result": {
"workflow_id": "tbl_01HS...",
"total_rows": 63,
"ready_rows": 61,
"failed_rows": 2
},
"beat_sweep": {
"status": "completed",
"rows_added": 27,
"run_id": "run_01HV..."
},
"budget": {
"credits": 50,
"spent": 48,
"reserved": 0
}
}Status values are:
| Status | Meaning |
|---|---|
pending | The request was accepted but has not started. |
processing | Planning, journalist enrichment, or the Beat Sweep is running. |
complete | Every Journalist Profile row has finished (completed or failed) and the Beat Sweep, if one ran, has ended. |
failed | The job stopped. Inspect the response's error object and retry only when retryable is true. |
cancelled | The media list was deleted while the job was open (error.code is TABLE_DELETED). Deleting a list frees its slot. |
stage is one of planning, enrichment, beat_sweep, done, failed, or cancelled. result.ready_rows counts rows whose Journalist Profile completed; result.failed_rows counts rows whose enrichment failed permanently. A job can be complete with ready_rows below total_rows.
beat_sweep is null until the sweep is dispatched, then carries a status of dispatched, completed, cancelled, failed, or skipped (with a reason such as no_angles, target_reached, or out_of_credits), plus rows_added, the number of rows the sweep put in the table.
budget is null until the table exists, then reports target_list_size as a credit budget: credits is the cap, spent is what rows with a verified email have billed so far, and reserved is what rows still enriching are holding. spent never exceeds credits. Compare result.total_rows with budget.spent to see how many rows came back blank.
The API allows at most three active (pending or processing) media-list jobs per API key. Jobs leave that count when they complete, fail, or are cancelled, and deleting a list cancels its job.
3. Read Results
Add include=results to read normalized journalist rows from the job endpoint:
curl \
'https://medialyst.ai/api/v1/jobs/job_01HT...?include=results&limit=50' \
--header "Authorization: Bearer $MEDIALYST_API_KEY"The response returns normalized journalists in the top-level rows array. Each row uses the stable journalist_list_v1 shape, including journalist identity and contact fields, outlet information, match score and reasoning, recent articles, and the source workflow ID. Use page.next_cursor as cursor on the next request until it is null. A page can contain at most 200 rows.
Results can be requested while the job is processing. Fields produced by Journalist Profile enrichment may remain null until the corresponding row is ready.
When an address belongs to a contributor's own current organisation rather
than the listed outlet, signals.email_source is "affiliation" and
journalist.email_affiliation contains its organization and domain.
These labelled, deliverable addresses are normal enriched-email results and
are chargeable under the same rules as other returned email addresses.
UI Visibility And Ownership
- The generated list is visible to members of the same organization as the API key.
- It counts toward that organization's active media-list allowance.
- It appears in the app after the worker creates the workflow table; the initial
202 Acceptedresponse alone does not mean the table exists yet. - A failure before table creation produces no list in the UI. A later pipeline failure can leave a partially populated list available for inspection.
Continue Building The List
The deep link or API creates the starting table; it is not a one-time export. Open the finished media list and use Add journalists to append people by name, from a CSV, from article URLs, through news search, or with AI discovery.
The pinned Sources column records each batch and when it was added. API-created rows show Via API; later rows keep the label for their own addition path. See Find Journalists for the five paths and Tables for source filtering and deduplication.
Common Errors
| HTTP status | Meaning | What to do |
|---|---|---|
400 | Missing, empty, mismatched, or out-of-range input | Correct the request body before retrying. |
401 | Missing or invalid API key | Check the bearer token. |
403 | Key lacks media_lists:manage | Create or use a key with the required scope. |
402 | Organization cannot create another list | Delete an existing list or change the plan before retrying. |
409 | Idempotency key was reused with different input | Use the original input or a new idempotency key. |
429 | Request rate or active-job limit reached | Back off before retrying; wait for active jobs to finish. |
Each API key can have at most 3 list jobs in pending or processing at once; a fourth create call returns 429 RATE_LIMITED until one finishes. Jobs finish on their own without polling, and deleting a list frees its slot immediately. The API-wide limit is 60 requests per minute per key — see Rate Limits.
For the generated request schema and operation metadata, see the interactive REST API reference or raw OpenAPI document.