Skip to content

Discovery Order Guide

Let OpenProspect find companies for you based on your Ideal Customer Profile.


Overview

Discovery orders are the counterpart to enrichment orders. Instead of providing a company list for enrichment, you describe your ideal customer and OpenProspect finds matching companies for you.

Enrichment Order Discovery Order
Input Your company list Your Ideal Customer Profile
Output Enriched data for your companies New companies matching your ICP
order_type ENRICHMENT DISCOVERY
companies field Required (1-10,000) Not allowed
source_id in results Your external_id null (system-discovered)

Order Lifecycle

Discovery orders follow the same 5-status lifecycle as enrichment orders:

stateDiagram-v2
    [*] --> RECEIVED: Create order
    RECEIVED --> ACCEPTED: Admin review
    RECEIVED --> CANCELLED: Cancel
    ACCEPTED --> IN_PROGRESS: Discovery starts
    ACCEPTED --> CANCELLED: Cancel
    IN_PROGRESS --> COMPLETED: Companies found & enriched
    IN_PROGRESS --> CANCELLED: Cancel
    COMPLETED --> [*]
    CANCELLED --> [*]
Status What It Means
RECEIVED Your order is queued for admin review
ACCEPTED Approved and the discovery pipeline is starting
IN_PROGRESS Actively searching for and enriching companies
COMPLETED All data is ready \u2014 retrieve your results
CANCELLED Order was cancelled

Prerequisites

Before you begin:

  • API Key with orders:read, orders:write, blacklists:read, and blacklists:write scopes. Create one in the OpenProspect dashboard under Settings > Developer > API Keys.
  • Base URL: https://api.openprospect.io/api/v1

Scopes

Discovery orders need four scopes: orders:read, orders:write for order management, plus blacklists:read, blacklists:write for blacklist management. This combination is available as the Discovery partner scope set in the dashboard.


Step 1: Create a Blacklist (Optional)

If you want to exclude known companies from discovery results, create a blacklist first. This is optional but recommended to avoid discovering companies you already know.

See the Blacklist Management Guide for full details. Quick summary:

# Create a blacklist
curl -X POST "https://api.openprospect.io/api/v1/blacklists" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Existing Customers", "description": "Companies already in our CRM"}'

# Add entries
curl -X POST "https://api.openprospect.io/api/v1/blacklists/BLACKLIST_ID/entries" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "entries": [
      {"company_name": "Acme Corp", "website_url": "https://www.acme-corp.com"},
      {"company_name": "Global Industries GmbH", "city": "Munich", "country": "Germany"}
    ]
  }'

Matching Accuracy

Always provide website_url in blacklist entries when available. Domain-based matching is more reliable than name-only matching. See Matching Accuracy for details.


Step 2: Create a Discovery Order

Submit your Ideal Customer Profile and let OpenProspect find matching companies.

Request

POST /api/v1/orders

curl -X POST "https://api.openprospect.io/api/v1/orders" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "order_type": "DISCOVERY",
    "title": "DACH SaaS Discovery Q1 2026",
    "features": ["COMPANY_DATA", "CONTACTS", "ANALYSIS"],
    "ideal_customer_profile": "Mid-market SaaS companies in the DACH region with 50-500 employees, focused on B2B software solutions",
    "seller_offering": "AI-powered lead generation and prospect enrichment platform",
    "seller_industry": "Software / Technology",
    "target_countries": ["Germany", "Austria", "Switzerland"],
    "target_cities": ["Berlin", "Munich", "Vienna"],
    "excluded_industries": ["Government", "Non-profit"],
    "output_language": "en",
    "blacklist_ids": ["d33b69ce-80ce-4ede-b4df-4b11427f8b55"],
    "profile_name": "DACH SaaS Discovery Profile"
  }'
import httpx

API_KEY = "lnc_live_your_api_key_here"
BASE_URL = "https://api.openprospect.io/api/v1"

order = {
    "order_type": "DISCOVERY",
    "title": "DACH SaaS Discovery Q1 2026",
    "features": ["COMPANY_DATA", "CONTACTS", "ANALYSIS"],
    "ideal_customer_profile": (
        "Mid-market SaaS companies in the DACH region "
        "with 50-500 employees, focused on B2B software solutions"
    ),
    "seller_offering": "AI-powered lead generation and prospect enrichment platform",
    "seller_industry": "Software / Technology",
    "target_countries": ["Germany", "Austria", "Switzerland"],
    "target_cities": ["Berlin", "Munich", "Vienna"],
    "excluded_industries": ["Government", "Non-profit"],
    "output_language": "en",
    "blacklist_ids": ["d33b69ce-80ce-4ede-b4df-4b11427f8b55"],
    "profile_name": "DACH SaaS Discovery Profile",
}

response = httpx.post(
    f"{BASE_URL}/orders",
    json=order,
    headers={"Authorization": f"Bearer {API_KEY}"},
)

if response.status_code == 202:
    data = response.json()
    print(f"Discovery order created: {data['order_id']}")
    print(f"Status: {data['status']}")
else:
    print(f"Error {response.status_code}: {response.json()}")
const API_KEY = "lnc_live_your_api_key_here";
const BASE_URL = "https://api.openprospect.io/api/v1";

const order = {
  order_type: "DISCOVERY",
  title: "DACH SaaS Discovery Q1 2026",
  features: ["COMPANY_DATA", "CONTACTS", "ANALYSIS"],
  ideal_customer_profile:
    "Mid-market SaaS companies in the DACH region with 50-500 employees, focused on B2B software solutions",
  seller_offering:
    "AI-powered lead generation and prospect enrichment platform",
  seller_industry: "Software / Technology",
  target_countries: ["Germany", "Austria", "Switzerland"],
  target_cities: ["Berlin", "Munich", "Vienna"],
  excluded_industries: ["Government", "Non-profit"],
  output_language: "en",
  blacklist_ids: ["d33b69ce-80ce-4ede-b4df-4b11427f8b55"],
  profile_name: "DACH SaaS Discovery Profile",
};

const response = await fetch(`${BASE_URL}/orders`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(order),
});

if (response.status === 202) {
  const data = await response.json();
  console.log(`Discovery order created: ${data.order_id}`);
  console.log(`Status: ${data.status}`);
} else {
  console.error(`Error ${response.status}:`, await response.json());
}
const API_KEY = "lnc_live_your_api_key_here";
const BASE_URL = "https://api.openprospect.io/api/v1";

interface CreateOrderResponse {
  order_id: string;
  order_type: "ENRICHMENT" | "DISCOVERY";
  status: string;
  company_count: number;
  estimated_cost: { total: number; currency: string } | null;
  message: string;
}

const order = {
  order_type: "DISCOVERY" as const,
  title: "DACH SaaS Discovery Q1 2026",
  features: ["COMPANY_DATA", "CONTACTS", "ANALYSIS"],
  ideal_customer_profile:
    "Mid-market SaaS companies in the DACH region with 50-500 employees, focused on B2B software solutions",
  seller_offering:
    "AI-powered lead generation and prospect enrichment platform",
  seller_industry: "Software / Technology",
  target_countries: ["Germany", "Austria", "Switzerland"],
  target_cities: ["Berlin", "Munich", "Vienna"],
  excluded_industries: ["Government", "Non-profit"],
  output_language: "en",
  blacklist_ids: ["d33b69ce-80ce-4ede-b4df-4b11427f8b55"],
  profile_name: "DACH SaaS Discovery Profile",
};

const response = await fetch(`${BASE_URL}/orders`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify(order),
});

if (response.status === 202) {
  const data: CreateOrderResponse = await response.json();
  console.log(`Discovery order created: ${data.order_id}`);
  console.log(`Status: ${data.status}`);
} else {
  console.error(`Error ${response.status}:`, await response.json());
}
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;

var apiKey = "lnc_live_your_api_key_here";
var baseUrl = "https://api.openprospect.io/api/v1";

var order = new
{
    order_type = "DISCOVERY",
    title = "DACH SaaS Discovery Q1 2026",
    features = new[] { "COMPANY_DATA", "CONTACTS", "ANALYSIS" },
    ideal_customer_profile =
        "Mid-market SaaS companies in the DACH region with 50-500 employees, focused on B2B software solutions",
    seller_offering =
        "AI-powered lead generation and prospect enrichment platform",
    seller_industry = "Software / Technology",
    target_countries = new[] { "Germany", "Austria", "Switzerland" },
    target_cities = new[] { "Berlin", "Munich", "Vienna" },
    excluded_industries = new[] { "Government", "Non-profit" },
    output_language = "en",
    blacklist_ids = new[] { "d33b69ce-80ce-4ede-b4df-4b11427f8b55" },
    profile_name = "DACH SaaS Discovery Profile"
};

using var client = new HttpClient();
client.DefaultRequestHeaders.Authorization =
    new AuthenticationHeaderValue("Bearer", apiKey);

var json = JsonSerializer.Serialize(order);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync($"{baseUrl}/orders", content);

if ((int)response.StatusCode == 202)
{
    var body = await response.Content.ReadAsStringAsync();
    Console.WriteLine($"Discovery order created: {body}");
}
else
{
    Console.WriteLine($"Error {(int)response.StatusCode}");
    Console.WriteLine(await response.Content.ReadAsStringAsync());
}

Response (202 Accepted)

{
  "order_id": "74f00d79-7e4a-4ba3-a30c-012de85d6d78",
  "order_type": "DISCOVERY",
  "status": "RECEIVED",
  "company_count": 0,
  "estimated_cost": null,
  "message": "Order received. Awaiting admin review."
}

Checkpoint

Save the order_id from the response. You need it for all subsequent calls.

Discovery Response Differences

  • company_count is 0 at submission (companies haven't been discovered yet)
  • estimated_cost is null (cost is determined after discovery)

Request Parameters

Required Fields

Parameter Type Max Length Description
order_type string -- Must be "DISCOVERY"
title string 500 A descriptive name for your order
features string[] -- Enrichment features to apply to discovered companies (see Features Reference)
ideal_customer_profile string 5,000 Detailed description of your ideal customer
seller_offering string 5,000 What your company sells \u2014 used for match scoring
target_countries string[] -- Countries to search in (e.g., ["Germany", "Austria"])

Optional Fields

Parameter Type Max Length Description
seller_industry string 1,000 Your company's industry
target_cities string[] -- Specific cities to focus on
excluded_industries string[] -- Industries to exclude from results
output_language string 10 Language code for output (e.g., "en", "de")
blacklist_ids UUID[] -- Blacklist IDs to assign. Excluded companies won't appear in results.
priority string -- "NORMAL" (default) or "EXPRESS"
webhook_url string -- URL for status change notifications
contact_roles string[] -- Target contact roles (e.g., ["CEO", "CTO"])
profile_name string 500 Name for auto-created profile. Mutually exclusive with profile_id.
profile_id UUID -- Existing profile ID. Mutually exclusive with profile_name.

Forbidden Fields

Discovery orders cannot include a companies array. If you want to enrich specific companies, use an enrichment order instead.

Writing an Effective ICP

The ideal_customer_profile field drives the quality of discovery results. Be specific:

Good ICP:

"Mid-market B2B SaaS companies in the DACH region with 50-500 employees, focused on enterprise software solutions for HR, finance, or operations. Ideally companies that have raised Series A-C funding and are actively growing their sales team."

Too vague:

"Tech companies in Europe"

Include details about:

  • Industry / vertical (e.g., "B2B SaaS", "hospitality", "logistics")
  • Company size (employee count, revenue range)
  • Geography (use target_countries and target_cities for precision)
  • Business characteristics (growth stage, tech stack, hiring signals)
  • What makes them a good fit for your product/service

Step 3: Track Order Status

After creating a discovery order, poll the status endpoint to know when results are ready.

Request

GET /api/v1/orders/{order_id}

curl -X GET "https://api.openprospect.io/api/v1/orders/74f00d79-7e4a-4ba3-a30c-012de85d6d78" \
  -H "Authorization: Bearer YOUR_API_KEY"
import time

order_id = "74f00d79-7e4a-4ba3-a30c-012de85d6d78"

while True:
    response = httpx.get(
        f"{BASE_URL}/orders/{order_id}",
        headers={"Authorization": f"Bearer {API_KEY}"},
    )

    if response.status_code != 200:
        print(f"Polling error {response.status_code}, retrying...")
        time.sleep(60)
        continue

    data = response.json()
    status = data["status"]
    print(f"Order status: {status}")

    if status == "COMPLETED":
        print(f"Results are ready! {data['company_count']} companies found.")
        break
    elif status == "CANCELLED":
        print("Order was cancelled.")
        break

    time.sleep(60)  # Check every minute
const orderId = "74f00d79-7e4a-4ba3-a30c-012de85d6d78";

async function pollOrderStatus() {
  while (true) {
    const response = await fetch(`${BASE_URL}/orders/${orderId}`, {
      headers: { Authorization: `Bearer ${API_KEY}` },
    });

    if (!response.ok) {
      console.warn(`Polling error ${response.status}, retrying...`);
      await new Promise((resolve) => setTimeout(resolve, 60000));
      continue;
    }

    const data = await response.json();
    console.log(`Order status: ${data.status}`);

    if (data.status === "COMPLETED") {
      console.log(`Results are ready! ${data.company_count} companies found.`);
      return data;
    }
    if (data.status === "CANCELLED") {
      console.log("Order was cancelled.");
      return data;
    }

    await new Promise((resolve) => setTimeout(resolve, 60000));
  }
}

await pollOrderStatus();
interface OrderStatusResponse {
  order_id: string;
  order_type: "ENRICHMENT" | "DISCOVERY";
  title: string;
  status: "RECEIVED" | "ACCEPTED" | "IN_PROGRESS" | "COMPLETED" | "CANCELLED";
  priority: string;
  company_count: number;
  features: string[];
  estimated_cost: { total: number; currency: string } | null;
  prospect_search_id: string | null;
  admin_notes: string | null;
  webhook_url: string | null;
  seller_offering: string | null;
  seller_industry: string | null;
  excluded_industries: string[] | null;
  target_countries: string[] | null;
  target_cities: string[] | null;
  output_language: string | null;
  created_at: string;
  accepted_at: string | null;
  completed_at: string | null;
  cancelled_at: string | null;
}

const orderId = "74f00d79-7e4a-4ba3-a30c-012de85d6d78";

async function pollOrderStatus(): Promise<OrderStatusResponse> {
  while (true) {
    const response = await fetch(`${BASE_URL}/orders/${orderId}`, {
      headers: { Authorization: `Bearer ${API_KEY}` },
    });

    if (!response.ok) {
      console.warn(`Polling error ${response.status}, retrying...`);
      await new Promise((resolve) => setTimeout(resolve, 60000));
      continue;
    }

    const data: OrderStatusResponse = await response.json();
    console.log(`Order status: ${data.status}`);

    if (data.status === "COMPLETED" || data.status === "CANCELLED") {
      return data;
    }

    await new Promise((resolve) => setTimeout(resolve, 60000));
  }
}

const result = await pollOrderStatus();
var orderId = "74f00d79-7e4a-4ba3-a30c-012de85d6d78";

while (true)
{
    var response = await client.GetAsync($"{baseUrl}/orders/{orderId}");

    if (!response.IsSuccessStatusCode)
    {
        Console.WriteLine($"Polling error {(int)response.StatusCode}, retrying...");
        await Task.Delay(60000);
        continue;
    }

    var body = await response.Content.ReadAsStringAsync();
    var data = JsonSerializer.Deserialize<JsonElement>(body);
    var status = data.GetProperty("status").GetString();

    Console.WriteLine($"Order status: {status}");

    if (status == "COMPLETED")
    {
        var count = data.GetProperty("company_count").GetInt32();
        Console.WriteLine($"Results are ready! {count} companies found.");
        break;
    }
    if (status == "CANCELLED")
    {
        Console.WriteLine("Order was cancelled.");
        break;
    }

    await Task.Delay(60000); // Check every minute
}

Response (200 OK)

{
  "order_id": "74f00d79-7e4a-4ba3-a30c-012de85d6d78",
  "order_type": "DISCOVERY",
  "title": "DACH SaaS Discovery Q1 2026",
  "status": "RECEIVED",
  "priority": "NORMAL",
  "company_count": 0,
  "features": ["COMPANY_DATA", "CONTACTS", "ANALYSIS"],
  "estimated_cost": null,
  "prospect_search_id": null,
  "admin_notes": null,
  "webhook_url": null,
  "seller_offering": "AI-powered lead generation and prospect enrichment platform",
  "seller_industry": "Software / Technology",
  "excluded_industries": ["Government", "Non-profit"],
  "target_countries": ["Germany", "Austria", "Switzerland"],
  "target_cities": ["Berlin", "Munich", "Vienna"],
  "output_language": "en",
  "created_at": "2026-03-01T10:30:00.000000Z",
  "accepted_at": null,
  "completed_at": null,
  "cancelled_at": null
}

The status response includes all discovery-specific fields (seller_offering, seller_industry, excluded_industries, target_countries, target_cities, output_language) so you can verify your order configuration.

List All Orders

You can also list all your orders with optional status filtering:

curl -X GET "https://api.openprospect.io/api/v1/orders?status=COMPLETED&limit=10" \
  -H "Authorization: Bearer YOUR_API_KEY"

Discovery orders appear alongside enrichment orders, distinguished by order_type: "DISCOVERY".


Step 4: Retrieve Results

Once the order is COMPLETED, retrieve the discovered companies with all their enriched data.

Request

GET /api/v1/orders/{order_id}/results

curl -X GET "https://api.openprospect.io/api/v1/orders/74f00d79-7e4a-4ba3-a30c-012de85d6d78/results?limit=50" \
  -H "Authorization: Bearer YOUR_API_KEY"
order_id = "74f00d79-7e4a-4ba3-a30c-012de85d6d78"
all_companies = []
offset = 0
limit = 50

while True:
    response = httpx.get(
        f"{BASE_URL}/orders/{order_id}/results",
        params={"limit": limit, "offset": offset},
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    data = response.json()

    for company in data["items"]:
        print(f"Company: {company['name']}")
        print(f"  Match Score: {company['match_score']}")
        print(f"  Prospects: {len(company['prospects'])}")
        all_companies.append(company)

    if not data["has_more"]:
        break
    offset += limit

print(f"\nTotal companies discovered: {len(all_companies)}")
const orderId = "74f00d79-7e4a-4ba3-a30c-012de85d6d78";
const allCompanies = [];
let offset = 0;
const limit = 50;

while (true) {
  const response = await fetch(
    `${BASE_URL}/orders/${orderId}/results?limit=${limit}&offset=${offset}`,
    { headers: { Authorization: `Bearer ${API_KEY}` } }
  );
  const data = await response.json();

  for (const company of data.items) {
    console.log(`Company: ${company.name}`);
    console.log(`  Match Score: ${company.match_score}`);
    console.log(`  Prospects: ${company.prospects.length}`);
    allCompanies.push(company);
  }

  if (!data.has_more) break;
  offset += limit;
}

console.log(`\nTotal companies discovered: ${allCompanies.length}`);
interface Prospect {
  id: string;
  company_id: string;
  first_name: string;
  last_name: string | null;
  job_title: string | null;
  email: string | null;
  phone_number: string | null;
  linkedin_url: string | null;
  qualification_score: number | null;
  qualification_reason: string | null;
  email_type: string | null;
  validation_confidence: number | null;
  bounce_risk_score: number | null;
}

interface Company {
  id: string;
  name: string;
  website_url: string | null;
  normalized_url: string | null;
  business_type: string | null;
  city: string | null;
  country: string | null;
  description: string | null;
  employee_count: number | null;
  phone_numbers: string[] | null;
  social_media_links: { platform: string; url: string }[] | null;
  match_score: number | null;
  match_reasons: string[] | null;
  match_reasoning: string | null;
  web_technologies: string[] | null;
  web_technologies_analyzed_at: string | null;
  source_id: string | null;
  delivered_at: string | null;
  legal_registration_info: Record<string, unknown> | null;
  prospects: Prospect[];
}

interface ResultsResponse {
  items: Company[];
  total: number;
  limit: number;
  offset: number;
  has_more: boolean;
}

const orderId = "74f00d79-7e4a-4ba3-a30c-012de85d6d78";
const allCompanies: Company[] = [];
let offset = 0;
const limit = 50;

while (true) {
  const response = await fetch(
    `${BASE_URL}/orders/${orderId}/results?limit=${limit}&offset=${offset}`,
    { headers: { Authorization: `Bearer ${API_KEY}` } }
  );
  const data: ResultsResponse = await response.json();
  allCompanies.push(...data.items);

  if (!data.has_more) break;
  offset += limit;
}

console.log(`Total companies discovered: ${allCompanies.length}`);
var orderId = "74f00d79-7e4a-4ba3-a30c-012de85d6d78";
var offset = 0;
var limit = 50;
var allCompanies = new List<JsonElement>();

while (true)
{
    var response = await client.GetAsync(
        $"{baseUrl}/orders/{orderId}/results?limit={limit}&offset={offset}");
    var body = await response.Content.ReadAsStringAsync();
    var data = JsonSerializer.Deserialize<JsonElement>(body);

    foreach (var company in data.GetProperty("items").EnumerateArray())
    {
        Console.WriteLine($"Company: {company.GetProperty("name")}");
        Console.WriteLine($"  Match Score: {company.GetProperty("match_score")}");
        allCompanies.Add(company);
    }

    if (!data.GetProperty("has_more").GetBoolean()) break;
    offset += limit;
}

Console.WriteLine($"\nTotal companies discovered: {allCompanies.Count}");

Response (200 OK)

{
  "items": [
    {
      "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "name": "CloudOps Technologies GmbH",
      "website_url": "https://www.cloudops-tech.de",
      "normalized_url": "cloudops-tech.de",
      "business_type": "B2B SaaS",
      "city": "Berlin",
      "country": "Germany",
      "description": "Cloud operations platform for enterprise DevOps teams...",
      "employee_count": 85,
      "phone_numbers": ["+49 30 98765432"],
      "social_media_links": [
        {"platform": "linkedin", "url": "https://linkedin.com/company/cloudops-tech"}
      ],
      "match_score": 9,
      "match_reasons": ["Industry match", "Size match", "Location match", "Growth signals"],
      "match_reasoning": "Strong ICP fit: B2B SaaS company in Berlin with 85 employees...",
      "web_technologies": ["React", "AWS", "Kubernetes"],
      "web_technologies_analyzed_at": "2026-03-02T14:00:00.000000Z",
      "source_id": null,
      "delivered_at": "2026-03-02T14:30:00.000000Z",
      "legal_registration_info": null,
      "prospects": [
        {
          "id": "f6a7b8c9-d0e1-2345-f6a7-b8c9d0e12345",
          "company_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
          "first_name": "Thomas",
          "last_name": "Weber",
          "job_title": "CTO",
          "email": "t.weber@cloudops-tech.de",
          "phone_number": null,
          "linkedin_url": "https://linkedin.com/in/thomasweber",
          "qualification_score": 9,
          "qualification_reason": "C-level technology decision maker",
          "email_type": "PERSONAL",
          "validation_confidence": 92,
          "bounce_risk_score": 8
        }
      ]
    }
  ],
  "total": 47,
  "limit": 50,
  "offset": 0,
  "has_more": false
}

Discovery Results vs Enrichment Results

Discovery results have the same structure as enrichment results, with one key difference: source_id is null for discovered companies because they were found by the system, not submitted by you. Use the company id field for your own tracking.

Pagination

Parameter Default Range Description
limit 50 1-100 Companies per page
offset 0 0+ Number of companies to skip
delivered_since -- ISO 8601 Only companies delivered after this timestamp

Webhook Notifications

Instead of polling, you can receive status change notifications via webhook. Include webhook_url when creating your order:

{
  "order_type": "DISCOVERY",
  "title": "DACH SaaS Discovery Q1 2026",
  "features": ["COMPANY_DATA", "CONTACTS", "ANALYSIS"],
  "ideal_customer_profile": "...",
  "seller_offering": "...",
  "target_countries": ["Germany"],
  "webhook_url": "https://your-api.com/webhooks/openprospect"
}

When the order status changes, we send a POST request to your URL:

{
  "event": "order.status_changed",
  "order_id": "74f00d79-7e4a-4ba3-a30c-012de85d6d78",
  "status": "COMPLETED",
  "previous_status": "IN_PROGRESS",
  "timestamp": "2026-03-02T14:30:00.000000+00:00"
}

For webhook security options and retry policy, see the Enrichment Order Guide - Webhooks.


Error Handling

Discovery-Specific Validation Errors (422)

Error Cause Fix
ideal_customer_profile is required Missing ICP for discovery Provide a detailed ICP description
seller_offering is required Missing seller offering Describe what your company sells
target_countries is required Missing target countries Provide at least one target country
companies is not allowed Included companies array Remove companies \u2014 discovery finds companies for you
Duplicate features Same feature listed twice Remove duplicates from features
Profile conflict Both profile_id and profile_name Use one or the other

Other Errors

Status Code Cause
401 UNAUTHORIZED Invalid or expired API key
403 AUTHORIZATION_ERROR Missing required scopes
404 ORDER_NOT_FOUND Order ID does not exist

For the full error reference, see Error Codes.


FAQ

How long does discovery take?

Discovery orders typically take longer than enrichment orders because the system needs to find companies before enriching them. Processing time depends on the breadth of your ICP and target geography.

Orders are processed by our admin team. You receive a webhook notification (or can poll) when processing completes.

How many companies will be discovered?

The number of results depends on your ICP specificity, target geography, and the availability of matching companies. More specific ICPs tend to produce fewer but higher-quality matches.

Can I combine discovery with enrichment features?

Yes. The features array works the same way for both order types. Common discovery combinations:

  • Minimum: ["COMPANY_DATA"] \u2014 basic company profiles
  • Standard: ["COMPANY_DATA", "CONTACTS", "ANALYSIS"] \u2014 profiles + contacts + match scoring
  • Full: ["COMPANY_DATA", "CONTACTS", "ANALYSIS", "TECHNOLOGIES", "EMAIL_DISCOVERY"] \u2014 everything

See Enrichment Features for the complete list.

What does source_id: null mean in results?

For enrichment orders, source_id maps to the external_id you submitted with each company. For discovery orders, source_id is always null because the companies were found by the system, not submitted by you.

Can I attach blacklists after creating an order?

No. Blacklists must be specified at order creation via blacklist_ids. To change blacklists, create a new order.

How do discovery orders work with the enrichment guide?

Discovery and enrichment orders share the same endpoints (POST /api/v1/orders, GET /api/v1/orders/{id}, GET /api/v1/orders/{id}/results). The key differences are in the request body (ICP fields vs company list) and response shapes. See the comparison table at the top of this guide.


API Reference Summary

Method Path Scope Description
POST /api/v1/orders orders:write Create discovery order (202) with order_type: "DISCOVERY"
GET /api/v1/orders orders:read List your orders
GET /api/v1/orders/{order_id} orders:read Get order status
GET /api/v1/orders/{order_id}/results orders:read Get discovered companies

See also: Blacklist Management Guide for managing company exclusion lists.