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.
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
Make your first call in 60 seconds.
- 1Sign in and generate an API key at Settings → API. Treat it like a password.
- 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). - 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"
}'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 separatetm_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.
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.
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" }
]
}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"
}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]" }'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"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"
}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..."]
}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" }
]
}
}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"),
);
}event.id at the destination.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.
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]>" }
}Error codes.
| Code | HTTP | Meaning | Tip |
|---|---|---|---|
| unauthorized | 401 | Missing, malformed or revoked API key. | Generate a fresh key from Settings → API. |
| forbidden | 403 | Key is valid but lacks the scope for this resource. | Add the right scope to the key or use a workspace-admin key. |
| not_found | 404 | The resource does not exist or is in a different workspace. | Confirm the ID and that the key belongs to the same workspace. |
| rate_limited | 429 | Per-key request budget exceeded for this window. | Honour the Retry-After header — back off, don't hammer. |
| insufficient_credits | 402 | Not enough credits on the workspace to complete this call. | Top up from Settings → Billing or wait for monthly reset. |
| validation_error | 422 | A field failed validation. See the issues[] payload. | Inspect issues[].path and issues[].message for the failing field. |
| provider_down | 503 | An upstream data partner is temporarily unavailable. | The waterfall is resilient — retry the request, we will route to a healthy partner. |
| internal_error | 500 | Something broke on our side. | Email [email protected] with the request_id from the error body. |
Official libraries.
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.
What's changed lately.
The API is currently in pre-release; expect occasional shape changes ahead of the formal v1 freeze. Non-breaking additions are announced in our changelog.
- 2026-05-26ADDED
POST /api/leads/:id/enrich-companyfor Apollo- sourced company enrichment on existing leads. - 2026-05-22ADDEDNew webhook event
EMAIL_REPLIEDfires when an incoming message matches an active campaign. - 2026-05-15IMPROVEDPhone Waterfall now exposes per-provider attempts trace in the response for debugging cold lookups.
- 2026-05-01ADDED
POST /api/phone/bulkasync batch enrichment (up to 1,000 lead IDs per call).
Stuck on something?
Our DX team answers technical questions directly. Average first reply: under 1 hour during the workweek.