discovery

Fetch a completed job's result summary.

Returns the result once the job reaches any terminal state: completed, failed, exhausted, or cancelled. A cancelled job returns the prospects it had already delivered before cancellation (it is terminal-with-data, not a perpetual 202). Non-terminal states return the current status with a "not yet complete" message.

get/mcp/jobs/{jobId}/result

Request

  • Base URL: https://api.prosp.cc
  • URL: https://api.prosp.cc/mcp/jobs/{jobId}/result
  • Auth: OAuth 2 (client credentials, token URL https://prosp-mcp-641053169627.auth.eu-central-1.amazoncognito.com/oauth2/token, scopes: mcp/execute)

Response

Job result — a terminal state (incl. cancelled).

jobIdstring required

Id of this discovery.

icp_idstring

The saved ICP this discovery was started from (POST /api/v1/discoveries {"icpId": ...}), for billing attribution — the same value the matching credit-ledger rows carry, so a charge reconciles back to the ICP that caused it. Served on EVERY job surface (the progressive status, the merged discovery view, the finished result, and each entry of the discoveries list) from one reader, so a customer can attribute a run to its ICP without waiting for the run to finish or cross-referencing the ledger. ABSENT (field omitted, never an empty string) when the run was ad-hoc from inline criteria. Ids are scoped to the calling customer — the same namespace GET /api/v1/icps returns.

status'queued' | 'running' | 'processing' | 'completed' | 'failed' | 'exhausted' | 'cancelled' required

Discovery lifecycle state. queued = accepted, not yet started; running and processing = discovery in progress; completed = finished and reached its target; failed = ended with an error; exhausted = finished but ran out of candidates before hitting the target; cancelled = stopped by the caller via POST /mcp/jobs/{jobId}/cancel. The last four are terminal.

total_prospectsinteger

Number of prospects this discovery produced — the canonical per-discovery delivery count, the SAME field name and metric the live JobStatus.total_prospects reports (this is its terminal, frozen value). Key on this ONE name to group/count a discovery's delivery across the status and result surfaces. On the merged Discovery view this is read at result.total_prospects. Distinct from the pagination total on ProspectList/ProspectSummaryList (a LIST/SEARCH result-set count across your inbox).

withheld_countinteger

How many companies this discovery DELIVERED and then RETRACTED, so total_prospects above is the SERVABLE set rather than the originally-announced one. A prospect is retracted when a check that can only complete after delivery decides it does not belong to your search — most often a headquarters re-check placing it outside your target region. Retracted companies are removed from prospects[]/found[] and never reach your CRM. ABSENT (never 0) when nothing was retracted: absence is the signal.

terminal_reason'candidates_exhausted' | 'trailing_candidates_forgiven' | 'candidate_cap_reached' | 'candidate_search_failed' | 'time_limit_reached' | 'stalled_no_progress' | 'fan_in_incomplete' | 'target_reached' | 'saturated_known_set' | 'cancelled_by_customer' | 'null' nullable

Why the discovery stopped, once terminal; null while still running, and null on any job terminal before this field's writers shipped. Carried on BOTH the progressive status and the finalized result — they read the same durable value, so the two surfaces cannot disagree about why a run ended. candidates_exhausted = the market had no more matching candidates to evaluate, and every candidate it did dispatch was evaluated; trailing_candidates_forgiven = the run stopped while a small remainder of its own candidates (up to 5%) was still being evaluated, so the shortfall was forgiven rather than exhausted — reported separately from candidates_exhausted because the two call for OPPOSITE responses: a genuinely dry market wants a wider profile, whereas a forgiven remainder means the run was cut short and re-running the SAME profile may deliver; candidate_cap_reached = when a run's candidates ran out before your target, it fetched further batches of new candidates, and it stopped because it had screened the maximum number of candidates one run may evaluate — the sources were still returning new candidates, so the shortfall reflects the per-run screening limit, not an exhausted market; candidate_search_failed = the run's candidates ran out before your target and the search for further candidates failed repeatedly, so the market was never shown to be exhausted — re-running the SAME profile later may deliver; time_limit_reached = the run hit its time budget while still producing; stalled_no_progress = the run stopped producing results and was terminated early rather than hitting its budget (a different situation from either of the two above, and reported separately so it is not mistaken for a thin market or a short budget); fan_in_incomplete = a search step never finished, so the run was never able to ask for more candidates; target_reached = the run delivered everything you asked for; saturated_known_set = every delivered domain was already yours from an earlier search (refunded); cancelled_by_customer = you cancelled it via POST /mcp/jobs/{jobId}/cancel. When credits are refunded, the refund reason matches this.

already_owned_countinteger

How many of this discovery's delivered domains you ALREADY owned from a PRIOR discovery (any earlier search, not just this one's filters) — i.e. cross-search overlap with your existing prospects. 0 means every delivered domain is new to you. Domains already owned under this exact search's filters are de-duplicated out before delivery and are not counted here. Served on both the progressive status and the finalized result from one durable value. See already_owned_withheld_count for how many WERE de-duplicated out.

already_owned_withheld_countinteger

How many companies this discovery found, judged in-scope for THIS search, and then did NOT deliver because you already own them from an earlier run of the same search. This is the count already_owned_count explicitly excludes: those domains were suppressed one step BEFORE delivery, so they appear in neither prospects nor total_prospects. It is the number that explains an otherwise puzzling result — a repeat search of a market you have already worked returns few new companies precisely BECAUSE most of what it found is already yours, and without this field that outcome is indistinguishable from a failed run. Counted as DISTINCT companies across both suppression paths (freshly searched and re-served from your existing knowledge base), so it never double-counts one company found twice. ABSENT — never 0 — when nothing was suppressed, so any value you receive is a real one.

candidates_screenedinteger

Number of candidate companies this discovery began SCREENING (search/candidate-dispatch phase). Increases monotonically during screening so a client sees live progress even before the first prospect domain lands; 0 before screening has started. It legitimately PLATEAUS once search has exhausted the niche and the run moves into its crawl/validation tail — that is not a stall. For liveness during that tail, read evaluated_count. On the finished result it is the frozen final value, so the same number explains a run after it ends.

evaluated_countinteger

Number of screened candidates that have finished crawl + validation (a running verdict count = accepted + rejected). Complements candidates_screened: it keeps climbing through the crawl/validation tail — after screening plateaus — so a polling client sees liveness for the WHOLE run. Retains its final total once the discovery is terminal, where together with candidates_screened it is the funnel that explains the delivered count.

auto_complete_reason'stuck_processing_no_marker' | 'stuck_processing_no_prospects' | 'null' nullable

Present ONLY when automatic recovery force-completed a run that stalled in processing with no completion marker (stuck_processing_no_marker when prospects had accumulated, stuck_processing_no_prospects when none had). Absent on every normally-terminating run.

credits_refundedinteger nullable

Credits returned to the caller's balance when this run was auto-refunded (e.g. an exhausted run that delivered fewer prospects than requested). Absent when no refund applied. Same field, semantics, and shape as JobCancelled.credits_refunded — one canonical refund projection across the result and cancel surfaces.

credits_refund_reasonstring nullable

Bounded reason the refund was applied (e.g. candidates_exhausted, time_limit_reached), resolved from durable terminal state. Present iff credits_refunded is. Mirrors JobCancelled.credits_refund_reason.

completed_atstring date-time nullable

When the discovery finished (ISO-8601). Null while not yet terminal.

Example response

{
  "withheld_count": 1,
  "already_owned_count": 2,
  "already_owned_withheld_count": 11,
  "candidates_screened": 157,
  "evaluated_count": 195,
  "prospects": [
    {
      "domain": "acme.de"
    }
  ]
}

Changes

No recorded changes to this operation across all 1 revision of this API.