Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mandatez.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

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()

PatternUse when
client.track(...)Interactive agents — single user request, low volume, you need the returned event immediately.
client.trackBatch([...])Background jobs, nightly imports, bulk replays, or any loop that produces 10+ events at once.
batchConfig bufferingContinuous high-volume agents where each individual track() call should return instantly and the SDK handles batching transparently.
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

import { MandateZClient } from '@mandatez/sdk';

const client = new MandateZClient({
  agentId: 'ag_...',
  ownerId: 'your_org_id',
  privateKey: process.env.AGENT_PRIVATE_KEY!,
  supabaseUrl: process.env.SUPABASE_URL!,
  supabaseAnonKey: process.env.SUPABASE_ANON_KEY!,
  apiUrl: 'https://dashboard.mandatez.com',
  apiKey: process.env.MANDATEZ_API_KEY!,
});

const result = await client.trackBatch([
  { action_type: 'read', resource: 'emails' },
  { action_type: 'export', resource: 'orders' },
  { action_type: 'call', resource: 'api/stripe' },
]);

console.log(result);
// { accepted: 3, rejected: 0, errors: [] }
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:
const client = new MandateZClient({
  agentId: 'ag_...',
  ownerId: 'your_org_id',
  privateKey: process.env.AGENT_PRIVATE_KEY!,
  supabaseUrl: process.env.SUPABASE_URL!,
  supabaseAnonKey: process.env.SUPABASE_ANON_KEY!,
  apiUrl: 'https://dashboard.mandatez.com',
  batchConfig: {
    enabled: true,
    maxSize: 200,      // flush when 200 events are queued
    maxWaitMs: 2_000,  // or after 2s, whichever comes first
  },
});

for (const message of stream) {
  await client.track({ action_type: 'read', resource: 'kafka/inbound' });
  // returns immediately with a signed event; delivery happens in the background
}

// Always flush on shutdown so pending events are not dropped.
await client.flush();

Buffering Config

FieldTypeDescription
enabledbooleanTurn buffering on/off. Off by default.
maxSizenumberFlush as soon as the buffer holds this many events.
maxWaitMsnumberFlush this long after the first queued event, even if maxSize is not reached.
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

LimitValue
Events per request1,000
Max request body size5 MB
Signature checkEd25519 per event, whole batch rejected on any failure
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

{
  "accepted": 842,
  "rejected": 0,
  "errors": []
}
When a batch is rejected, the response includes a structured error list so you can pinpoint which events failed before retrying:
{
  "error": "Batch contains invalid events — no events were inserted",
  "accepted": 0,
  "rejected": 3,
  "errors": [
    { "index": 1, "event_id": "…", "reason": "signature_invalid" },
    { "index": 2, "reason": "schema_invalid", "detail": "action_type: Invalid enum value" }
  ]
}
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:
const source = new EventSource(
  'https://dashboard.mandatez.com/api/events/stream?owner_id=your_org_id',
);

source.addEventListener('ready', (e) => {
  console.log('connected', JSON.parse(e.data));
});

source.addEventListener('event', (e) => {
  const event = JSON.parse(e.data);
  console.log(event.action_type, event.resource, event.outcome);
});
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:
import { verifyEvent } from '@mandatez/sdk';

const valid = await verifyEvent(event);
// true — signature matches public_key and canonicalized payload
Tampering with any field of a batched event will fail this check even if the event already landed in your database.