Skip to Content
API referenceSourcesAvailable

API Reference

Reference for the Vendo web tracking ingestion API — the /collect endpoint, event schema, authentication, and error codes.

Last reviewed July 25, 2026

The ingestion API receives events from the SDK and writes them to BigQuery. It runs as a Node.js Express service. Vendo’s managed deployment runs on Hetzner behind a Caddy reverse proxy; the public endpoints are https://track.vendodata.com (production) and https://track-staging.vendodata.com (staging).

Base URL

The API runs on the host configured for your workspace (e.g., https://track.vendodata.com, or your own domain when self-hosting). All endpoints are relative to this base.

Endpoints

MethodPathDescription
POST/collectReceive and process events
POST/admin/simulateValidate, normalize, and preview a payload without writes
GET/healthHealth check
GET/v1/sdk.jsServe the JavaScript SDK
GET/v1/snippetGenerate a pre-filled HTML snippet

POST /collect

The primary endpoint. Accepts a single event or a batch of events.

Authentication

Include the write key in one of these locations (checked in order):

MethodExample
X-Write-Key headerX-Write-Key: your-write-key
Authorization headerAuthorization: Bearer your-write-key
Body field{ "writeKey": "your-write-key", ... }

The SDK sends the write key in the request body automatically.

Request: Single Event

{ "writeKey": "your-write-key", "type": "track", "messageId": "550e8400-e29b-41d4-a716-446655440000", "timestamp": "2026-02-08T12:00:00.000Z", "anonymousId": "anon-abc-123", "event": "Product Viewed", "properties": { "sku": "SKU-1", "price": 29.99 }, "context": { "source": "web", "session": { "id": "session-123" }, "page": { "url": "https://example.com/products/sku-1" }, "campaign": { "utm_source": "google" }, "clickId": { "gclid": "..." } } }

Request: Batch

{ "writeKey": "your-write-key", "sentAt": "2026-02-08T12:00:05.000Z", "batch": [ { "type": "page", "messageId": "msg-1", "timestamp": "2026-02-08T12:00:00.000Z", "anonymousId": "anon-abc-123", "properties": { "page_url": "https://example.com/pricing" } }, { "type": "track", "messageId": "msg-2", "timestamp": "2026-02-08T12:00:03.000Z", "anonymousId": "anon-abc-123", "event": "Button Clicked", "properties": { "label": "signup" } } ] }

Maximum batch size: 100 events per request. This is the server-side hard limit — a request with more than 100 events is rejected with 413 batch_too_large. It is independent of, and larger than, the SDK’s default batchSize of 20 events per batch; the SDK flushes well below the API limit, and you can raise its batchSize up to 100.

Response: Success

{ "ok": true, "received": 2, "inserted": { "events": { "bigquery": { "inserted": 2, "errors": 0 } } }, "invalid": 0, "errors": [] }

Response: Partial Success

If some events are invalid, valid events are still processed:

{ "ok": true, "received": 1, "inserted": { ... }, "invalid": 1, "errors": [ { "index": 1, "errors": [{ "message": "must have required property 'type'" }] } ] }

Event Schema

Required Fields (All Events)

FieldTypeDescription
typestringEvent type: track, identify, page, group, or alias
messageIdstringUnique event ID (UUID recommended)
timestampstringISO 8601 timestamp
anonymousIdstringAnonymous visitor ID

Conditional Fields

Event TypeRequired Fields
trackevent (event name)
identifyuserId
groupgroupId
aliasuserId, previousId
pageNone additional

Optional Fields

FieldTypeDescription
userIdstringKnown user ID (set after identify)
sessionIdstringSession identifier
writeKeystringWrite key (alternative to header auth)
propertiesobjectEvent properties (for track and page)
traitsobjectUser/group traits (for identify and group)
contextobjectShared metadata envelope such as page, session, campaign, click ID, device, locale, and consent
sentAtstringClient-side send timestamp (for clock correction)

The context Envelope

Every event carries a structured context object. The SDK populates it automatically; you can merge additional keys via the context argument on track(), page(), etc. The API normalizes these keys into typed warehouse columns.

KeyTypeDescription
sourcestringAlways "web" for the browser SDK.
libraryobjectSDK identity: { name: "vendo-web-tracking", version }.
sessionobject{ id } — the current session ID.
pageobject{ url, path, title, referrer } for the current page.
screenobject{ width, height } in pixels.
campaignobjectUTM parameters captured from the URL (last-touch), e.g. { utm_source, utm_medium, … }.
clickIdobjectAdvertising click IDs captured from the URL (last-touch), e.g. { gclid, fbclid, … }.
consentobjectConsent groups granted/denied at send time, e.g. { analytics: true, marketing: false }.
browserstringBrowser name.
osstringOperating system.
devicestringDevice / platform.
languagestringBrowser language (e.g. "en-US").
localestringBrowser locale.
userAgentstringFull user-agent string.

The key names are camelCase (campaign, clickId, userAgent, consent) as emitted by the SDK.

Table Routing

Event TypeBigQuery Table
trackevents
pageevents
identifyusers
groupgroups
aliasaliases

Server-Added Fields

The ingestion API enriches each event with:

FieldDescription
received_atServer timestamp when the event was received
ipClient IP (from X-Forwarded-For or request IP)
user_agentClient user agent string

POST /admin/simulate

Dry-run an event payload through validation, normalization, and destination preview without writing rows or sending events to downstream destinations.

This endpoint is intended for demos, setup flows, and internal support tooling. It only works when DEMO_MODE=true, ADMIN_SETUP_ENABLED is enabled, and a matching ADMIN_SETUP_TOKEN is sent in the X-Admin-Token header.

curl -X POST https://track.yourdomain.com/admin/simulate \ -H "Content-Type: application/json" \ -H "X-Admin-Token: $ADMIN_SETUP_TOKEN" \ -d '{ "writeKey": "YOUR_WRITE_KEY", "batch": [ { "type": "track", "messageId": "debug-1", "timestamp": "2026-04-30T00:00:00.000Z", "anonymousId": "anon-123", "event": "Product Viewed", "properties": { "sku": "SKU-1" }, "context": { "page": { "url": "https://example.com/products/sku-1" }, "campaign": { "utm_source": "google" } } } ] }'

The response includes:

FieldDescription
simulatedAlways true for this endpoint
rowsNormalized event/user/group/alias rows that would be written
destinationsPreview of rows after destination mapping and consent filtering
warningsDry-run caveats, including skipped writes, rules, schema discovery, and live events
merchantResolved tenant details when a write key maps to a tenant

No BigQuery inserts, live event publishes, schema discovery, rules engine execution, or destination sends happen during simulation.


Error Responses

StatusError CodeDescription
400invalid_payloadRequest body is not valid JSON
400invalid_batchBatch array failed schema validation
400no_valid_eventsNo events in the batch passed validation
401missing_write_keyNo write key found in header or body
401invalid_write_keyWrite key does not match
413batch_too_largeBatch exceeds 100 events
500server_errorInternal server error

All error responses follow this format:

{ "ok": false, "error": "error_code", "details": [] }

GET /health

Returns { "ok": true }. Use for load balancer health checks.

GET /v1/sdk.js

Serves the compiled JavaScript SDK as application/javascript with a 1-hour cache header. The SDK file is loaded into memory at API startup.

Note: The SDK is also available via Vendo’s CDN at https://cdn.vendodata.com/sdk/v1/vendo.js. The CDN-hosted version is identical to the one served by the ingestion API. Use the CDN for the fastest setup, or serve from your own domain for maximum first-party control.

Set the SDK_JS_PATH environment variable to override the default SDK file location.

GET /v1/snippet

Returns a ready-to-paste HTML <script> block with the write key and host pre-filled.

Query ParamDefaultDescription
writeKeyYOUR_WRITE_KEYWrite key to embed in the snippet
hostAuto-detected from requestTracking host URL

Example: GET /v1/snippet?writeKey=my-key


Authentication Modes

The API has exactly two auth modes:

ModeConfigUse Case
StaticWRITE_KEY env varSingle-tenant, local dev, demos
SupabaseSUPABASE_AUTH_ENABLED=trueMulti-tenant (production deployment)

In static mode, the write key in the request must match the WRITE_KEY environment variable.

In Supabase mode, the write key is resolved through Supabase — the sole write-key control plane. The resolved tenant record carries the merchant’s destination configuration (Mixpanel, Segment, Customer.io, OneSignal, BigQuery). The former firebase and dual modes were retired; Firestore is no longer consulted.


Rate Limits

The API does not enforce rate limits by default. For production deployments, configure rate limiting at the load balancer or CDN level. Recommended limits:

ScopeLimit
Per IP100 requests/second
Per write key1000 events/second
Request body2 MB max

Need help?

Include your workspace, integration or job ID, and the first error message when you contact support.

support@vendodata.com
Last updated on