Skip to content
Developers

Netounim API

A single REST API to find, verify, enrich and sequence B2B leads. HTTPS only, JSON in and out, predictable error codes, idempotent where it counts.

API is in pre-release.

The endpoints below work today and are stable enough to integrate against. But the response envelope, error shape and some advanced features (Idempotency-Key headers, opt-in scopes per key, sandbox tm_test_* keys, X-RateLimit-* response headers, OpenAPI 3.0 spec, official SDKs) are not yet implemented — they're listed on the roadmap. We'll lock the formal v1 contract once we have a couple of production customers driving real feedback. Until then, expect occasional shape adjustments announced in the changelog.

Overview

What you can do with the API.

The Netounim API exposes the same primitives as the dashboard. Bearer auth + JSON, no SDK required. Most integrations are live in under an hour.

  • Find an email from a name + company
  • Verify deliverability (MX + SMTP + catch-all)
  • Resolve mobile + direct-dial phone numbers
  • Bulk-enrich lists of leads asynchronously
  • Trigger workflow runs from your own systems
  • Subscribe to webhook events for state changes
Quickstart

Make your first call in 60 seconds.

  1. 1Sign in and generate an API key at Settings → API. Treat it like a password.
  2. 2Base URL: https://api.netounim.com. A sandbox environment (tm_test_* keys, no credits charged) is on the roadmap — until then, test with a real key against the production base URL using mock-friendly inputs (names containing "test" / "example" return synthetic data without charging real provider credits).
  3. 3Send your first request. The example below resolves an email for a real person at a real domain.
curl https://api.netounim.com/api/search/find \
  -H "Authorization: Bearer $NETOUNIM_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "firstName": "Ada",
    "lastName":  "Lovelace",
    "domain":    "analyticalengine.io"
  }'
Test-friendly inputs
Inputs containing "test" / "example" / "fake" return synthetic deterministic data via the mock provider without charging real provider credits. Use them in CI until the proper sandbox lands.
Authentication

Bearer tokens, scoped to a workspace.

Every request must carry an Authorization: Bearer <key> header. Keys are workspace-scoped — a key from workspace A can never read a row from workspace B, even by ID.

  • Key prefix today: tm_live_… for production. A separate tm_test_… sandbox prefix is on the roadmap.
  • Scopes: each key currently grants full workspace access. Per-key opt-in scopes (emails:read, phones:write, etc.) are on the roadmap.
  • Rotation: revoke and recreate from Settings → API. The revoked key stops working immediately (no 24 h overlap yet — that's a roadmap item).
  • Leaked a key? Revoke from Settings → API right away — it's killed globally on the next request lookup.
Rate limits & errors

Predictable budgets, structured errors.

Per-key budgets are enforced via a global limiter plus per-route tighter limiters on sensitive endpoints (auth, phone find, bulk uploads). Defaults today are conservative — ~120 req/min on most endpoints; bulk submissions are throttled separately. Specific budgets are listed alongside each endpoint when meaningful.

Canonical rate-limit headers are on the roadmap
The standard X-RateLimit-Limit / Remaining / Reset response headers are not yet emitted. Today, the server responds with HTTP 429 + Retry-After when you hit a limit — that's the only signal to back off on. Headers will land once we tag a stable v1.

Errors come back as JSON with a top-level success: false and an error string. Validation errors include the zod issues array surfaced by our middleware. Today this envelope is simpler than the eventual Stripe-style { error: { code, request_id, issues } } — the richer shape is on the roadmap for the v1 freeze.

{
  "success": false,
  "error":   "domain is not a valid hostname",
  "issues":  [
    { "path": ["domain"], "message": "must be a valid hostname" }
  ]
}
POST /api/search/find

Find an email.

Resolves the most likely email for a person given a name + company hint. Returns the address, a confidence score 0-100, and the status from our verifier (VALID, ACCEPT_ALL, RISKY, INVALID). Charges 1 credit per hit; failed lookups are free.

POST /api/search/find HTTP/1.1
Host: api.netounim.com
Authorization: Bearer tm_live_...
Content-Type: application/json

{
  "firstName": "Ada",
  "lastName":  "Lovelace",
  "company":   "Analytical Engine",
  "domain":    "analyticalengine.io"
}
POST /api/verify/email

Verify deliverability.

MX + SMTP + catch-all + disposable check. No email is sent. 95%+ accuracy on real-world lists. 1 credit per check.

curl https://api.netounim.com/api/verify/email \
  -H "Authorization: Bearer $NETOUNIM_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "[email protected]" }'
POST /api/bulk/upload

Bulk enrichment.

Submit a CSV file via multipart/form-data. The endpoint returns immediately with a jobId. Subscribe via webhook BULK_COMPLETED or long-poll GET /api/bulk/jobs/{id} for the status.

curl https://api.netounim.com/api/bulk/upload \
  -H "Authorization: Bearer $NETOUNIM_KEY" \
  -F "[email protected]" \
  -F "type=FIND"
POST /api/phone/find

Phone Waterfall.

Multi-provider cascade (PDL → Apollo → Twilio Lookup validation). Returns the number in E.164, the type (MOBILE, DIRECT_DIAL, OFFICE, UNKNOWN), the source, and a confidence score. 4 credits per successful hit, 2 on real-provider miss, 0 on cache hit.

POST /api/phone/find
{
  "firstName":   "Ada",
  "lastName":    "Lovelace",
  "company":     "Analytical Engine",
  "linkedinUrl": "https://linkedin.com/in/adalovelace"
}
POST /api/phone/bulk

Bulk phone enrichment.

Async batch — submit up to 1,000 lead IDs in one call. Returns a jobId + total. Poll GET /api/bulk/:id for progress, or subscribe to the BULK_COMPLETED webhook.

POST /api/phone/bulk
{
  "leadIds": ["clz1abc...", "clz2def...", "clz3ghi..."]
}
POST /api/workflows/:id/run

Trigger a workflow run.

Kick off a saved workflow from your own backend. Returns immediately with a run_id; subscribe to workflow.completed webhooks for the final state.

POST /api/workflows/clzwf1abc.../run
{
  "input": {
    "leads": [
      { "firstName": "Ada", "lastName": "Lovelace", "domain": "analyticalengine.io" }
    ]
  }
}
Webhooks

Get notified when state changes.

Subscribe at Settings → API → Webhooks. We deliver with a 5-second timeout, 3 retries on a backoff, and an HMAC-SHA256 signature in the X-Netounim-Signature header so you can verify origin.

import crypto from "node:crypto";

export function verify(rawBody, signature, secret) {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(rawBody, "utf8")
    .digest("hex");
  // Use timingSafeEqual to avoid leaking timing info
  return crypto.timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(signature, "hex"),
  );
}
Ship idempotent handlers
Retries mean you can occasionally see the same event twice. De-dupe on event.id at the destination.
Mailbox + sending — Pre-release

Bring your own SMTP — or use a managed pool.

Connect a Gmail, Outlook 365 or any IMAP-compatible mailbox at Settings → Mailboxes. Today this is used for inbound reply sync only; the API-level POST /sends endpoint for queueing transactional sends from your backend is not yet available — outbound delivery currently runs through the campaign engine in the dashboard, or via the Postmark integration on managed plans. Get in touch if you need programmatic sending exposed via the API.

Pre-release surface
The shape below is a design preview — it will likely change before it ships. Don't build against it yet.
POST /api/sends   (NOT YET AVAILABLE)
{
  "mailboxId": "clz_mailbox...",
  "to":         "[email protected]",
  "subject":    "Quick question, Ada",
  "html":       "<p>Hi Ada,</p><p>I'm reaching out about ...</p>",
  "tracking":   { "opens": true, "clicks": true },
  "headers":    { "List-Unsubscribe": "<mailto:[email protected]>" }
}
Reference

Error codes.

CodeHTTPMeaningTip
unauthorized401Missing, malformed or revoked API key.Generate a fresh key from Settings → API.
forbidden403Key is valid but lacks the scope for this resource.Add the right scope to the key or use a workspace-admin key.
not_found404The resource does not exist or is in a different workspace.Confirm the ID and that the key belongs to the same workspace.
rate_limited429Per-key request budget exceeded for this window.Honour the Retry-After header — back off, don't hammer.
insufficient_credits402Not enough credits on the workspace to complete this call.Top up from Settings → Billing or wait for monthly reset.
validation_error422A field failed validation. See the issues[] payload.Inspect issues[].path and issues[].message for the failing field.
provider_down503An upstream data partner is temporarily unavailable.The waterfall is resilient — retry the request, we will route to a healthy partner.
internal_error500Something broke on our side.Email [email protected] with the request_id from the error body.
SDKs — Coming soon

Official libraries.

Not yet published
Official SDKs (Node, Python, Go, Ruby, PHP) are not yet released. Use any HTTP client (axios, fetch, requests, net/http) directly — the API is straightforward Bearer auth + JSON. An OpenAPI 3.0 spec for client generation is on the roadmap. Get in touch if you need a specific SDK prioritized.

Today, the recommended path is :

  • Node / TS: axios.post('/api/search/find', body, { headers: { Authorization: `Bearer ${key}` } })
  • Python: requests.post(url, json=body, headers={'Authorization': f'Bearer {key}'})
  • Other: any HTTP lib that does Bearer auth + JSON.

Stuck on something?

Our DX team answers technical questions directly. Average first reply: under 1 hour during the workweek.

Netounim — B2B prospecting in one workspace