---
title: "Trigger a discovery (by ICP id or inline criteria)."
method: POST
path: "/api/v1/discoveries"
tags: ["discoveries"]
---

# Trigger a discovery (by ICP id or inline criteria).

`POST /api/v1/discoveries`

## Request

- Base URL: `https://api.prosp.cc`
- URL: `https://api.prosp.cc/api/v1/discoveries`
- 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) and queues a discovery. The body is EITHER
`{icpId}` (runs a saved ICP's criteria) OR inline `DiscoveryCriteria` — mutually exclusive,
and ENFORCED: mixing them is a `400 schema_validation_failed` rejected before any debit,
never a silent "the saved ICP won and your inline fields were discarded". The lone
exception is the warn-and-confirm control `acknowledge_low_yield`, which may accompany
`icpId`. `X-Idempotency-Key` is required. An unknown `icpId` refunds the debit and
returns 404.

## Headers

- `X-Idempotency-Key` string, required

## Request body

- union: The body for starting a discovery. Provide EITHER a saved ICP id OR inline criteria — mutually exclusive, and enforced: a body carrying `icpId` alongside any search criterion (`query`, `industries`, `locations`, `keywords`, `company_sizes`, `exclusions`, `maxResults`, `language`, `website`) is rejected with `400 schema_validation_failed` BEFORE any credit is debited, rather than silently running the saved ICP's criteria and discarding the inline ones. The single exception is `acknowledge_low_yield`, which is a warn-and-confirm control rather than a search criterion: `{icpId, acknowledge_low_yield}` is how a saved ICP held by a `409` is re-submitted to run anyway.
  - object
    - `icpId` string, required: Id of a saved ICP to run. Its stored criteria are used for this discovery.
    - `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.
  - object
    - `opportunityHandle` string, required: The `handle` of one entry from `GET /api/v1/opportunities`. Opaque, and resolved against YOUR OWN portfolio - it names a graded opportunity, never a search. An unknown handle refunds the debit and returns 404.
    - `confirmedResource` string: Your own description of the resource, REQUIRED when the entry's `requires_resource_confirmation` is true (its resource is an unconfirmed hypothesis). Without it the run is refused with 404 `resource_confirmation_required` and the debit is refunded - an inferred hypothesis may not be used to target a search until you have confirmed or corrected it. What you send is RETAINED as a stated fact about your business and informs later inference, so correct it rather than echoing ours back.
    - `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.
  - object: 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`

Queued, or the existing job for a duplicate (idempotency key or query).

- 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`: Criteria validation failed (field-level violations).
- `401`: Missing/invalid/expired Bearer token.
- `402`: Insufficient credits.
- `404`: Unknown icpId (debit refunded).
- `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 `acknowledge_low_yield: true` to run and be charged anyway.
- `429`: Rate limit exceeded (100 requests / 60s per customer).

---

[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)
