---
title: "Start a discovery job."
method: POST
path: "/mcp/tools/execute"
tags: ["discovery"]
---

# Start a discovery job.

`POST /mcp/tools/execute`

## Request

- Base URL: `https://api.prosp.cc`
- URL: `https://api.prosp.cc/mcp/tools/execute`
- Auth: OAuth 2 (client credentials, token URL `https://prosp-mcp-641053169627.auth.eu-central-1.amazoncognito.com/oauth2/token`, scopes: `mcp/execute`)

Debits 100 credits (the per-discovery cost) atomically and queues a discovery. The
`X-Idempotency-Key` header is **required**; replaying the same key returns the
original job with `deduplicated: true` and does not double-charge.

**Concurrency limit.** A tenant may run only a bounded number of discovery jobs at once.
While you are at that limit a new `execute` is rejected with **429** and
`error: concurrent_limit` — cancel or wait for a running job to finish, then retry. The
credits for a rejected request are **refunded**, so a 429 never costs you credits (your
balance nets to zero and no ledger entry is written for the rejected attempt).

**Saturation pre-check (net-new estimate).** Before charging, the service estimates how
many *new* companies this exact ICP would yield for you (companies you do not already own
from prior runs). When that estimate is ~0 and reliable — you have already discovered the
addressable set for these criteria — the request is held with **409** and a
`status: saturation_warning` body (see `SaturationWarning`) instead of running. **No credits
are charged and no job is started.** To run it anyway, re-submit with
`parameters.acknowledge_low_yield: true`; the acknowledged run charges normally and starts.

## Headers

- `X-Idempotency-Key` string, required

## Request body

- DiscoveryRequest: Request body for `POST /mcp/tools/execute`.
  - `toolName` 'discovery', required: The tool to run. Use `discovery` to start a prospect discovery.
  - `parameters` DiscoveryCriteria, required: The criteria that describe the companies you want to find. Only `query` is required; the other fields narrow and steer the search. `maxResults` must be 1–200 (a value outside the range is rejected with a 400), and invalid `company_sizes` values are likewise rejected with a 400 rather than silently ignored. Unknown fields are rejected with a 400 that names the offending property.
    - `query` string, required: Free-text description of the companies you want to discover. The primary search signal.
    - `industries` string[]: Target industries. Prospects are scored for fit against these; companies in unrelated industries are filtered out.
    - `locations` string[]: Target countries or regions to search within (e.g. country names). Restricts results to companies located there.
    - `company_sizes` string[]: Company-size buckets to include, by employee headcount. Any combination of: `micro` (≤10), `small` (up to ~50), `medium` (up to ~200), `large` (up to ~500), `enterprise` (500+). Omit to allow any size. Each bucket maps to a `company_size` value; request `micro` to prioritize the ≤10-employee shops, which are returned with `company_size: micro` (the output `company_size` uses the SAME lowercase tokens as this input filter, so a value read off a result can be re-submitted verbatim). NOTE: this filter is ADVISORY, not strict — in thin markets the engine may relax it to adjacent buckets and still deliver e.g. `micro` under a `small` request (this is disclosed at delivery via a `FILTER_RELAXED` suggestion). So omitting a size does not guarantee its exclusion; it expresses preference, not a hard exclusion.
    - `keywords` string[]: Additional keywords or phrases to bias the search toward (e.g. products, technologies, or capabilities).
    - `exclusions` string[]: Domains or terms to exclude from results — e.g. competitors, existing customers, or your own brand. Matching companies are dropped from discovery.
    - `sub_country_regions` string[]: ISO-3166-2 sub-country regions (e.g. states or provinces) to focus the search within a country. Validated against the supported region set. This is a REGIONAL PREFERENCE, not a hard filter: companies located in the requested region(s) are prioritized (ranked higher for relevance), which can substantially raise the in-region share of your results, while companies OUTSIDE those regions are still returned, ranked lower. It is a precision dial — expect fewer but more-targeted results, trading total volume for regional focus. Omit it (or clear it) to broaden coverage across the whole country. When this field is set, the discovery result's `suggestions` array discloses the relaxation: a `FILTER_RELAXED` region suggestion (naming the filter, with a `{sub_country_regions: []}` correction) when returned companies with a known region fall outside the request, otherwise an informational `REGION_PREFERENCE_APPLIED` note — both carrying the same one-tap correction.
    - `target_business_models` string[]: Optional value-chain-role filter. When set, only companies whose classified business model is one of these roles are kept; a prospect whose role is a confirmed member of none is dropped. Use e.g. `["manufacturer"]` for manufacturers only, or `["distributor"]` for wholesale distributors only. Omit to accept any role (default — no role filtering). The role describes what the TARGET company itself IS, taken from the noun the customer used for the targets in their own language (roasters, farms, makers, producers, tostadores, Kaffeeroester, vyrobce -> `manufacturer`; shops, stores -> `retailer`; wholesalers, importers -> `distributor`). It is NEVER inferred from the customer's own business or supply chain: a coffee shop asking for roasters wants `manufacturer`, not `distributor`, even though a coffee shop buys from distributors. When the request names no target role, or you are unsure, OMIT the field — a wrong role silently drops every genuine match, an omitted one drops nothing.
    - `website` string: Your own website. Automatically excluded from results so you never discover yourself.
    - `maxResults` integer: Maximum number of prospects to return for this discovery (1–200; a value outside this range is rejected with 400, not clamped; defaults to 50).
    - `acknowledge_low_yield` boolean: Set to `true` to run this discovery even when the saturation pre-check estimates ~0 net-new companies (see the `409 saturation_warning` response on `POST /mcp/tools/execute`). The acknowledged run charges normally and starts. Omit (or `false`) for the default behaviour, where a saturated ICP is held with a `409` and NOT charged. Not a search criterion — it steers the warn-and-confirm gate only, which is why it is the ONE field that may accompany `icpId` in a `DiscoveryTrigger` body.
    - `language` string: Optional market-language hint as an ISO-639-1 code (e.g. `de`, `cs`, `pl`). Steers the query→ICP expansion so keyword/industry expansion is generated in the market's language rather than inferred solely from `query`. Advisory, not a hard filter — company and person names are never translated, and results are not restricted to a single content language. Omit to let the engine infer the language from `query` itself.

## Response `200`

Job queued, OR the existing job when the request was de-duplicated — by a replayed idempotency key, or by a query-fingerprint match against a discovery you already started. A de-duplicated response carries `deduplicated: true` + `reason` and is NOT charged (the credit debit is refunded). Distinguish the two shapes via `deduplicated`.

- union
  - JobAccepted: Returned when a discovery has been accepted and queued for processing.
    - `jobId` string, required: Id of the newly queued discovery. Use it to poll status and read results.
    - `status` 'queued', required: Always `queued` — the discovery has been accepted and will start shortly.
    - `createdAt` string, date-time: When the discovery was queued (ISO-8601).
    - `poll_after_seconds` integer: Recommended number of seconds to wait before polling this discovery's status again. A cadence hint for polite back-off — poll no more often than this rather than hammering the API.
    - `estimated_duration_seconds` integer: Expected wall-clock duration for this discovery, in seconds — the single planning-safe number, always present. Equals the p95 of recent OBSERVED job runtimes (the industry-standard user-facing UPPER BOUND so a slow-but-healthy run is not mistaken for a stuck one, and a client planning timeouts is not misled low): it is identical to `estimated_duration_p95_seconds` once history exists, and falls back to a static coarse default until enough jobs have finished. For the full percentile trio read `estimated_duration_p50_seconds` / `estimated_duration_p90_seconds` / `estimated_duration_p95_seconds`. An estimate, not a guarantee.
    - `estimated_duration_p50_seconds` integer: Median (p50) of recent OBSERVED job runtimes, in seconds — the TYPICAL duration. The lowest member of the explicit p50/p90/p95 trio; `estimated_duration_p95_seconds` is the planning-safe upper bound. Omitted (together with the p90 and p95 fields) until enough jobs have finished to derive it.
    - `estimated_duration_p90_seconds` integer: 90th-percentile (p90) of recent OBSERVED job runtimes, in seconds — a monitoring-baseline percentile and the middle member of the explicit p50/p90/p95 trio. Omitted (together with the p50 and p95 fields) until enough jobs have finished to derive it.
    - `estimated_duration_p95_seconds` integer: 95th-percentile (p95) of recent OBSERVED job runtimes, in seconds — the planning-safe user-facing UPPER BOUND, and the top member of the explicit p50/p90/p95 trio. This is the same value carried by `estimated_duration_seconds` whenever history exists (that field is the always-present single number; this one is the labelled member of the trio). Omitted (together with the p50 and p90 fields) until enough jobs have finished to derive it.
    - `estimated_yield` integer: Expectation-setting hint: the TOTAL number of prospects this discovery is likely to DELIVER — i.e. total deliverable, NOT net-new. It is projected from YOUR own historical delivery ratio (prospects delivered ÷ target requested, accumulated across your completed discoveries) applied to this run's requested target, and it is never projected above the request. "Total deliverable" means the count is GROSS: it includes domains you may already own from a prior discovery (see `already_owned_count` on the status/result for how many of the delivered set were already yours); it is not a promise of that many BRAND-NEW companies. The projection is deliberately CONSERVATIVE (it tracks your historical delivery ratio and empirically under-predicts a fast-converging run), so on the status poll it is floored at the live `prospectsFound` — the value shown is `max(frozen_estimate, prospectsFound)` and therefore never sits BELOW the count already delivered in the same response. Omitted entirely (never null) until you have enough completed-discovery history to project honestly. An estimate, not a guarantee.
  - DuplicateResponse: Returned instead of a new job when the request matched a discovery you already started.
    - `jobId` string, required: Id of the existing discovery the request was matched to.
    - `status` 'queued', required: Status of the matched discovery.
    - `deduplicated` true, required: Always `true` — this response indicates the request was de-duplicated onto an existing job.
    - `reason` 'idempotency_key' | 'duplicate_query', required: Why the request was de-duplicated: `idempotency_key` = you replayed the same idempotency key; `duplicate_query` = an identical discovery is already running.
    - `message` string: Human-readable explanation of the de-duplication.

## Other responses

- `400`: Malformed request (missing/invalid field or header).
- `401`: Missing/invalid/expired Bearer token.
- `402`: Insufficient credits (balance below the per-discovery cost).
- `405`: HTTP method not allowed on this endpoint (rejected before any credit debit).
- `409`: Pre-flight warning, run held — a **saturation warning** (this ICP would yield ~0 net-new companies for you), an **ICP-mismatch warning** (the targeting is a material mismatch for your product/offering; a corrected ICP is offered under `suggestions`), or a **low-yield prediction warning** (ICPs like yours historically under-delivered), or a **duplicate running job** (`status: duplicate_running_job`, an identical search is still running — not overridable; see `running_job_id`). **No credits were charged and no job was started.** For the warnings, re-submit with `parameters.acknowledge_low_yield: true` to run and be charged anyway.
- `429`: Request throttled. `error` is `rate_limit_exceeded` when the 100-requests/60s per-customer window is exceeded, or `concurrent_limit` when the tenant already has the maximum number of discovery jobs running. On `concurrent_limit` the request's credits are refunded (no charge).
- `503`: Credit service unavailable.

---

[API](https://skmtc.dev/prosp/apis/prosp-discovery-api.md) · [All operations](https://skmtc.dev/prosp/apis/prosp-discovery-api/llms.txt) · [OpenAPI document](https://skmtc.dev/prosp/apis/prosp-discovery-api/revisions/1f2e0c55da22?raw)
