Skip to main content

Batch Ingestion & Streaming

High-volume agents can produce thousands of events per minute. MandateZ provides two endpoints for throughput-sensitive workloads:
  • POST /api/events/batch — up to 1,000 signed events per request.
  • GET /api/events/stream — Server-Sent Events for live consumption.
Both sit on the canonical event stream — no separate data layer.

When to Use Batch vs. track()

A single track() call is one HTTP round-trip per event. Batching 500 events into one request cuts network overhead by ~500× and reduces Supabase load proportionally.

Direct Batch Calls

Each event is signed locally with the agent’s Ed25519 private key before leaving the process. The dashboard verifies every signature server-side and rejects the whole batch if any event fails schema or signature checks — never a partial insert.
trackBatch() bypasses PolicyEngine and OversightGate. Events submitted to /api/events/batch are pre-signed by the caller and inserted directly into the audit trail. Policy rules that would block or flag an action via client.track() are NOT re-evaluated on the batch path. This is intentional for backfills and bulk replays, but means you must enforce policy BEFORE signing events you intend to batch.

Buffered track() (Background Flush)

Enable batchConfig to keep the ergonomic track() API while getting batch-level throughput:

Buffering Config

Always call client.flush() in shutdown handlers (process exit, SIGTERM, serverless request end). Buffered events live in memory and will be lost if the process dies before a flush.

Limits

Requests that exceed the per-request cap return 413 Payload Too Large. Partition larger workloads into multiple trackBatch() calls. MandateZ does not currently enforce a per-minute organization rate limit; if you need one, front the endpoint with your own quota layer.

Response Shape

When a batch is rejected, the response includes a structured error list so you can pinpoint which events failed before retrying:
reason is always one of: schema_invalid, signature_invalid, owner_mismatch, or http_error.

Real-Time Streaming (SSE)

For live dashboards, SOC monitors, or agent-to-agent observation, connect to the streaming endpoint:
The endpoint emits ready on connection, event for every newly inserted AgentEvent, and heartbeat comments every 15s to keep proxies from closing idle connections.

Authentication

Pass an API key in the Authorization: Bearer mz_live_... header to gate the stream. The key’s owner_id must match the owner_id query parameter or the request is rejected with 403.

Reconnection & Gap-Close

Vercel caps long-lived SSE connections at plan-specific durations. Browser EventSource reconnects automatically and includes the ID of the last event it received in the Last-Event-ID header. MandateZ honours this header: on reconnect, the endpoint first replays every event with a timestamp strictly greater than the last event’s timestamp (matched by event_id), then switches over to the live realtime subscription. Any events emitted while the client was disconnected are delivered before the stream resumes in real time. For server-side consumers, set Last-Event-ID manually on reconnect using the event.event_id of the most recent event you acknowledged. Retry with exponential backoff on onerror.

Cross-Verification

Every event delivered through batch or stream carries its Ed25519 signature and public key. Any downstream system can re-verify on receipt:
Tampering with any field of a batched event will fail this check even if the event already landed in your database.