Managed Prospecting API
Managed prospecting interprets company and persona requirements, then runs company matching or discovery, ICP validation, indexed contact search, ContaGen gap filling, and email finding. Optional segmentation groups accepted companies. A run can also deliver companies only, with no contact work (see Companies-Only Runs). REST runs wait for your approval before research starts; chat messages let you steer the run or answer a clarification question.
Start a Run
Section titled “Start a Run”POST /prospecting/runs
Provide normal API authentication, a JSON body, and an Idempotency-Key header (1–128 characters). Reuse the key and identical request when retrying. A changed request, including changing an omitted setting into an explicit setting, returns 409 for the same key. The response is 202 with a stable run_id and drafting status; planning continues after you disconnect.
| Field | Type | Default | Description |
|---|---|---|---|
brief | string | Required | Company and persona requirements, 10–4,000 characters. |
domains | string[] | [] | Explicit target companies, up to 1,000; these are not lookalike examples. |
company_names | string[] | [] | Up to 100 company names to match. |
customer_domains | string[] | [] | Up to 1,000 of your customers’ domains. The run groups them into segments and finds lookalikes of each; they are never returned as results. Cannot be combined with domains or company_names (422). |
exclude_domains | string[] | [] | Up to 1,000 domains to suppress. |
target_companies | integer | 1000 | Companies with accepted contact coverage (matching companies on a companies-only run); 1–10,000. If omitted, the brief can supply the count. |
contacts_per_company | integer | 3 | Accepted contacts per company; 1–10. If omitted, the brief can supply the count. |
max_candidates | integer | 0 | Automatic when 0; otherwise 1–100,000, at least the target and supplied company count. |
max_actions | integer | 0 | Automatic when 0; otherwise 1–10,000. Polling an existing child does not create another action. |
validation_integration_id | string or null | null | Your LLM integration, organization default, or explicit native-icp (DiscoLike VibeCheck). Left open, it runs on your default or only AI integration; with none, on DiscoLike VibeCheck; with several and none marked default, the plan leaves it for you to pick. |
contact_integration_id | string or null | null | Your LLM integration, or explicit native (DiscoLike Groove). Left open with a search provider available, research runs on DiscoLike Groove. |
search_provider_id | string or null | null | Your search integration, organization default, or only search provider (with several and no default, the plan leaves it for you to pick). "none", or having no search provider, finds indexed contacts only with no web research. |
segment | boolean | false | Segment qualified companies when at least ten are available. |
deliverable | string | "leads" | "leads" finds people to contact at the companies; "accounts" returns the checked companies only, with no contact work (see Companies-Only Runs). |
goal | string | "leads" | "leads" keeps adding rounds of companies until the contact target is met; "companies" takes target_companies matching companies and works only those, finishing with stop_reason: "companies_worked" even if some of them have no reachable people. |
checkpoints | string | "auto" | "ask" pauses the run in chat at each checkpoint (see Checkpoints); "auto" never pauses. Not part of the OpenAPI schema; send it explicitly in the request body. |
checkpoints is omitted from the schema on purpose, so set it explicitly rather than relying on a generated client to surface it. The REST default is "auto".
Omit quantity fields to infer them from the brief, with 1,000 companies and 1 contact per company as fallbacks. Explicit values, including explicit defaults, override the text. Employee counts and revenue remain targeting criteria. Ambiguous quantities or counts outside the limits produce a clarification question. Automatic candidate limits use the largest of target × 8, 50, and len(domains) + len(company_names). For example, a target of 25 with 1,000 supplied domains gets a candidate limit of 1,000. Automatic action limits use ceil(target/10) × 6 + 12 with a minimum of 24. An automatic candidate limit can grow once during the run: after enough companies have been researched, the run raises it from its own measured fit rate and contact yield, within a per-target ceiling and its action budget. A max_candidates you set is never raised. Explicit caps remain authoritative.
curl -X POST https://api.discolike.com/v1/prospecting/runs \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -H 'Idempotency-Key: logistics-september-01' \ -d '{"brief":"Find 100 US logistics companies and 3 operations directors at each"}'An active paid account with quota and tenant-owned integrations is required. Explicit native engines must be available on the deployment. Intent interpretation, wizards, segmentation, and prompt preparation use platform credentials. Agent coordination and independent contact qualification use your contact LLM integration; native contacts use your validation LLM integration or organization default. A run whose company checks use DiscoLike VibeCheck (native-icp) needs no generative AI integration when its contacts are researched with DiscoLike Groove (native), come only from the contact index because no search provider is connected, or are not requested (deliverable: accounts): it takes the next permitted stage in a fixed order, and keeps contacts by the title research or the contact index found without checking them against the persona (role_fit is accepted and role_reason says so). Existing processing and provider charges apply. Missing keys and provider failures do not fall back to platform credentials. Work limits (max_candidates, max_actions) are not provider-dollar caps; to cap what a run spends on your own AI and search provider keys, set max_provider_spend_usd when you change the plan.
Starting a run can return 403 when your organization can’t start another prospecting run right now; the message says when you can retry. Repeating a request that was already accepted, with the same Idempotency-Key, is not affected.
Review and Approve
Section titled “Review and Approve”POST /prospecting/runs/{run_id}/approve
Poll the run until it reaches proposed, then review its plan and the plan chat message. Send the exact version you reviewed:
{"plan_version": 1}plan_version is required and must be at least 1. checkpoints is optional ("ask" or "auto"); omit or send null to keep the value from the brief. Success returns 202 with the run queued. Repeating approval of the already approved version is safe. A stale version or a plan that is not ready returns 409; fetch and review the current plan before retrying. When your organization has several AI or search providers and none is set as the default, the plan leaves that engine’s selected as null with pick_required: true and approval returns 409 until you pick one with Change the Plan; with a default, or only one provider, the plan uses it. An Idempotency-Key header is accepted but not required for approval. One queued or running run per actor is allowed within an organization.
seed_segments is optional: the ids from the plan card’s seed_segments to expand. Omit it or send null to keep the run’s current selection (the plan card’s default, or whatever a chat edit set); an empty list or an id the plan does not list returns 422. segment is optional too: true or false overrides the brief’s segment (group the results into segments at the end); omit it to keep the brief’s value.
The plan message’s data also carries checkpoints (the mode this run will approve with), market_size (an upper bound on companies matching the planned queries, capped at 10,000; null when unknown or when the brief supplies its own domains/company_names), and all_matching_target (offered only when market_size exceeds target_companies, capped at the same maximum: the count you’d get by raising the target to match the whole market).
For a run started with customer_domains, the plan message’s data also carries seed_segments (each with id, name, customers, three examples and selected) and segment. market_size then sums only the selected segments’ planned filter queries, since lookalike searches have no free count.
Change a Plan
Section titled “Change a Plan”PATCH /prospecting/runs/{run_id}/plan
Change the engines, size, deliverable, or spending limits of a plan that is waiting for approval:
{"plan_version": 1, "search_provider_id": "none", "max_spend_usd": 25, "max_provider_spend_usd": 10}| Field | Type | Description |
|---|---|---|
plan_version | integer | Required, at least 1. The plan version you reviewed. |
contact_integration_id | string | Contact research engine. Use an option id from the plan message’s data.contact_engine.options. |
validation_integration_id | string | Company check engine. Use an option id from data.company_check_engine.options. |
search_provider_id | string | Search provider. Use an option id from data.search_provider.options; "none" skips web research and finds indexed contacts only. |
target_companies | integer | How many qualified companies the run aims for, 1–10,000. Limits you left automatic are re-derived around it, as a chat edit would. |
contacts_per_company | integer | Most contacts to find at each qualified company, 1–10. |
deliverable | string | "leads" (people to contact) or "accounts" (the checked companies only). Switching back to "leads" brings back a persona to search for. |
goal | string | "leads" (keep adding rounds until the contact target is met) or "companies" (take target_companies matching companies and work only those). |
max_spend_usd | number | Most the run may spend on DiscoLike credits: records and per-request fees, in USD at your plan’s rates, at least 0. 0 removes the limit. A plan with no per-record price returns 422. |
max_provider_spend_usd | number | Most the run may spend on your own AI and search provider keys, in USD, at least 0. Once reached, the run starts no new work; batches already running finish, so the total can end slightly above it. 0 removes the limit. |
Every field except plan_version is optional; an omitted field keeps its current value, and a choice made here is never replaced by a later default. Engines you have not picked are re-chosen around your picks (picking a search provider brings research back to DiscoLike Groove). Unknown fields return 422. The plan message’s data lists each engine’s selected id and its options (id and label), plus target_companies, contacts_per_company, deliverable, goal, max_spend_usd, max_provider_spend_usd, estimate.records (the records the run is expected to bill: companies checked, indexed contacts and found emails), estimate.billed_calls (billed company searches and name matches, company checks, contact searches, research jobs and segmentation), estimate.records_cost_usd (records and per-request fees at your plan’s rates), estimate.llm_cost_usd (estimated spend on your AI and search providers, DiscoLike Groove’s web searches included), estimate.ai_cost_usd and estimate.search_cost_usd (the AI and search providers’ parts of llm_cost_usd; null when the estimate cannot split it), estimate.search_billed_separately (true only on plans made before 2026-10-02, whose llm_cost_usd left out those searches), account_spend_left_usd (what the account can still spend this billing cycle) and, on a wave run, email_lookup_note (says approving also starts email lookups for contacts already in DiscoLike’s database; absent otherwise).
The plan is re-estimated at a new plan_version, and the latest plan message is rewritten in place with the same seq rather than posted again, so a messages_after cursor past it will not return it. Read the updated plan from this response’s messages (or poll with messages_after=0), then approve the new version. Success returns the run, as GET /prospecting/runs/{run_id} does. A value that is not one of the plan’s options returns 422. A run that is not waiting for approval, or a stale plan_version, returns 409. A missing run or another organization’s run returns 404. A request that changes nothing returns the run unchanged, at the same plan_version.
With no contact engine chosen, contact research runs on DiscoLike Groove when a search provider exists; Groove adds no AI provider cost; its web searches bill your own search provider, about two queries per researched company at that provider’s per-query price, and never DiscoLike credits. With no search provider, the plan says contacts come from DiscoLike’s contact index only.
Two spending limits
Section titled “Two spending limits”A run can carry two independent limits. max_spend_usd caps DiscoLike credits (records and per-request fees at your plan’s rates); reaching it stops the run with stop_reason: "credit_limit". max_provider_spend_usd caps what the run spends on your own AI and search provider keys; reaching it stops the run with stop_reason: "provider_limit". Results found before either stop are kept. The run response’s provider_cost_usd reports the provider spend so far, as the providers report it (a custom AI endpoint reports no price), and provider_ai_cost_usd and provider_search_cost_usd split it between your AI and search providers. The closing message names each part (“Your AI provider key was charged about $X and your search provider about $Y”, leaving out a part under a cent); runs started before the split was recorded report only the total.
Companies-Only Runs
Section titled “Companies-Only Runs”Start with deliverable: "accounts" (or set it on the plan) when you need the companies and not the people at them. A companies-only run searches for companies and checks each against your ICP, but does no contact search, research or email finding, so it bills no contact or email records and needs no persona. It completes once it has target_companies fit companies (stop_reason: "target_reached"), or earlier if the searches run dry or a limit is reached. Its results list holds one row per fit company and no contacts (see Saved Results).
A brief can ask for this in plain words (“just the companies”); when it is unclear whether you want people or companies, the intake card asks.
Phased Runs
Section titled “Phased Runs”New runs and every companies-only run work in phases: they search for and check every company first, then find people at the companies that fit. The run response’s pipeline_phase says where a phased run is: "companies" while it searches and checks companies, "people" while it finds people at them, and null for older runs that interleave both. A companies-only run stays in "companies".
When the people phase ends short of the contact target because too few of the companies had reachable people, a run with goal: "leads" proposes another round of companies sized from the yield so far. A round that fits within the run’s limits starts on its own, with a text message saying how many companies it adds and what it may cost. A round that needs more room than the spending limits allow pauses at the top_up checkpoint instead, even in auto mode; a round with no room at all stops the run at that limit. With goal: "companies", the run never adds rounds: it works the companies it took and finishes with stop_reason: "companies_worked".
Lead runs are moving to waves (being rolled out; runs planned before it reaches your account, and every companies-only run, keep the behavior above). A wave run sizes each wave from the target: enough companies to check that, at the expected fit rate, the share of fit companies with a contact matching your persona, and the email find rate, it should reach the target with a 25% margin. The rates come from history for the first wave and from the run’s own results after that. The run checks every company in the wave, with no email lookups while it does, then finds and qualifies contacts at all the fit ones.
While it researches contacts on the web, the run already looks up emails for persona-matched contacts that came from DiscoLike’s database, in ask and auto mode alike: approving the plan is your consent for those, and the plan message’s email_lookup_note says so. It sends one batch at a time, once 50 are waiting or the database search is done, within the lookups the emails the target still needs would take. Contacts found by web research wait for the review. If these early emails reach the target, the run completes without a review. A run whose plan message had no email_lookup_note (planned before early lookups reached your account) looks up no email before the review.
When the wave’s research settles, the run stops before looking up any other email, at the contacts_review checkpoint, in ask and auto mode alike. The question says how many companies were checked, how many fit, and how many contacts matched the persona at how many companies, what the early lookups found and how many are still running, plus what finding the remaining contacts’ emails would take: how many lookups (every matched contact not already being looked up, capped at the lookups the emails the target still needs would take at the find rate plus a 15% margin, less those still running, raised in a wave after the first to 50 lookups across the wave, early ones included, and capped at what the credit limit leaves), about how long (email lookups are shared and run at about 70 a minute), how many emails to expect at the find rate, and the DiscoLike credits reserved until each lookup settles (only found emails stay billed). If a limit cut the research short, it says which. Once the run reaches its target, lookups that have not started yet are cancelled and their credits released, so they are not billed.
- Find emails looks up emails for that wave only, up to the number of lookups the question showed. Because it is consent to spend, it counts only when sent with
question_seqset to the review question’sseq(see Send a Message); a “Find emails” without it, typed or from an older client, approves nothing, and the agent posts the review again for you to confirm. - Find more companies first checks another wave before looking up the remaining contacts’ emails and asks again after it, with the contacts found so far kept. It is left out when there are no more candidates to check or the contact rows are full. A message that changes the criteria at this point counts as this choice, never as consent to look up emails.
- Finish completes the run (
stop_reason: "user_finished") with its companies and contacts and no new lookups. The closing message counts the matched companies and contacts. The saved list keeps every contact found, with or without an email; a CRM push sends only contacts with a found email.
If the emails still fall short of the target, the run proposes the next wave through the usual round of companies, sized from its own measured rates, and that wave is reviewed again before its emails. Every wave after the first gets at least 50 email lookups, its early lookups and the review’s together, as long as that many matched contacts are waiting, and is sized at the run’s find rate to find them: a wave costs a full research round however few emails it needs, and lookups not yet started when the target is reached are cancelled, so the extra is not billed. Runs nobody can answer (MCP runs, or a chat closed for misuse) answer Find emails themselves and post the review’s numbers as a text message.
Customer Segments
Section titled “Customer Segments”A run started with customer_domains first groups your customers into up to eight segments (stage seed_segment, platform-funded; five or more customers are needed, otherwise they are searched as one group). Customers the index does not know are left out and listed in the plan’s issues. If grouping fails twice, all customers are searched as one group and the plan says so. Each segment then gets its own searches: a lookalike of its most representative customers plus the discover wizard’s queries for companies like them. The target is split across the selected segments by their share of your customers (each gets at least 10% of it); a segment that runs out of searches hands its remaining share to the others. Every company row carries seed_segment (id and name); the saved companies list has a Customer segment column and each company in a contact list carries customer_segment. The closing message counts matching companies per segment. At the pilot check of a large run, a segment whose first companies fit poorly stops being searched while the others continue (checkpoints: "ask" offers “Skip the weak segments” instead).
In chat, paste your customers’ domains and say they are customers or examples to find more like (“companies like these”, “our customers are”); domains described as the companies to prospect (“contacts at these”) become domains instead. On a proposed plan, “only the dental ones” or “skip Other” changes the segment selection and updates the plan.
Results segments. segment: true (or the plan card’s “Group results into segments”) groups the accepted companies into segments once the run finishes; a run needs at least ten. On a finished run, saying “segment these” in chat groups its accepted companies without reopening the run: the segments are written to the company rows (segment_id, segment_description), the saved lists are rewritten, and a message lists each segment with its size and three example companies. Customer segments (seed_segment) and results segments (segment_*) are separate fields; neither overwrites the other.
Send a Message
Section titled “Send a Message”POST /prospecting/runs/{run_id}/messages
Send a JSON body with text (1–4,000 characters), or intake to answer an intake card, and a required Idempotency-Key header (1–128 characters). When text is a suggested reply to a question, also send question_seq, the seq of that question message (optional, at least 1): the reply then answers that question only. If a newer question has been posted since, the reply answers nothing and the agent says the question changed, so a stale screen can never answer a question it did not show. Without question_seq, a reply answers the checkpoint that was open when you sent it, except “Find emails” at a contacts_review, which needs question_seq. Reuse the key when retrying that message; a duplicate returns the stored message. The 202 response is the user message, with seq, created_at, role, kind, content, and optional data. The agent’s reply arrives asynchronously through the messages cursor.
{"text": "Make it 250 companies and exclude staffing firms"}Answering a suggested reply, here “Find emails” at a contacts_review question with seq 42:
{"text": "Find emails", "question_seq": 42}Messages can change targeting or size, answer a pending question, approve or stop a run, or ask about progress. Running batches finish under the plan version that started them. Messages are accepted in every status; a finished run retains its results and cannot be restarted by an edit.
A chat has a per-conversation message limit; past it, a message gets a fixed reply instead of a model call. A chat repeatedly used for something other than prospecting is closed after a few off-topic or rejected messages in a row: later messages get the same fixed reply, and the run response’s chat_closed field turns true. A run with no approved plan, or an approved run paused waiting on a question, is cancelled with stop_reason: "misuse" when its chat closes, and its saved results (if any) are kept; an approved run that is queued or running keeps working and its saved lists keep filling regardless of chat_closed. Start a new chat to keep prospecting after a chat closes.
Checkpoints
Section titled “Checkpoints”A run can pause at a decision point mid-run and ask in chat (checkpoints: "ask"), or decide on its own without pausing (checkpoints: "auto", the REST default); the top_up and contacts_review rows below say when auto asks too. Each checkpoint fires at most once per condition.
When a run pauses, it moves to needs_input with stop_reason set to the checkpoint’s name and posts a question chat message. The message’s data carries reason (the checkpoint name), suggested_replies (exact strings to send back), and sample (checked companies with their fit verdict, for the pilot checkpoint only). Answer by sending one of the suggested replies as text to POST /prospecting/runs/{run_id}/messages; free-text steering also works, but the exact suggested reply is the reliable path.
stop_reason | When it fires | Suggested replies |
|---|---|---|
pilot | The run validated its first batch of candidates (targets of 500 or more companies pilot a first 100). | Good fit: “Run the full list” or “Stop here”. Below 80% fit: “Tell me sharper criteria” (keeps the question open and asks you to type the criteria), “Try suggested language” (only when a rewrite is available), “Run as is”, or “Stop here”. When a re-pilot on sharpened criteria finds no new companies: “Run the full list anyway” or “Stop here”. |
tail_quality | A validation batch of at least 20 companies from one search settled under 50% fit. | “Skip that search and continue” or “Stop here”. |
short | Planned searches ran out short of the target, with room left in the action budget; offered once per run. | “Widen the search” or “Finish with {found}” (the companies found so far). |
target_reached | The target companies and target emails (target_companies × contacts_per_company) are both met. | “That’s enough” or “Find {count} more” (up to the target, capped at 10,000). |
contacts_review | A wave run finished checking and researching a wave and has not looked up its researched contacts’ emails (contacts from DiscoLike’s database may already be looked up). Asked in auto mode too. The question carries the wave’s counts and the email estimate. | “Find emails” (send it with question_seq), “Find more companies first” (only when another wave has room), or “Finish”. |
top_up | A phased run needs another round of companies to reach its contact target, and that round needs more room than its spending limits leave. Asked in auto mode too. The question says how many companies had reachable people, how many more the round needs, what it may cost, and which limit it crosses. | “Raise the limit and run it” (raises the crossed limit by what the round needs), “Run a smaller round ({companies} companies)” (only when the limits leave room for part of it), or “Finish with {found}”. When no limit is crossed (in ask mode), “Run another round” replaces the raise reply. The reply’s choice names (continue, raise, shrink, finish) are accepted too. |
In auto mode, the run never pauses: a poor pilot (under 80% fit) gets its criteria automatically sharpened once, then continues on the sharpened criteria with a text notice rather than a question. The run only stops with stop_reason: "pilot_failed" and the checked sample in the question message’s data.sample when the re-piloted fit, after that one sharpening, comes in under 20% (the searches are effectively broken); a fit at or above 20% keeps going. If the sharpening itself fails or finds nothing new, the run continues on the original criteria instead of stopping. A drifting search (the tail_quality condition) is dropped automatically and the run continues. A run whose search runs dry finishes as candidates_exhausted. Reaching the target keeps going, since target_reached only ever asks. A top_up round that fits the limits runs with a text notice; one that crosses a limit still asks. A contacts_review always asks, because it is your consent to spend on the remaining email lookups.
Finishing at a checkpoint by choice stops the run with stop_reason: "user_finished"; its saved results are kept.
The mode can be changed mid-run with PATCH /prospecting/runs/{run_id}; switching to "auto" answers an open checkpoint with its continue choice, except contacts_review, which stays open until you answer it.
MCP-started runs are always auto and have no way to answer a checkpoint question; a top_up round on an MCP run is cut to what the limits allow instead of asking, and a contacts_review is answered with Find emails. An MCP client that sees stop_reason: "pilot_failed" should show the user the sample and start a new run with a sharper brief.
Intake
Section titled “Intake”When a brief leaves out details the run needs, planning pauses on one structured question instead of a chain of single questions. The run moves to needs_input with stop_reason: "question" and posts a question message with data.reason set to "intake". Its content is a one-line headline followed by the field labels (for example A few details so I target the right list: What do these companies do?, Who should I contact?, How many companies) and suggested_replies is ["Use your best guess"], so a client that does not know about intake still shows what is asked. The card itself is in data.fields, a list of at most five fields, required ones first.
| Field | Type | Description |
|---|---|---|
key | string | One of deliverable, company_activity, industry, geography, company_size, persona_roles, list_size. |
label | string | The question text. |
multi | boolean | true when several options can be picked. |
options | object[] | 2–9 options, each with a stable value and a label of up to 60 characters. |
allow_other | boolean | true when free text can be sent as other. |
default | string[] | Option values preselected for optional fields. Empty for required fields. |
required | boolean | deliverable (when asked), company_activity and persona_roles are required; the rest are optional. |
deliverable is asked only when the brief does not say whether you want people or companies; it offers leads (“People to contact”) and accounts (“Just the companies”), and answering accounts makes the run companies-only and drops persona_roles from what must be answered. company_size and list_size always use fixed options. company_size offers emp_1_10, emp_11_50, emp_51_200, emp_201_1000, emp_1000_plus, rev_under_1m, rev_1m_10m, rev_10m_plus, and any (default any). list_size offers 100, 500, 1000 (default), and all; all means the maximum of 10,000 companies. The options of the other fields are drafted from your brief.
Explicit settings win. When the request sets target_companies, the card never asks list_size. When it sets max_candidates, list_size offers only sizes within it (the field is left out if fewer than two fit), and a chosen size is capped at max_candidates. An unclear or out-of-range count in the brief (“between 300 and 800 companies”) is asked as list_size on the card instead of a separate question.
Answer with intake on POST /prospecting/runs/{run_id}/messages, keyed by field key. Each answer has values (option values) and an optional other (up to 200 characters). text is not needed; the stored user message carries a readable summary of your choices in content and the answers in data.intake. Fields you leave out keep no answer, so send the defaults you want to apply.
Option values other than the fixed company_size and list_size ones come from the card you received; the ones below are illustrative.
{ "intake": { "company_activity": {"values": ["sell_services"]}, "persona_roles": {"values": ["founder"], "other": "Head of operations"}, "company_size": {"values": ["rev_1m_10m"]}, "list_size": {"values": ["500"]} }}Your choices are added to the brief as labelled clarification lines without another interpretation pass, list_size sets target_companies, and the run plans again once. An answer is rejected with 422 when it names a question that is not on the card, picks a value that is not an option, sends other for a field without allow_other, picks several values for a single-choice field, or leaves a required field empty; the message says which field. It returns 409 (“This question was already answered”) when the card is closed: the run resumed, a newer question was posted, or an intake answer was already stored. A chat message that leaves the run waiting, such as a question about the card, keeps it open. Sending both text and intake stores the summary of the choices, not your text.
The card is asked at most once per run. If the brief still has a gap after you answer, the run asks one plain question for it, and that question never repeats a detail you already answered. After that the run plans from its best reading of the brief and your answers instead of asking again (senior decision makers when no people are named); you can still change the plan before approving it. A free-text reply to the card works too: it is handled as any other answer to a question, and the card is not asked again. If the card cannot be drafted, the run falls back to a single question for the first missing required detail; that question counts as the intake round. MCP-started runs never ask intake because they approve automatically; a brief too vague to plan gets the single question instead.
List Recent Runs
Section titled “List Recent Runs”GET /prospecting/runs
limit defaults to 20 and accepts 1–50. before (a run ID) keys pagination: pass the last run ID from a page to fetch the next one, ordered by created_at then run_id descending. An unknown or other-organization run ID returns an empty page rather than an error. Returns an array of organization runs, newest first. Each summary includes run_id, status, title, stop_reason, stage, a shortened brief, target_companies, contacts_per_company, qualified_companies, accepted_contacts, created_at, and updated_at. The response has no total count, so a full page (limit items) means more may exist; a short page means you’ve reached the end.
Progress and Results
Section titled “Progress and Results”GET /prospecting/runs/{run_id}
Poll approximately every five seconds. Company and contact arrays use the same page offset independently; each has its own total count.
| Parameter | Default | Description |
|---|---|---|
offset | 0 | Nonnegative result offset. |
limit | 100 | Result page size, 1–500. |
events_after | 0 | Return events after this sequence number. |
messages_after | 0 | Return messages after this sequence number. |
Use next_event_seq and next_message_seq on subsequent polls. reply_pending is true while the agent still owes a reply to a user message. After a “segment these” request on a finished run it also stays true, past the acknowledgement, until the message with the segments or the failure is posted. That can take more than an hour; if grouping stops without posting an outcome, the flag clears on its own after about 90 minutes. Message kinds are text, plan, milestone, question, ack, and error; data carries kind-specific details such as plan estimates, suggested replies, or milestone previews.
The response includes:
- Identity and lifecycle:
run_id,status,stop_reason,stage,phase,pipeline_phase("companies","people", ornull; see Phased Runs),created_at,updated_at,error, andwaiting_for_worker. - Planning: current
brief,title,target_companies,contacts_per_company,plan,plan_version,approved_plan_version, andlast_decision. - Progress:
events,next_event_seq,messages,next_message_seq,reply_pending, andin_flight(stage, item count, plan version, state, and start time for active batches). - Results:
companies,contacts,total_companies,total_contacts,qualified_companies,accepted_contacts,fit_companies,emails_found,offset,limit,saved_query_id,saved_query_ids, andcompanies_saved_query_id. - Work allowance and spend:
actions_used,max_actions, andprovider_cost_usd(what the run has spent so far on your own AI and search provider keys, in USD, as the providers report it;nullfor runs that recorded no provider cost), withprovider_ai_cost_usdandprovider_search_cost_usd(its AI and search providers’ parts;nullwhenprovider_cost_usdis, and for runs started before the split was recorded). - Chat:
chat_closedistrueonce the chat has been closed for off-topic use. An approved run keeps working and its saved lists keep filling even whenchat_closedistrue.
Statuses are drafting, proposed, queued, running, needs_input, completed, failed, and cancelled. proposed needs approval. needs_input can carry a question or a provider problem; inspect error and the agent message. Scope or targeting questions use stop_reason: "question"; their message data.reason can be out_of_scope, company_target, persona_target, ambiguous_target, result_quantity, or intake (see Intake). Answer through the message endpoint. Interpretation attempts consume an action even when they need clarification.
An error message’s data.reason can also be message_limit (the conversation’s per-chat message limit was reached; a fixed reply is returned instead of a model call) or chat_closed (the chat was closed for off-topic use). Neither reason implies the run itself failed.
Inspect stop_reason: completion can mean the target, candidate supply, or an operational limit ended the run. candidates_exhausted means only that the search ran dry. candidate_limit means the run reached its candidate cap; raise max_candidates or ask for fewer companies in a new run. credit_limit means the run stopped at its DiscoLike credit limit (max_spend_usd), and provider_limit at its AI and search provider limit (max_provider_spend_usd), with its results kept; start a new run with a higher limit to continue. companies_worked means a goal: "companies" run worked through the companies it took. misuse means the chat was closed for off-topic use; pilot_failed, user_finished, top_up and contacts_review come from a checkpoint. Potentially billed work interrupted before acknowledgement is marked uncertain instead of blindly repeated, except an interrupted indexed contact search, which is retried because it bills only contacts new to your account. Runs have a 72-hour lifetime, including time waiting for input. Partial results remain available after stopping.
A company needs accepted ICP fit. A counted contact needs accepted persona fit and a nonempty email with finder status found, catch_all, or catch_all_pattern. These email statuses remain distinct; they are not delivery guarantees. Indexed and generated contacts undergo independent assessment against profile evidence. Similarity scores and upstream title labels alone do not establish acceptance. Stored contacts are bounded by target × contacts_per_company × 5, with a minimum of 500 and maximum of 50,000. Segmentation adds cluster fields to companies; cluster probability is not ICP fit confidence.
Email processing finds addresses from a person’s name and company domain. It does not expose the app-only raw-address verification API.
Saved Results
Section titled “Saved Results”Approved runs expose a stable saved_query_id for their saved results. The list is grouped by company: every fit company is a parent row with its domain and fit columns (ICP Fit, Confidence, Reasoning, and, when present, Segment and Customer segment), and the contacts found for it are nested in its contacts. Companies with a found email come first, then catch-alls, then the rest, and fit companies with no contacts last; within a company, accepted contacts lead. Opted-out contacts are left out and duplicate emails removed. A companies-only run has parent rows only, with no contacts. Results refresh at workflow checkpoints and stops; the saved snapshot can lag live progress.
A large result is split across several saved queries (“parts”) instead of being truncated: each part holds up to 50 MiB, and a company’s contacts are never split across two parts. saved_query_ids lists every part in order; the first entry always equals saved_query_id. Both are empty until the run has saved anything. All parts use action thin_prospecting and are tagged prospecting, the run’s status tag, and (only when the result is split) part. When there is more than one part, every part (including part 1) is named “<title> (part k of N)”; a single, unsplit result keeps the plain run title. Parts 2..N also carry query_params.saved_query_part: k; part 1’s params are unchanged. While the run is still going, contacts can move between parts as later checkpoints reproject the result, so treat parts as provisional until the run reaches a terminal status. Find them in the saved query listing. Read current company and contact rows through the run endpoint.
A companies list is still saved alongside it, for every run including companies-only ones: one row per fit company with the same fit columns, at companies_saved_query_id. It stays a single saved query; if it hits its own row cap, it is tagged partial instead of silently dropping rows.
Rename a Run or Switch Its Mode
Section titled “Rename a Run or Switch Its Mode”PATCH /prospecting/runs/{run_id}
{ "title": "Logistics operations leaders" }{ "checkpoints": "auto" }| Field | Type | Description |
|---|---|---|
title | string | New chat title. Surrounding whitespace is trimmed, and the trimmed title must be 1 to 80 characters. |
checkpoints | string | "ask" (Manual) pauses the run at later checkpoints; "auto" (Auto) never pauses. |
Send title, checkpoints, or both; a body with neither, a blank or too-long title, an unknown mode, or extra fields returns 422. Returns the run summary, the same shape as each row of List Recent Runs. A missing, deleted, or other-organization run returns 404.
A title alone works in any status. A title set before the agent plans is kept; the agent only names a chat that has no title yet. Renaming does not rename result lists the run already saved.
checkpoints switches a run between Manual and Auto while it is still going (drafting, proposed, queued, running, or waiting for input). Switching to "auto" while the run is paused at a checkpoint answers that checkpoint with its continue choice and resumes the run, as if you had sent the continue reply; a run paused on any other question keeps waiting. A run that is about to pause at a checkpoint when you switch to "auto" keeps going instead. A contacts_review is the exception to both: it stays open in auto until you answer it. A mode switch is gated like starting a run: it needs a plan with prospecting, an active subscription and a payment method (403 otherwise) and remaining quota (402 otherwise). A finished run returns 409, and so does resuming a paused run while another of your runs is queued or running. When both fields are sent, the mode switch is applied first, and a failed switch leaves the title unchanged.
Cancel a Run
Section titled “Cancel a Run”POST /prospecting/runs/{run_id}/cancel
Stop scheduling new work. Supported children receive cancellation; already submitted work can still finish and incur its normal charges. Cancellation cleanup is bounded. Completed results are retained; active children remain visible in in_flight while cleanup continues. Returns the run.
Delete a Run
Section titled “Delete a Run”DELETE /prospecting/runs/{run_id}
Removes the chat from the organization’s run list and hides the run from get and list. An active run is cancelled first. Returns 204 No Content.
Charges already incurred are not refunded. Saved result lists (the queries in saved_query_ids) remain available in the saved query listing. Later calls for the deleted run return 404. Starting a new run with the deleted run’s Idempotency-Key returns 409; use a new key.
MCP, SDK, and CLI Access
Section titled “MCP, SDK, and CLI Access”MCP exposes start-prospecting (brief, idempotency_key; the brief takes customer_domains for a seeded run), get-prospecting-run (run_id, offset=0, limit=100, events_after=0), and cancel-prospecting (run_id). MCP start automatically approves the run, so when the brief names no company count, start-prospecting defaults target_companies to 100 rather than the REST/SDK default of 1000, since approval (and the spend it triggers) happens immediately with no review step. Its page limit remains 100, and its response omits REST chat fields; it does carry saved_query_ids (every saved-list part, in order) and pilot_sample (the checked companies, populated once stop_reason is pilot_failed). With several AI or search providers and no default, set validation_integration_id, contact_integration_id or search_provider_id in the brief; otherwise the run stops waiting for input and its error lists the provider ids to choose from. There is no MCP tool to answer a checkpoint; MCP runs always use checkpoints: "auto" (see Checkpoints). The approval, message, list, rename, and delete operations described above are REST capabilities.
The Python SDK exposes sync/async start, get, list, approve, message, rename, cancel, delete, and wait. The CLI exposes corresponding commands (status for get) except rename and delete. Both stop waiting at proposed so you can review and approve the plan.