API Reference
The ingestion API receives events from the SDK and writes them to BigQuery. It runs as a Node.js Express service, typically deployed on Cloud Run.
Base URL
The API runs on the host you configure (e.g., https://track.yourdomain.com). All endpoints are relative to this base.
Endpoints
| Method | Path | Description |
|---|---|---|
POST | /collect | Receive and process events |
GET | /health | Health check |
GET | /v1/sdk.js | Serve the JavaScript SDK |
GET | /v1/snippet | Generate 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):
| Method | Example |
|---|---|
X-Write-Key header | X-Write-Key: your-write-key |
Authorization header | Authorization: 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
}
}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.
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)
| Field | Type | Description |
|---|---|---|
type | string | Event type: track, identify, page, group, or alias |
messageId | string | Unique event ID (UUID recommended) |
timestamp | string | ISO 8601 timestamp |
anonymousId | string | Anonymous visitor ID |
Conditional Fields
| Event Type | Required Fields |
|---|---|
track | event (event name) |
identify | userId |
group | groupId |
alias | userId, previousId |
page | None additional |
Optional Fields
| Field | Type | Description |
|---|---|---|
userId | string | Known user ID (set after identify) |
sessionId | string | Session identifier |
writeKey | string | Write key (alternative to header auth) |
properties | object | Event properties (for track and page) |
traits | object | User/group traits (for identify and group) |
sentAt | string | Client-side send timestamp (for clock correction) |
Table Routing
| Event Type | BigQuery Table |
|---|---|
track | events |
page | events |
identify | users |
group | groups |
alias | aliases |
Server-Added Fields
The ingestion API enriches each event with:
| Field | Description |
|---|---|
received_at | Server timestamp when the event was received |
ip | Client IP (from X-Forwarded-For or request IP) |
user_agent | Client user agent string |
Error Responses
| Status | Error Code | Description |
|---|---|---|
400 | invalid_payload | Request body is not valid JSON |
400 | invalid_batch | Batch array failed schema validation |
400 | no_valid_events | No events in the batch passed validation |
401 | missing_write_key | No write key found in header or body |
401 | invalid_write_key | Write key does not match |
413 | batch_too_large | Batch exceeds 100 events |
500 | server_error | Internal 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 Param | Default | Description |
|---|---|---|
writeKey | YOUR_WRITE_KEY | Write key to embed in the snippet |
host | Auto-detected from request | Tracking host URL |
Example: GET /v1/snippet?writeKey=my-key
Authentication Modes
| Mode | Config | Use Case |
|---|---|---|
| Static | WRITE_KEY env var | Single-tenant, local dev, demos |
| Firebase | FIREBASE_AUTH_ENABLED=true | Multi-tenant (Shopify integration) |
In static mode, the write key in the request must match the WRITE_KEY environment variable.
In Firebase mode, the write key is looked up in the tracking_write_keys Firestore collection. Each document contains the merchant’s destination configuration (Mixpanel, Segment, Customer.io, OneSignal, BigQuery).
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:
| Scope | Limit |
|---|---|
| Per IP | 100 requests/second |
| Per write key | 1000 events/second |
| Request body | 2 MB max |
Related
- JavaScript SDK — Client-side SDK reference
- Self-Hosting — Deploy the ingestion API