Verinode Developer
API reference
Every endpoint, grouped by area. The machine-readable contract is at /openapi.
Meta
Connectivity and key introspection. Server-side only: the API sends no CORS headers, so a browser fetch cannot call it. Every authenticated response carries RateLimit-Limit / -Remaining / -Reset; 401, 403, and the pre-auth per-IP 429 carry none. Every response carries Verinode-Request-Id.
#GET/v1/ping
Authenticates the key and echoes its resolved identity, the fastest way to confirm setup. Requires no scope, so it is also the right call to isolate a 403.
Responses
200 | The key is valid; returns operator_id, mode, scopes, sandbox. | |
401 | Missing or malformed key. | |
curl -H "Authorization: Bearer $KEY" https://api.verinode.ai/v1/ping
#GET/v1/api-keys
Returns key metadata for the operator this key resolves to. Secrets are never returned, only the prefix and status. Note the boundary: a test key resolves to the sandbox operator, so it lists only sandbox keys, never your live ones. Minting stays in the dashboard (admin-only).
Responses
200 | { data: [ApiKey] } | |
403 | Missing the keys scope. | |
curl -H "Authorization: Bearer $KEY" https://api.verinode.ai/v1/api-keys
Documents
Push documents through the extraction pipeline and poll results.
#POST/v1/documents
Uploads a document for extraction. Asynchronous: returns 202 with an ingestion id. Send multipart form-data with a file, or JSON with filename + base64 content_base64. 300/hour; test keys are additionally capped at 25 extractions/day.
Parameters
file | binary · body | Multipart file part (or use the JSON lane below). |
filenamerequired | string · body | REQUIRED on the JSON lane (omit it and the call 400s). Carried by the file part on multipart. |
content_base64 | string · body | Base64 file bytes (JSON alternative to multipart). Requires filename. |
content_type | string · body | Optional; inferred from the filename extension when omitted. |
Idempotency-Key | string · header | Honored here. Makes a retry safe: replays the first response instead of charging and extracting twice. |
Responses
202 | Accepted for extraction; returns { id, status }. IUs are already spent at this point. | |
400 | Missing file, or JSON body missing filename / content_base64. | |
402 | Intelligence Units exhausted; nothing was queued. | |
413 | Over the 25 MB cap. | |
415 | Unsupported content type. | |
429 | Over 300/hour, or over the test-key 25/day cap (sandbox_quota). | |
curl -X POST https://api.verinode.ai/v1/documents \
-H "Authorization: Bearer $KEY" -H "Idempotency-Key: $(uuidgen)" \
-F file=@invoice.pdf
#GET/v1/ingestions/{id}
Returns status and, once complete, the humanized extraction receipt (what was detected, saved, and normalized). Poll while status is `processing`, the only non-terminal state. `canceled` means the operator aborted it in the dashboard and it will never be processed: stop polling and re-send if you still need it.
Parameters
idrequired | uuid · path | The ingestion id. |
Responses
200 | { status: processing | completed | failed | canceled, receipt, error }. | |
404 | No ingestion with that id. | |
curl -H "Authorization: Bearer $KEY" https://api.verinode.ai/v1/ingestions/<id>
Events
Import activity events to feed process mining. Free (no Intelligence Units).
#POST/v1/events
Records one activity event onto Verinode's process model. Case resolves by job_id or claim_number. Idempotency-Key is IGNORED here: dedupe on external_id instead. 5,000/hour.
Parameters
activityrequired | string · body | What happened (e.g. job_completed). |
occurred_atrequired | date-time · body | When it happened (ISO 8601 UTC). Not in the future, not over 10 years old. |
case | object · body | Optional. { job_id } or { claim_number }; omit or null to record an event unattached to a job. |
domain | string · body | One of vendor, field, client, billing, team. Defaults to field. |
external_id | string · body | Your stable id. Re-sending it is a no-op (duplicate). WITHOUT it, a retry writes the event AGAIN. |
Responses
201 | Recorded; returns { id, status: created }. | |
200 | Already seen (external_id match); returns { id: null, status: duplicate }. | |
400 | Validation failed (activity, occurred_at, domain, attributes). | |
422 | Case reference could not resolve, or matched more than one job. | |
curl -X POST https://api.verinode.ai/v1/events \
-H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{ "activity":"job_completed","occurred_at":"2026-07-01T16:30:00Z","case":{"job_id":"…"} }'
#POST/v1/events/batch
Bulk-import events. Returns a per-item result envelope so partial failures are visible. Always inspect it: a 200 does not mean all 1,000 landed. Idempotency-Key is IGNORED: give every event an external_id, or a retried batch double-writes each one that lacks it. 500 calls/hour.
Parameters
eventsrequired | array · body | Array of event objects (max 1,000). |
Responses
200 | Per-item results { accepted, rejected, results }. Each result carries an id (null when duplicate) or an error. | |
413 | More than 1,000 events in the batch. Split the backfill. | |
429 | Over 500 batch calls this hour. | |
curl -X POST https://api.verinode.ai/v1/events/batch \
-H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{ "events": [ … ] }'
Records
Create and read structured records directly.
#POST/v1/jobs
Create a job without a source document. Fields pass through the schema validator; operator_id, id, source, and timestamps are server-owned. Returns the created object, serialized exactly as its list row. 300/hour.
Parameters
Idempotency-Key | string · header | Honored here. Replays the first response instead of creating a second record. Reusing a key with a different body returns 422. |
Responses
201 | The created job. | |
400 | Validation failed. | |
422 | Idempotency-Key reused with a different body. | |
curl -X POST https://api.verinode.ai/v1/jobs \
-H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{ "client_name":"Riverside Restoration","rcv_total":"18450.00" }'
#GET/v1/jobs
Cursor-paginated over (updated_at, id). Each enum value ships a humanized _label; money as decimal strings. Sync is at-least-once, so upsert by id. 600/hour.
Parameters
limit | integer · query | 1–100, default 25. |
cursor | string · query | From a previous next_cursor. Pass it back verbatim; never construct one. |
updated_since | date-time · query | Inclusive. Pass back the synced_through you stored to fetch only what changed. |
created_after | date-time · query | Exclusive, on created_at. New records only. Never shows an edit to a record you already have. |
Responses
200 | { data, next_cursor, has_more, synced_through }. synced_through is non-null only on the last page. | |
400 | Malformed cursor or timestamp. | |
curl -H "Authorization: Bearer $KEY" \
"https://api.verinode.ai/v1/jobs?limit=100&updated_since=2026-07-17T09:30:00Z"
#GET/v1/jobs/{id}
One job, serialized identically to its list row. A record that does not exist and one owned by another operator return the same 404, deliberately. 1,000/hour.
Parameters
idrequired | uuid · path | The job id. |
Responses
200 | The job. | |
404 | No job with that id for this operator. | |
curl -H "Authorization: Bearer $KEY" https://api.verinode.ai/v1/jobs/<id>
#POST/v1/clients
Create a client; PII fields are encrypted at rest on write. Returns the created object. Free. 300/hour.
Parameters
Idempotency-Key | string · header | Honored here. Replays the first response instead of creating a second record. |
Responses
201 | The created client. | |
400 | Validation failed. | |
422 | Idempotency-Key reused with a different body. | |
curl -X POST https://api.verinode.ai/v1/clients \
-H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{ "name":"Acme Property Management","contact_email":"dana@acmepm.example" }'
#GET/v1/clients
Cursor-paginated, contact fields decrypted. Same sync semantics as jobs. 600/hour.
Parameters
limit | integer · query | 1–100, default 25. |
cursor | string · query | Page cursor. |
updated_since | date-time · query | Inclusive. Only what changed since. |
created_after | date-time · query | Exclusive, on created_at. |
Responses
200 | { data, next_cursor, has_more, synced_through }. | |
curl -H "Authorization: Bearer $KEY" "https://api.verinode.ai/v1/clients"
#GET/v1/clients/{id}
One client, serialized identically to its list row. 1,000/hour.
Parameters
idrequired | uuid · path | The client id. |
Responses
200 | The client. | |
404 | No client with that id for this operator. | |
curl -H "Authorization: Bearer $KEY" https://api.verinode.ai/v1/clients/<id>
#GET/v1/vendors
Cursor-paginated. Read-only in v1. Your own vendor records only, nothing catalog- or peer-derived. 600/hour.
Parameters
limit | integer · query | 1–100, default 25. |
cursor | string · query | Page cursor. |
updated_since | date-time · query | Inclusive. Only what changed since. |
created_after | date-time · query | Exclusive, on created_at. |
Responses
200 | { data, next_cursor, has_more, synced_through }. | |
curl -H "Authorization: Bearer $KEY" "https://api.verinode.ai/v1/vendors"
#GET/v1/vendors/{id}
One vendor, serialized identically to its list row. 1,000/hour.
Parameters
idrequired | uuid · path | The vendor id. |
Responses
200 | The vendor. | |
404 | No vendor with that id for this operator. | |
curl -H "Authorization: Bearer $KEY" https://api.verinode.ai/v1/vendors/<id>
Export
Export your own data asynchronously.
#POST/v1/exports
Kick off an async full-account export. Omit resources for everything (jobs, vendors, clients). One at a time, 4 per rolling 24h, 20 calls/hour. Idempotency-Key is IGNORED here: the 409 is what makes a retry safe.
Parameters
resources | string[] · body | Resource names, or omit for all. |
Responses
202 | { id, status: processing }. | |
400 | Unknown or empty resource list. | |
409 | An export is already in progress (export_in_progress). | |
429 | Past 4 exports in 24h (export_quota). | |
curl -X POST https://api.verinode.ai/v1/exports \
-H "Authorization: Bearer $KEY" -d '{}'
#GET/v1/exports/{id}
Poll status, row counts, and the download URL. `processing` is the only non-terminal state (queued and running both read as processing, so this always agrees with the create response); it resolves to completed, failed, or expired.
Parameters
idrequired | uuid · path | The export id. |
Responses
200 | { status, resource_counts, download_url }. | |
curl -H "Authorization: Bearer $KEY" https://api.verinode.ai/v1/exports/<id>
#GET/v1/exports/{id}/download
Streams the decrypted JSON bundle. Available while completed and unexpired (7-day TTL).
Parameters
idrequired | uuid · path | The export id. |
Responses
200 | The export artifact (application/json). | |
410 | Expired. | |
curl -H "Authorization: Bearer $KEY" https://api.verinode.ai/v1/exports/<id>/download -o export.json
Webhooks
Register endpoints to receive events in real time. Five event types fire today: job.created, client.created, ingestion.completed, ingestion.failed, export.completed. Ingestion events fire ONLY for documents pushed through POST /v1/documents. A document the operator emailed or uploaded themselves never reaches your endpoint.
#POST/v1/webhook-endpoints
Register an HTTPS endpoint. The signing secret is returned once. Only public HTTPS URLs are accepted (re-validated at every delivery). Max 10 endpoints per operator.
Parameters
urlrequired | string · body | Your HTTPS endpoint. Must resolve to a public address. |
events | string[] · body | Event types to receive, or ["*"] for all five. |
Idempotency-Key | string · header | Honored here. |
Responses
201 | { id, secret, status }. | |
400 | URL not public HTTPS. | |
409 | Already at 10 endpoints (too_many_endpoints). | |
curl -X POST https://api.verinode.ai/v1/webhook-endpoints \
-H "Authorization: Bearer $KEY" -H "content-type: application/json" \
-d '{ "url":"https://example.com/hooks","events":["job.created"] }'
#GET/v1/webhook-endpoints
Lists your registered endpoints (secrets excluded).
Responses
200 | { data: [Endpoint] }. | |
curl -H "Authorization: Bearer $KEY" https://api.verinode.ai/v1/webhook-endpoints
#DELETE/v1/webhook-endpoints/{id}
Removes an endpoint; deliveries stop immediately.
Parameters
idrequired | uuid · path | The endpoint id. |
Responses
200 | Deleted. | |
404 | No such endpoint. | |
curl -X DELETE https://api.verinode.ai/v1/webhook-endpoints/<id> \
-H "Authorization: Bearer $KEY"
#GET/v1/outbound-events
Can't run a listener? Poll the recent outbound events for your operator.
Parameters
limit | integer · query | 1–100. |
cursor | string · query | Page cursor. |
Responses
200 | { data, next_cursor, has_more }. | |
curl -H "Authorization: Bearer $KEY" "https://api.verinode.ai/v1/outbound-events"
Network (HQ)
Read the network layer with a group key (vn_group_live_…). Aggregates and compliance only, never a member's raw data. Read-only, 600/hour per endpoint. The keys:manage scope is selectable at mint time but required by no endpoint: it is reserved and grants nothing today. See the Network API (HQ) overview.
#GET/v1/network/ping
Authenticates the group key and echoes the network it resolves to. The fastest way to confirm HQ setup.
Responses
200 | { ok, group_id, tier, scopes }. | |
401 | Missing or malformed key. | |
curl -H "Authorization: Bearer $KEY" https://api.verinode.ai/v1/network/ping
#GET/v1/network/summary
The group's latest network snapshot: membership counts, compliance rate, reputation and action-rate composites, margin percentiles. Sensitive metrics are suppressed (null) until a cohort is large enough to protect any single member; a k_anon block reports which thresholds were met.
Responses
200 | { data: NetworkSummary | null }. | |
403 | Missing the network:read scope. | |
curl -H "Authorization: Bearer $KEY" https://api.verinode.ai/v1/network/summary
#GET/v1/network/summary/history
The network-health snapshot series, most recent first, each row k-anon-gated identically to the latest snapshot. The time-series for your own warehouse. Cursor is a snapshot_date.
Parameters
limit | integer · query | 1–200 (default 50). |
cursor | string · query | snapshot_date to page before. |
Responses
200 | { data: NetworkSummary[], next_cursor, has_more }. | |
curl -H "Authorization: Bearer $KEY" "https://api.verinode.ai/v1/network/summary/history?limit=90"
#GET/v1/network/members
The group's own member roster, cursor-paginated: location, owner, city, state, membership status, open and invite dates. Never a member's raw business data, contact details, or operator linkage.
Parameters
limit | integer · query | 1–200 (default 50). |
cursor | string · query | Page cursor. |
Responses
200 | { data, next_cursor, has_more }. | |
curl -H "Authorization: Bearer $KEY" "https://api.verinode.ai/v1/network/members?limit=50"
#GET/v1/network/compliance
Each member's certification and compliance posture, by name: cert type, status, expiration, carrier-program eligibility. Cursor-paginated. Roster and status only.
Parameters
limit | integer · query | 1–200 (default 50). |
cursor | string · query | Page cursor. |
Responses
200 | { data, next_cursor, has_more }. | |
curl -H "Authorization: Bearer $KEY" "https://api.verinode.ai/v1/network/compliance?limit=50"
#GET/v1/network/signals
The network pattern layer: how many members hit each signal (e.g. 12 of 30 on vendor cost drift), by severity and domain. Counts only, never member attribution. Every token ships a humanized _label. Cursor-paginated.
Parameters
limit | integer · query | 1–200 (default 50). |
cursor | string · query | Page cursor. |
Responses
200 | { data, next_cursor, has_more }. | |
curl -H "Authorization: Bearer $KEY" "https://api.verinode.ai/v1/network/signals?limit=50"
#GET/v1/network/programs
Adoption rates across the network by program (carrier / TPA / vendor): active vs adopted members and adoption percentage. Rates only, never which member declined. Cursor-paginated.
Parameters
limit | integer · query | 1–200 (default 50). |
cursor | string · query | Page cursor. |
Responses
200 | { data, next_cursor, has_more }. | |
curl -H "Authorization: Bearer $KEY" "https://api.verinode.ai/v1/network/programs?limit=50"
#GET/v1/network/process
Network-wide process-mining medians (jobs, recruiting, safety, supplements): stage-to-stage median / p25 / p75 days and sample size. The cross-network industry benchmark columns are stripped (the moat). Cursor-paginated.
Parameters
limit | integer · query | 1–200 (default 50). |
cursor | string · query | Page cursor. |
Responses
200 | { data, next_cursor, has_more }. | |
curl -H "Authorization: Bearer $KEY" "https://api.verinode.ai/v1/network/process?limit=50"
#GET/v1/network/export
Assembles the snapshot, full roster, and full compliance posture into one JSON bundle, using the same serialization as the individual endpoints. Synchronous (network data is bounded).
Responses
200 | { data: { manifest, resources: { summary, members, compliance } } }. | |
curl -H "Authorization: Bearer $KEY" https://api.verinode.ai/v1/network/export