Skip to content

Delivery Integration

Use the delivery sync endpoint when your CRM or data warehouse pulls completed OpenProspect results on a schedule.

Endpoint

GET /api/v1/deliveries/{prospect_search_id}/companies

Required scopes: companies:read and prospects:read.

This endpoint returns delivered companies with embedded prospects and pagination metadata. Use delivered_since for incremental syncs.

Set OPENPROSPECT_API_KEY and PROSPECT_SEARCH_ID before running the examples. For one completed order, use the order-scoped result endpoint documented in the Enrichment Quick Start.

Query Parameters

Parameter Type Required Description
delivered_since ISO 8601 datetime No Return companies delivered after this timestamp
limit integer No Maximum companies to return, from 1 to 100; default 50
offset integer No Number of companies to skip; default 0

Pull Delivered Companies

This endpoint uses offset pagination over a live, newest-first result set. New deliveries can arrive between page requests and shift later offsets, so a scheduled sync can receive the same company more than once. Treat delivery pages as at-least-once input: idempotently upsert companies by their stable id and nested prospects by their stable id instead of blindly appending rows. The examples below deduplicate companies in memory; persist the same upsert behavior in your CRM or warehouse.

This example uses jq to read pagination metadata and emit one company per line. Replace the final jq command with an idempotent upsert keyed by .id.

offset=0

while true; do
  if ! page="$(
    curl --fail-with-body -sS \
      "https://api.openprospect.io/api/v1/deliveries/${PROSPECT_SEARCH_ID}/companies?limit=100&offset=${offset}" \
      -H "Authorization: Bearer ${OPENPROSPECT_API_KEY}"
  )"; then
    printf 'Delivery request failed; pagination stopped.\n' >&2
    exit 1
  fi
  jq -c '.items[]' <<<"${page}"

  has_more="$(jq -r '.has_more' <<<"${page}")"
  item_count="$(jq '.items | length' <<<"${page}")"
  if [[ "${has_more}" == "false" ]]; then
    break
  fi
  offset=$((offset + item_count))
done
import os

import httpx

api_key = os.environ["OPENPROSPECT_API_KEY"]
prospect_search_id = os.environ["PROSPECT_SEARCH_ID"]
companies_by_id = {}
offset = 0

while True:
    response = httpx.get(
        f"https://api.openprospect.io/api/v1/deliveries/{prospect_search_id}/companies",
        headers={"Authorization": f"Bearer {api_key}"},
        params={"limit": 100, "offset": offset},
        timeout=30.0,
    )
    response.raise_for_status()
    page = response.json()
    companies_by_id.update({company["id"]: company for company in page["items"]})
    if not page["has_more"]:
        break
    offset += len(page["items"])

print(f"Retrieved {len(companies_by_id)} unique companies")
const apiKey = process.env.OPENPROSPECT_API_KEY;
const prospectSearchId = process.env.PROSPECT_SEARCH_ID;
if (!apiKey || !prospectSearchId) {
  throw new Error("OPENPROSPECT_API_KEY and PROSPECT_SEARCH_ID must be set");
}

const companiesById = new Map();
let offset = 0;

while (true) {
  const url = new URL(
    `https://api.openprospect.io/api/v1/deliveries/${prospectSearchId}/companies`,
  );
  url.searchParams.set("limit", "100");
  url.searchParams.set("offset", String(offset));

  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${apiKey}` },
  });
  if (!response.ok) {
    throw new Error(`Delivery sync failed: ${response.status} ${await response.text()}`);
  }

  const page = await response.json();
  for (const company of page.items) {
    companiesById.set(company.id, company);
  }
  if (!page.has_more) {
    break;
  }
  offset += page.items.length;
}

console.log(`Retrieved ${companiesById.size} unique companies`);
interface DeliveredProspect {
  id: string;
  company_id: string;
  first_name: string;
  email: string | null;
  email_status: string | null;
  email_type: string | null;
  validation_confidence: number | null;
  bounce_risk_score: number | null;
  email_client_deliverable: boolean;
}

interface DeliveredCompany {
  id: string;
  name: string;
  source_id: string | null;
  prospects: DeliveredProspect[];
}

interface DeliveredCompaniesResponse {
  items: DeliveredCompany[];
  total: number;
  limit: number;
  offset: number;
  has_more: boolean;
}

const apiKey = process.env.OPENPROSPECT_API_KEY;
const prospectSearchId = process.env.PROSPECT_SEARCH_ID;
if (!apiKey || !prospectSearchId) {
  throw new Error("OPENPROSPECT_API_KEY and PROSPECT_SEARCH_ID must be set");
}

const companiesById = new Map<string, DeliveredCompany>();
let offset = 0;

while (true) {
  const url = new URL(
    `https://api.openprospect.io/api/v1/deliveries/${prospectSearchId}/companies`,
  );
  url.searchParams.set("limit", "100");
  url.searchParams.set("offset", String(offset));

  const response = await fetch(url, {
    headers: { Authorization: `Bearer ${apiKey}` },
  });
  if (!response.ok) {
    throw new Error(`Delivery sync failed: ${response.status} ${await response.text()}`);
  }

  const page = (await response.json()) as DeliveredCompaniesResponse;
  for (const company of page.items) {
    companiesById.set(company.id, company);
  }
  if (!page.has_more) {
    break;
  }
  offset += page.items.length;
}

console.log(`Retrieved ${companiesById.size} unique companies`);
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text.Json;

var apiKey = Environment.GetEnvironmentVariable("OPENPROSPECT_API_KEY")
    ?? throw new InvalidOperationException("OPENPROSPECT_API_KEY is not set.");
var prospectSearchId = Environment.GetEnvironmentVariable("PROSPECT_SEARCH_ID")
    ?? throw new InvalidOperationException("PROSPECT_SEARCH_ID is not set.");

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

var companiesById = new Dictionary<string, JsonElement>();
var offset = 0;

while (true)
{
    var url =
        $"https://api.openprospect.io/api/v1/deliveries/{prospectSearchId}/companies"
        + $"?limit=100&offset={offset}";
    var response = await client.GetAsync(url);
    response.EnsureSuccessStatusCode();

    using var page = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
    var items = page.RootElement.GetProperty("items").EnumerateArray().ToArray();
    foreach (var item in items)
    {
        var company = item.Clone();
        companiesById[company.GetProperty("id").GetString()!] = company;
    }

    if (!page.RootElement.GetProperty("has_more").GetBoolean())
    {
        break;
    }
    offset += items.Length;
}

Console.WriteLine($"Retrieved {companiesById.Count} unique companies");

Expected response:

The embedded prospect includes email_status, email_type, validation_confidence, bounce_risk_score, and email_client_deliverable.

{
  "items": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "Grand Hotel Berlin",
      "website_url": "https://www.grandhotel-berlin.de",
      "source_id": "crm-1001",
      "prospects": [
        {
          "id": "e5f6a7b8-c9d0-1234-e5f6-a7b8c9d01234",
          "company_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
          "first_name": "Maria",
          "last_name": "Schmidt",
          "email": "m.schmidt@grandhotel-berlin.de",
          "job_title": "General Manager",
          "email_status": "VERIFIED",
          "email_type": "PERSONAL",
          "validation_confidence": 95,
          "bounce_risk_score": 5,
          "email_client_deliverable": true
        }
      ]
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0,
  "has_more": false
}

Keep prospects whose email_status == "VERIFIED" when your integration accepts only verified emails. Use email_client_deliverable == true when you want OpenProspect's stricter default-campaign policy decision.

Incremental Sync

Store the latest successfully upserted delivered_at timestamp in your system. On the next run, send it as delivered_since.

curl -sS \
  "https://api.openprospect.io/api/v1/deliveries/${PROSPECT_SEARCH_ID}/companies?delivered_since=2026-02-25T12:00:00Z" \
  -H "Authorization: Bearer ${OPENPROSPECT_API_KEY}"

delivered_since is a lower bound, not a snapshot cursor. Apply the same value to every page request in one run, advance offset by the number of items returned until has_more is false, and idempotently upsert repeated IDs. Only after the complete run succeeds should you advance the stored checkpoint to the greatest delivered_at value you successfully persisted.