Automated Media Lists
Medialyst can turn a campaign brief into a researched journalist list in three ways: hand the brief to a person with a browser deep link, call the asynchronous REST API, or run the same asynchronous contract through the hosted MCP server.
All three 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 |
| Hosted MCP | MCP-capable agents that should create and inspect a list in one tool session | Not required | Immediately after the create tool is accepted |
Use the API as the primary method when the workflow should complete without a person: choose hosted MCP for an MCP-capable agent and REST for a backend service. Use a deep link when you want Medialyst to prepare the workflow but keep a person in control of approval and credit spend.
Fully Automated MCP
Connect to https://medialyst.ai/api/mcp, then use this exact tool sequence:
create_media_listwithprompt, the credit-budgettarget_list_size, and a stablerequest_options.idempotency_key.get_media_list_jobwith the returnedjob_iduntilstatusiscomplete,failed, orcancelled.- On completion,
get_media_listwith the returnedmedia_list_id,include_rows: true,row_detail: "full", andlimit: 100; continue withpage.next_cursoruntil it isnull.
The create response exposes job_id, media_list_id, workflow_id,
media_list_url, and results_url, and each MCP response includes
next_tool guidance. These tools delegate to the REST operations documented
below, so authentication, scopes, organization isolation, plan limits,
free-tier behavior, budget settlement, idempotency, errors, and response fields
do not form a separate MCP contract. See Medialyst MCP
for connection instructions.
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, stable list/table identifiers, and a durable Medialyst URL 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.
The API creates the empty table before returning 202 Accepted, so the returned media_list_url does not have a table-creation 404 race. The list appears on the Media Lists page immediately 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...",
"media_list_id": "tbl_01HS...",
"workflow_id": "tbl_01HS...",
"media_list_url": "https://medialyst.ai/app/media-lists/tbl_01HS...",
"results_url": "https://medialyst.ai/api/v1/media-lists/tbl_01HS...?include_rows=true&row_detail=full&limit=100"
}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.
media_list_id and workflow_id are two names for the same stable table identifier. media_list_url is an absolute, signed-in Medialyst app URL: a Teams bot can post it immediately, and Medialyst still checks the visitor's session and organization membership before redirecting to the table under the organization's current slug. It survives organization slug changes. It is not a public share link and does not contain a share token. results_url is the canonical API-key-authenticated full-table read described in Read Results.
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.
During rollout, replaying a job created before durable table preallocation can temporarily return null for media_list_id, workflow_id, media_list_url, and results_url. Poll status_url; the legacy worker fills those fields after it attaches the table. Newly created jobs always return the locators shown above.
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,
"media_list_id": "tbl_01HS...",
"workflow_id": "tbl_01HS...",
"media_list_url": "https://medialyst.ai/app/media-lists/tbl_01HS...",
"results_url": "https://medialyst.ai/api/v1/media-lists/tbl_01HS...?include_rows=true&row_detail=full&limit=100",
"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,
"media_list_id": "tbl_01HS...",
"workflow_id": "tbl_01HS...",
"media_list_url": "https://medialyst.ai/app/media-lists/tbl_01HS...",
"results_url": "https://medialyst.ai/api/v1/media-lists/tbl_01HS...?include_rows=true&row_detail=full&limit=100",
"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 during planning until the table's first rows and credit budget are installed, 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. The table identifiers and URLs are already present while budget is null. 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
For the complete first-party table used by the Medialyst website, call the returned results_url with the same bearer key:
curl \
'https://medialyst.ai/api/v1/media-lists/tbl_01HS...?include_rows=true&row_detail=full&limit=100' \
--header "Authorization: Bearer $MEDIALYST_API_KEY"The response includes table metadata, ordered columns (id, display name, and workflowType), and full row values keyed by those column IDs. It also carries the source data and safe row-origin metadata used by the website's pinned Sources column. This preserves fields beyond the normalized journalist projection, including the source Article, full Journalist Profile, Score, Pitch Angle, Why They Fit, and any columns authorized users add while refining the table. Follow page.next_cursor by sending it as cursor until it is null; each page can contain up to 100 rows.
The endpoint uses the same media_lists:manage scope and organization boundary as the signed-in table. Contact masking and per-contact unlocks are no longer product gates; if an older list still contains masked contact values, the read restores them for every plan. The URL is not public: callers must keep sending the API key. Leave include_schema unset (its default is false) to receive column identity/type metadata without workflow execution configuration.
When Medialyst temporarily pauses new free-tier search work during a capacity incident, existing job polling and results_url reads remain available.
The original normalized projection remains available for existing integrations. Add include=results to 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 backward-compatible 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. New integrations that need website-equivalent columns should use results_url; include=results intentionally remains a smaller stable projection for legacy clients.
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.
- The table exists before
202 Acceptedis returned, so authorized users can openmedia_list_urlimmediately while its name, columns, and rows are filled in. - A pipeline or dispatch failure leaves the durable empty or partially populated list available for inspection; the job's
errorexplains the failure. - No public share is created automatically. A signed-in visitor must belong to the API key's organization, and API reads still require the bearer key.
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.