Best practices

Credit efficiency#

Run with "size": 1 first to see the total result count, then decide how many results you actually need before spending credits.

Use fields to request only what you need#

{
  "size": 500,
  "fields": ["domain", "name", "companySize", "primaryCountry", "industries"],
  "companiesFilters": { ... }
}

Reduces response size and latency on high-volume pipelines.

Search → Reveal vs. Enrich Person with revealEmails#

Approach When to use Cost (v3)
Search People → Reveal Emails Building a list from scratch 0.2/result + 1/email found
Enrich Person with revealEmails: true Enriching a known CRM record 0.1/result + 1/email found

Both cost the same per-email — the difference is workflow, not price.

Lookalike searches — qualify before scaling#

Lookalike searches cost the same per result as filter searches on v3. That said, poor seed domains return poor results — test on a small size first and validate seed domains with Warmup before scaling up.

Lookup Companies is cheaper per result than Search Companies with includeDomains. Use Lookup whenever you have a specific list of domains.

Data quality#

Email status#

Status Use in outreach?
verified Yes — SMTP-confirmed via ZeroBounce
guessed Yes — high-confidence pattern match
catchAll Yes — ZeroBounce-verified, catch-all domain accepts all inbound
notFound No — not charged

Headcount fields#

Field Source Use for
companySizes Ocean.io estimate (bracket) Quick segmentation
employeeCountOcean Ocean.io estimate (numeric) Precise thresholds (e.g. > 75)
employeeCountLinkedin LinkedIn self-reported When LinkedIn count is specifically the signal

LinkedIn counts lag and can be inflated by contractors.

Handling triggered in enrich responses#

triggered means the domain wasn't in the database — crawling started. Wait 2–5 minutes, then retry:

import time

def enrich_with_retry(domains, api_token, max_attempts=3):
    pending = domains[:]
    results = {}
    for attempt in range(max_attempts):
        if not pending:
            break
        response = enrich_companies(pending, api_token)
        still_pending = []
        for domain, result in response.items():
            if result["status"] == "triggered":
                still_pending.append(domain)
            else:
                results[domain] = result
        pending = still_pending
        if pending:
            time.sleep(180)
    return results

Why your search returns 0 results#

  1. Filters too narrow — remove one at a time until results appear
  2. Invalid enum values — industry/technology names must match /v2/data-fields exactly
  3. Wrong location format — use lowercase alpha-2 codes: "de" not "DE" or "Germany"
  4. minScore too high — lower or remove it when using lookalikeDomains

Rate limiting#

60 req/min, 1,000 req/day (self-serve). See Rate Limiting for backoff code. Check dailyLimitRateLeft from Get Credit Balance before starting large batch jobs.

Webhook reliability#

Return 2xx immediately, process async#

Acknowledge the webhook right away and offload processing to a background queue. If your handler is slow, Ocean.io will assume failure and retry.

work_queue = Queue()

@app.route("/webhooks/ocean", methods=["POST"])
def receive_webhook():
    work_queue.put(request.json)
    return jsonify({"status": "ok"}), 200

Make your handler idempotent#

The same payload may arrive more than once. Deduplicate on the record IDs in the payload. Use Redis or a database in production — not an in-memory set.

Secure your endpoint#

Include a secret token in the webhook URL and validate it before processing:

https://yourapp.com/webhooks/ocean?secret=MY_SECRET

Delivery times#

Operation Typical
Reveal Emails (≤50 IDs) 1–3 min
Reveal Emails (500 IDs) 2–10 min
Enrich batch (≤100 records) 1–5 min
Enrich batch (5,000 records) 10–30 min