Tapemetric

HTTP API

Ingest API

Send events via HTTP when an SDK isn't an option — server-side tracking, Roku, HbbTV, beacons.

Base URL

text
https://ingest.tapemetric.com

Authentication

Send your API key in the X-API-Key header. See Authentication.

POST /v1/ingest/events

Send a batch of events. Max 500 events per batch.

Request

bash
curl -X POST https://ingest.tapemetric.com/v1/ingest/events \
  -H "X-API-Key: tm_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "event_type": "play_start",
        "ts": "2026-04-23T18:30:12.456Z",
        "anonymous_id": "a8f3c2d0-1e5b-4a9c-8d4e-7f2a1b3c4d5e",
        "session_id": "sess_a1b2c3d4",
        "content_id": "aashiqana_s04e12",
        "content_type": "series",
        "content_title": "Aashiqana S4 E12",
        "position_sec": 0,
        "bitrate_kbps": 4500,
        "context": { "device_type": "tv", "app_version": "4.12.0" }
      }
    ]
  }'

Response (200)

json
{
  "accepted": 1,
  "rejected": 0,
  "queued": true
}

Error responses

StatusMeaning
400Malformed JSON or event validation failed
401Missing or invalid API key
403Tenant inactive or quota exceeded
429Rate limit hit — retry with exponential backoff
503Ingest degraded — retry later

POST /v1/ingest/beacon

Optimized for navigator.sendBeacon on page unload. Accepts URL-encoded JSON in adata query parameter and always responds 200 even on error (per beacon spec).

javascript
const payload = JSON.stringify({ events: [...] });
navigator.sendBeacon(
  'https://ingest.tapemetric.com/v1/ingest/beacon?api_key=tm_live_...',
  new Blob([payload], { type: 'application/json' }),
);

Batching & rate limits

  • Batch size: max 500 events per call.
  • Rate limit: 120,000 events per minute per tenant by default. Enterprise plans raise this.
  • Quota: monthly event quota per plan. 429 responses when exceeded.

Idempotency

Include an event_id field (UUID) if you want the ingest endpoint to de-duplicate within a 24-hour window. Without event_id, retries may double-count.

json
{
  "event_id": "a1b2c3d4-e5f6-7890-a1b2-c3d4e5f67890",
  "event_type": "play_start",
  "ts": "2026-04-23T18:30:12Z",
  "anonymous_id": "...",
  "session_id": "..."
}
Clock skew: clients with system clocks off by more than ±30 seconds will have their timestamps corrected server-side. For strict ordering use server-generated ts via the?server_ts=1 query flag.

Server-side tracking (Node.js)

typescript
import { randomUUID } from 'crypto';

async function track(userId: string, eventType: string, props: Record<string, unknown>) {
  await fetch('https://ingest.tapemetric.com/v1/ingest/events', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.TAPEMETRIC_INGEST_KEY!,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      events: [{
        event_id: randomUUID(),
        event_type: eventType,
        ts: new Date().toISOString(),
        anonymous_id: userId,
        user_id: userId,
        session_id: `server-${userId}`,
        properties: props,
      }],
    }),
  });
}

Server-side tracking (Python)

python
import os, httpx
from datetime import datetime, timezone
from uuid import uuid4

INGEST = "https://ingest.tapemetric.com/v1/ingest/events"

async def track(user_id: str, event_type: str, props: dict):
    async with httpx.AsyncClient() as client:
        await client.post(INGEST, json={"events": [{
            "event_id": str(uuid4()),
            "event_type": event_type,
            "ts": datetime.now(timezone.utc).isoformat(),
            "anonymous_id": user_id,
            "user_id": user_id,
            "session_id": f"server-{user_id}",
            "properties": props,
        }]}, headers={"X-API-Key": os.environ["TAPEMETRIC_INGEST_KEY"]})