REST API
The Evident REST API lets you enrich IP addresses and stream visitor events from any HTTP client — a Next.js middleware, a Python script, a Segment webhook, or an existing server. Everything the dashboard does with CSV uploads and Amplitude syncs, you can do over the API.
Base URL: https://api.useevident.com
Authentication
Every request authenticates with an API key sent in the X-API-Key header.
X-API-Key: ipid_<64-hex-chars>Generate keys in Settings → API Keys in the dashboard. The raw key is shown exactly once at creation — copy it into your secret store immediately. Evident stores only a SHA-256 hash and cannot recover the raw value later; if you lose it, generate a new key.
Never put the raw key in client-side code, environment variables prefixed NEXT_PUBLIC_* / REACT_APP_*, or a Git repo. Keys are per-tenant and grant full ingestion + lookup access; treat them like a database password.
Endpoints
The API is split into two families:
- Lookup (
/v1/lookup,/v1/bulk) — real-time IP enrichment. Fast, no persistence. Use when you need a company/industry for an IP but don’t want it counted as a visit. - Ingest (
/v1/ingest,/v1/ingest/batch) — event ingestion. Enriches and persists to your Accounts dashboard. Use when the IP represents a real visitor.
POST /v1/lookup
Enrich a single IP address. Does not persist the result.
Request
curl -X POST https://api.useevident.com/v1/lookup \
-H "X-API-Key: $EVIDENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ip": "203.0.113.42"}'Optional fields (all fields accepted as query params too):
| Field | Description |
|---|---|
fields |
Array of enrichment fields to include (business_name, hostname, industry, …). Default: all. |
redact |
Array of fields to strip from the response. Use geo to remove all location fields. |
providers.allow / providers.deny |
Whitelist or blacklist enrichment providers. |
deadline_ms |
Overall lookup budget (ms). |
quality_min |
Return status: "insufficient_quality" if the result’s quality score is below this. |
Response (200 OK)
{
"ip": "203.0.113.42",
"tenant_id": "clerk_abc123",
"quality": 3,
"status": "ok",
"data": {
"business_name": "Acme Corp",
"org_name": "Acme Corporation Ltd",
"hostname": "office.acmecorp.com",
"etld_plus_one": "acmecorp.com",
"industry": "Software",
"description": "..."
},
"quota": {
"daily_limit": 10000,
"daily_used": 42,
"daily_remaining": 9958,
"reset_at": "2026-07-03T00:00:00+00:00"
}
}POST /v1/bulk
Enrich up to 512 IPs in a single call. Same options as /v1/lookup.
Request
curl -X POST 'https://api.useevident.com/v1/bulk?mode=sync' \
-H "X-API-Key: $EVIDENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"ips": ["203.0.113.10", "203.0.113.11", "203.0.113.12"]}'Modes:
mode=sync(default) — streams NDJSON, one enriched result per line. Right for scripts that want results back inline.mode=async— accepts the batch and returns202 Acceptedwith ajob_id. PollGET /v1/bulk/{job_id}to retrieve results. Right for larger batches or when you don’t want to hold an HTTP connection open.
Async response (202 Accepted)
{ "job_id": "9f4e...", "status": "queued" }POST /v1/ingest
Persist up to 100 events synchronously. Each event is enriched inline and its identified company shows up on the Accounts dashboard within seconds.
Request
curl -X POST https://api.useevident.com/v1/ingest \
-H "X-API-Key: $EVIDENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"events": [
{
"ip": "203.0.113.42",
"event_id": "evt_abc123",
"event_type": "page_view",
"timestamp": "2026-07-02T14:30:00Z",
"properties": { "path": "/pricing", "referer": "https://twitter.com" }
}
]
}'Event envelope (events[] — each element):
| Field | Required | Description |
|---|---|---|
ip |
Yes | IPv4 or IPv6 address. |
event_id |
No | Idempotency key. Server generates a UUID if omitted. Re-sending the same (tenant_id, event_id) within 7 days is a no-op. |
timestamp |
No | RFC 3339 datetime. Defaults to receipt time. |
event_type |
No | Free-form label (e.g. page_view, signup_started). |
account_id |
No | Your identifier for the account/user. Otherwise inferred from the IP. |
session_id |
No | Integer session identifier. |
platform |
No | web / ios / android / etc. |
properties |
No | Arbitrary JSON. Preserved in the object-storage archive (see Raw payload archive). |
user_properties |
No | Arbitrary JSON. Preserved as above. |
Additional keys not listed are also preserved — Evident does not reject unknown fields.
Response (200 OK)
{
"stats": {
"received": 1,
"accepted": 1,
"rejected": 0,
"unique_ips": 1
},
"results": [
{
"event_id": "evt_abc123",
"ip": "203.0.113.42",
"status": "ok",
"enrichment": {
"business_name": "Acme Corp",
"org_name": "Acme Corporation Ltd",
"hostname": "office.acmecorp.com",
"industry": "Software",
"industry_category": "Technology",
"classification": "corporate"
},
"quality": 3
}
]
}results[] has one entry per input event, in order. status values:
ok— event persisted, enrichment resolved. Renders on the Accounts page.filtered— IP identified as infrastructure noise (cloud provider, CDN, ISP, hosting). Not persisted to Accounts, but returned here so you can inline-gate UX (e.g. drop a chat-widget prompt for suspected bots).invalid_ip— theipfield failed IPv4/IPv6 parsing.
Limits: 100 events per request, 1 MB request body. Larger batches must use /v1/ingest/batch.
POST /v1/ingest/batch
Ingest a large NDJSON batch asynchronously. Right for backfills and any workload above the sync endpoint’s cap.
Request (send NDJSON — one event per line — as the request body)
curl -X POST https://api.useevident.com/v1/ingest/batch \
-H "X-API-Key: $EVIDENT_API_KEY" \
-H "Content-Type: application/x-ndjson" \
--data-binary @events-30d.ndjsonResponse (202 Accepted)
{
"job_id": "9f4e...",
"status": "queued",
"received": 42315
}Limits: 1,000,000 events per request, 100 MB body. The endpoint returns as soon as the job is queued; poll the status endpoint for progress.
GET /v1/ingest/batch/{job_id}
Poll the status of a queued batch ingestion job.
Request
curl https://api.useevident.com/v1/ingest/batch/9f4e... \
-H "X-API-Key: $EVIDENT_API_KEY"Response (200 OK)
{
"job_id": "9f4e...",
"status": "running",
"progress_pct": 42,
"progress_current": 17832,
"progress_total": 42315,
"progress_message": "Enriching batch...",
"result_summary": null
}status progresses queued → running → completed (or failed). When completed, result_summary includes final counts (processed, valid_ips, identified_ips).
When to use which endpoint
| You want to… | Use |
|---|---|
| Look up a single IP without persisting | POST /v1/lookup |
| Look up many IPs inline for a script | POST /v1/bulk?mode=sync |
| Look up many IPs and pick up results later | POST /v1/bulk?mode=async + GET /v1/bulk/{job_id} |
| Track live visitors on your site or app | POST /v1/ingest from your web middleware |
| Backfill historical visitors from access logs | POST /v1/ingest/batch |
| Check on a queued batch | GET /v1/ingest/batch/{job_id} |
Rate limits
Rate limits are enforced per tenant via a token-bucket algorithm.
| Plan | Steady-state | Burst |
|---|---|---|
| Starter | 5 req/sec | 20 |
| Growth | 15 req/sec | 60 |
| Professional | 25 req/sec | 100 |
| Custom | 100 req/sec | 500 |
When you exceed the limit, the API returns 429 Too Many Requests. Back off and retry.
Quotas
Your tracked accounts quota (visible in Settings → Quotas) counts unique identified companies per month. Duplicate visits from the same IP on the same day are deduplicated automatically — a customer who hits your site 500 times counts as one billable IP.
Cloud provider / CDN / ISP / hosting IPs are classified and excluded from your quota automatically. You’ll see them in the results[] array with status: "filtered", but they don’t count against your allowance.
Error responses
Errors follow FastAPI conventions — a detail string in the response body, with the HTTP status code carrying the semantic.
| Code | Meaning | Common causes |
|---|---|---|
400 Bad Request |
Malformed request | Empty events array, whitespace-only batch body |
401 Unauthorized |
Invalid or missing API key | Missing X-API-Key, key was revoked, key doesn’t exist |
413 Payload Too Large |
Body or event count over the endpoint’s cap | Over 100 events on sync, over 1 MB body on sync, over 100 MB batch body |
422 Unprocessable Entity |
Schema mismatch | Missing required field (e.g. ip), wrong data type |
429 Too Many Requests |
Rate limit exceeded | Traffic burst above the plan’s burst cap; queue depth exceeded on batch |
500 Internal Server Error |
Server error | Unexpected pipeline failure. Retry with exponential backoff; if persistent, contact support |
Idempotency
The ingest endpoints deduplicate by (tenant_id, event_id) for 7 days. If you re-send the same event within that window — say, from a retried Next.js middleware invocation — the second call is a no-op and the response still returns status: "ok" for the event.
If you rely on server-generated event IDs (omit event_id), every call is treated as a distinct event.
Raw payload archive
If your plan or configuration enables raw event archival, incoming /v1/ingest and /v1/ingest/batch payloads are archived to object storage as NDJSON, partitioned by tenant, day, source type, and run ID. This preserves properties, user_properties, and any other fields Evident’s pipeline doesn’t natively query, so you can replay historical batches or audit exact payloads later.
Contact support@useevident.com if you need archive access for your workspace.
SDKs
Official SDKs are on the roadmap — Python and Node clients that wrap this API with typed models, retries, and idempotency helpers. In the meantime, the raw endpoints are stable and safe to build against.