# Assistiv Gateway — SDK reference feed This file concatenates every per-feature integration guide, filtered to the TypeScript SDK surface. Use this as agent context for any agent building on @assistiv/sdk. For the raw HTTP / curl flavour, see /llms-api.txt. # Assistiv — API Keys API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Manage the platform's API keys. Create new platform keys (multiple allowed for rotation), list with prefix-only visibility, revoke immediately. Full hashes are never returned after creation — store the raw key on issuance or generate a new one. ## How to integrate 1. List with `GET /v1/platforms/{platformId}/api-keys` — returns `key_prefix` only. 2. Create with `POST /v1/platforms/{platformId}/api-keys` — `raw_key` returned ONCE. 3. Revoke with `DELETE /v1/platforms/{platformId}/api-keys/{keyId}` — invalidates Redis cache within ~1s. ## SDK example ```ts const created = await assistiv.apiKeys.create({ name: "Production server" }); console.log(created.raw_key); // sk-plat_... — store now, never returned again ``` ## Errors to handle - `AssistivAuthError` — missing or invalid platform/end-user key - `AssistivPaymentRequiredError` (HTTP 402) — wallet exhausted or budget suspended - `AssistivRateLimitError` (HTTP 429) — exceeded the configured rate limit - `AssistivError` — generic 4xx/5xx with a structured `error.code` body --- # Assistiv — Authentication API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Two long-lived bearer tokens. `sk-plat_*` (platform key) authorizes management — create end-users, configure LLM providers, manage budgets. `sk-eu_*` (end-user key) authorizes runtime inference and tool calls on behalf of one user. Send the token in the `Authorization: Bearer` header. Multi-platform owners must also send `X-Assistiv-Platform-Id`. ## How to integrate 1. Provision a platform key in the Assistiv dashboard (Settings → API Keys). 2. Store it server-side only. Never ship to the browser. 3. For inference, mint an end-user key per user via `POST /v1/platforms/{platformId}/end-users` and store its `raw_key` — it is shown ONCE. ## SDK example ```ts // Server-side only. const headers = { Authorization: `Bearer ${process.env.ASSISTIV_PLATFORM_KEY}`, "X-Assistiv-Platform-Id": process.env.ASSISTIV_PLATFORM_ID!, "Content-Type": "application/json", }; ``` ## Errors to handle - `AssistivAuthError` — missing or invalid platform/end-user key - `AssistivPaymentRequiredError` (HTTP 402) — wallet exhausted or budget suspended - `AssistivRateLimitError` (HTTP 429) — exceeded the configured rate limit - `AssistivError` — generic 4xx/5xx with a structured `error.code` body --- # Assistiv — Budgets API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Per-end-user USD budgets with one_time / daily / monthly periods. Idempotent topup/debit via `Idempotency-Key` header, full transaction ledger at `/budget/transactions`, and a `is_suspended` flag to pause access without losing state. ## How to integrate 1. Create with `POST /v1/platforms/{platformId}/end-users/{endUserId}/budget`. 2. Top up with `POST /v1/.../budget/topup` — pass `Idempotency-Key` to make retries safe. 3. Inspect ledger with `GET /v1/.../budget/transactions?limit=100`. ## SDK example ```ts await assistiv.budgets.topup(endUserId, { amount_usd: 5, reason: "Monthly stipend", }, { idempotencyKey: `stipend-${new Date().toISOString().slice(0,7)}` }); ``` ## Errors to handle - `AssistivAuthError` — missing or invalid platform/end-user key - `AssistivPaymentRequiredError` (HTTP 402) — wallet exhausted or budget suspended - `AssistivRateLimitError` (HTTP 429) — exceeded the configured rate limit - `AssistivError` — generic 4xx/5xx with a structured `error.code` body --- # Assistiv — Chat Completions API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does OpenAI-compatible `POST /v1/chat/completions`. Supports streaming, tool calling, structured output, and routing across providers (OpenAI, Anthropic, Google, xAI) by model name. Authenticate with the end-user's `sk-eu_*` key — the platform key is rejected here. ## How to integrate 1. Mint or load the end-user key (see end-users.md). 2. Pass `stream: true` for server-sent events. 3. Branch on 402 to handle wallet / budget cases. ## SDK example ```ts const user = new Assistiv({ apiKey: endUserKey }); const stream = await user.chat.completions.create({ model: "claude-sonnet-4-6", messages: [{ role: "user", content: "Plan a trip to Tokyo" }], stream: true, }); for await (const chunk of stream) process.stdout.write(chunk.choices[0]?.delta?.content ?? ""); ``` ## Errors to handle - `AssistivAuthError` — missing or invalid platform/end-user key - `AssistivPaymentRequiredError` (HTTP 402) — wallet exhausted or budget suspended - `AssistivRateLimitError` (HTTP 429) — exceeded the configured rate limit - `AssistivError` — generic 4xx/5xx with a structured `error.code` body --- # Assistiv — End Users API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does End-users represent your platform's users. Each one gets an auto-generated `sk-eu_*` key, an optional budget, and (if configured) MCP tool connections. `external_id` is your stable identifier; create is idempotent on it. ## How to integrate 1. Create with `POST /v1/platforms/{platformId}/end-users`. The response contains `api_key.raw_key` — store it. 2. Repeat calls with the same `external_id` return `200 OK` plus a fresh key (retry safety, NOT a key-retrieval mechanism). 3. Delete with `DELETE /v1/.../end-users/{endUserId}` — cascades to keys, budgets, and connections. ## SDK example ```ts const { id, api_key } = await assistiv.endUsers.create({ external_id: "user-42", display_name: "Jane Smith", metadata: { plan: "pro" }, }); // Store api_key.raw_key NOW — it is not returned again. ``` ## Errors to handle - `AssistivAuthError` — missing or invalid platform/end-user key - `AssistivPaymentRequiredError` (HTTP 402) — wallet exhausted or budget suspended - `AssistivRateLimitError` (HTTP 429) — exceeded the configured rate limit - `AssistivError` — generic 4xx/5xx with a structured `error.code` body --- # Assistiv — LLM Provider Configs API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Connect your own OpenAI / Anthropic / Google / xAI keys so the gateway can route inference to the upstream you chose. Keys are encrypted AES-256-GCM at rest. ## Where to do this Dashboard → LLM Configs (https://www.assistiv.ai/dashboard/llm-configs) 1. Open the LLM Configs page in your dashboard. 2. Click "Add provider" and pick OpenAI / Anthropic / Google / xAI. 3. Paste your provider API key. Submit. Encrypted at rest immediately. 4. Toggle "Enabled" — inference can now route through this provider. To rotate a key, click the existing provider row, paste a new key, submit. Old credentials are decrypted, replaced, re-encrypted in one transaction. ## Why no REST endpoint Provider keys are write-once secrets entered by a human. Exposing them over the API would mean platforms shipping those secrets through their own backend — an unnecessary handling surface. The dashboard form is the single entry point. ## Errors to handle None at integration time — the dashboard form surfaces validation errors inline. --- # Assistiv — Logs API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Per-platform structured logs of every inference and tool call. Filter by end-user, model, time range. Includes token counts, cost, latency, status code, error code. ## How to integrate 1. Fetch with `GET /v1/platforms/{platformId}/logs?start=…&end=…`. 2. Page with `cursor`, not offset — large platforms get bursty. 3. Cross-reference `request_id` to debug a specific call. ## SDK example ```ts const logs = await assistiv.logs.list({ end_user_id: someUserId, start: new Date(Date.now() - 86_400_000).toISOString(), }); ``` ## Errors to handle - `AssistivAuthError` — missing or invalid platform/end-user key - `AssistivPaymentRequiredError` (HTTP 402) — wallet exhausted or budget suspended - `AssistivRateLimitError` (HTTP 429) — exceeded the configured rate limit - `AssistivError` — generic 4xx/5xx with a structured `error.code` body --- # Assistiv — MCP / Tools API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does OAuth-backed third-party tool execution. Today: GitHub, Slack, Zoho Mail, Zendesk. Two surfaces: - **Activating apps for your platform** (one-time setup) — dashboard only. - **End-user runtime calls** (list/connect/execute) — API and agent MCP calls. ## Where to activate an MCP app Dashboard → MCP (https://www.assistiv.ai/dashboard/mcp) 1. Open Dashboard → MCP. Every supported app is listed with active/inactive state. 2. Click an inactive app (e.g. GitHub). Paste OAuth Client ID, OAuth Client Secret (encrypted AES-256-GCM), your platform's landing URL (end users return to `{base}/mcp/oauth-callback`), and any non-default scopes. 3. At your provider's OAuth settings (GitHub OAuth Apps, Slack app config, etc.), set the Authorization callback URL to `https://mcp.assistiv.ai/oauth/callback`. 4. Save. End users can now connect this app from your product. 5. To rotate credentials, edit the same row. To turn off the app, toggle inactive — existing end-user connections are revoked. OAuth Client Secrets are write-once secrets, so there is no curl or SDK path for these mutations. ## How agents call tools at runtime After activation, end users connect their accounts via OAuth, then your agent calls the MCP protocol at `https://mcp.assistiv.ai/mcp` using the end-user's `sk-eu_*` key. The gateway runs OAuth + tool execution server-side. ## SDK example ```ts // Read which apps are activated for your platform (so you can render // "Connect GitHub" / "Connect Slack" UI for your end users). import { Assistiv } from "@assistiv/sdk"; const platform = new Assistiv({ apiKey: process.env.ASSISTIV_PLATFORM_KEY! }); const { data: apps } = await platform.mcp(platformId).listApps(); ``` ## Errors to handle - `AssistivAuthError` — missing or invalid end-user key - MCP protocol errors come back as JSON-RPC `error` payloads, not HTTP status codes - An end-user who hasn't completed OAuth for an app gets a `not_connected` error from the relevant tool --- # Assistiv — Models API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Lists the model IDs your platform can call right now. Filtered by which LLM provider keys you've configured. Use the returned IDs as the `model` field for chat-completions. ## How to integrate 1. Call `GET /v1/models` with the platform key OR end-user key. 2. Display only models your UI supports — don't paginate to the user. ## SDK example ```ts const { data: models } = await assistiv.models.list(); console.log(models.map((m) => m.id)); ``` ## Errors to handle - `AssistivAuthError` — missing or invalid platform/end-user key - `AssistivPaymentRequiredError` (HTTP 402) — wallet exhausted or budget suspended - `AssistivRateLimitError` (HTTP 429) — exceeded the configured rate limit - `AssistivError` — generic 4xx/5xx with a structured `error.code` body --- # Assistiv — Overview API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Assistiv is a multi-tenant API gateway. Your platform gets one platform key (`sk-plat_*`), connects its own LLM provider keys, and each end-user gets their own `sk-eu_*` key. The gateway handles auth, billing, rate-limits, MCP tool execution, and OpenAI-compatible inference behind a single base URL. ## How to integrate 1. Pick the right key type: `sk-plat_*` for management calls, `sk-eu_*` for end-user inference. 2. Read [authentication.md] before any other snippet — every call needs `Authorization: Bearer …`. 3. Follow the integration steps in order: configure LLM → create end-user → first call → budgets → tools → webhooks → logs. ## SDK example ```ts import { Assistiv } from "@assistiv/sdk"; const assistiv = new Assistiv({ apiKey: process.env.ASSISTIV_PLATFORM_KEY!, // sk-plat_… platformId: process.env.ASSISTIV_PLATFORM_ID!, }); // One round-trip sanity check. const me = await assistiv.me.get(); console.log(me); ``` ## Errors to handle - `AssistivAuthError` — missing or invalid platform/end-user key - `AssistivPaymentRequiredError` (HTTP 402) — wallet exhausted or budget suspended - `AssistivRateLimitError` (HTTP 429) — exceeded the configured rate limit - `AssistivError` — generic 4xx/5xx with a structured `error.code` body --- # Assistiv — Platform settings API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does A platform is one tenant on the gateway — its own API keys, end-users, wallet, LLM configs, MCP apps. Settings (display name, default budget, markup, branding, feature flags like `skills_enabled`) are edited in the dashboard. ## Where to do this Dashboard → Settings (https://www.assistiv.ai/dashboard/settings) 1. Open Settings in your dashboard. 2. Adjust display name, default budget, markup, branding, feature toggles. 3. Submit — changes apply immediately to all end-users on this platform. Team management lives under Dashboard → Team. Each teammate signs in with their own email and inherits your platform's admin scope. Platform provisioning is NOT self-serve. Email platforms@assistiv.ai to request a new platform; Assistiv issues a `sk-plat_*` key and grants dashboard access. ## What your backend sees Read-only platform info (id, slug, created_at, settings) is included in: - Every outbound webhook payload (`data.platform_id`) - Every inference log entry Your backend rarely needs to query platform settings directly. ## Errors to handle None — platform settings are managed in the dashboard; your runtime code doesn't interact with this surface. --- # Assistiv — Quickstart API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Five steps to first inference. Install the SDK, set env vars, create an end-user, call chat-completions with that user's key, top up the wallet if you hit a 402. ## How to integrate 1. `npm install @assistiv/sdk` 2. Set `ASSISTIV_PLATFORM_KEY`, `ASSISTIV_PLATFORM_ID`. 3. `assistiv.endUsers.create({ external_id })` → store `result.api_key.raw_key`. 4. Call `assistiv.chat.completions.create({ model, messages })` using the end-user key. ## SDK example ```ts import { Assistiv } from "@assistiv/sdk"; const platform = new Assistiv({ apiKey: process.env.ASSISTIV_PLATFORM_KEY!, platformId: process.env.ASSISTIV_PLATFORM_ID!, }); // 1. Provision an end-user (idempotent on external_id). const { api_key } = await platform.endUsers.create({ external_id: "user-42" }); // 2. Use that end-user key to run inference. const user = new Assistiv({ apiKey: api_key.raw_key }); const res = await user.chat.completions.create({ model: "gpt-4o-mini", messages: [{ role: "user", content: "Say hi" }], }); console.log(res.choices[0].message); ``` ## Errors to handle - `AssistivAuthError` — missing or invalid platform/end-user key - `AssistivPaymentRequiredError` (HTTP 402) — wallet exhausted or budget suspended - `AssistivRateLimitError` (HTTP 429) — exceeded the configured rate limit - `AssistivError` — generic 4xx/5xx with a structured `error.code` body --- # Assistiv — Responses API API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does OpenAI-compatible `POST /v1/responses` — the newer, stateful inference shape. Supports the same provider routing as chat-completions and adds server-side conversation state. ## How to integrate 1. Use this when you want Assistiv to track conversation history server-side. 2. Otherwise prefer chat-completions for cross-provider portability. ## SDK example ```ts const res = await fetch("https://api.assistiv.ai/v1/responses", { method: "POST", headers: { Authorization: `Bearer ${endUserKey}`, "Content-Type": "application/json" }, body: JSON.stringify({ model: "gpt-4o-mini", input: "Plan a trip" }), }); ``` ## Errors to handle - `AssistivAuthError` — missing or invalid platform/end-user key - `AssistivPaymentRequiredError` (HTTP 402) — wallet exhausted or budget suspended - `AssistivRateLimitError` (HTTP 429) — exceeded the configured rate limit - `AssistivError` — generic 4xx/5xx with a structured `error.code` body --- # Assistiv — Self-Service (rate limits) API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Per-end-user rate-limit overrides. Set custom RPM / TPM / RPD for individual users without touching the platform default. Useful for high-value customers on a free product tier. ## How to integrate 1. Read defaults with `GET /v1/platforms/{platformId}/rate-limits`. 2. Override per user with `PUT /v1/platforms/{platformId}/end-users/{endUserId}/rate-limits`. 3. Clear an override with `DELETE` on the same path. ## SDK example ```ts await assistiv.rateLimits.set(endUserId, { requests_per_minute: 600, tokens_per_minute: 250_000, }); ``` ## Errors to handle - `AssistivAuthError` — missing or invalid platform/end-user key - `AssistivPaymentRequiredError` (HTTP 402) — wallet exhausted or budget suspended - `AssistivRateLimitError` (HTTP 429) — exceeded the configured rate limit - `AssistivError` — generic 4xx/5xx with a structured `error.code` body --- # Assistiv — Skills (uploads & catalog) API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Skills are agent-fetchable code packages. Upload a private skill from the dashboard; your end-users' agents can then discover and fetch it via the five skill MCP tools at mcp.assistiv.ai/mcp/skills. ## Where to do this Dashboard → Skills (https://www.assistiv.ai/dashboard/skills) 1. Enable skills for your platform first (Dashboard → MCP → toggle `skills_enabled`). 2. Open Dashboard → Skills. 3. Click "Upload skill", drop in your folder (zipped), fill in name + short description, submit. Encrypted in GCS and content-sha deduped. 4. Skill is immediately available via `find_skill` and `fetch_skill` MCP tools. 5. Reviews and rankings come from agent usage; see the 4-dimension scorecard on each skill detail page. Skills uploaded by your platform are PRIVATE to your platform. ## Runtime usage Agents do not call this REST surface. They use the MCP tools at `https://mcp.assistiv.ai/mcp/skills`: - `find_skill` — search the catalog (public + your private skills) - `fetch_skill` — download the full folder - `create_skill` / `delete_skill` / `submit_skill_review` — agent-driven (use with caution) See skills-mcp.md for agent integration. ## Errors to handle None at upload time (dashboard form). Runtime errors from agent calls are handled by your MCP client. --- # Assistiv — Skills via MCP API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Five MCP tools auto-register when `platform.skills_enabled = true`: `find_skill`, `fetch_skill`, `create_skill`, `delete_skill`, `submit_skill_review`. Hit `https://mcp.assistiv.ai/mcp/skills` for a clean skills-only tool list, or `/mcp` to merge with your connected OAuth apps (GitHub, Slack, Zoho, Zendesk). ## How to integrate 1. Point your agent's MCP client at `https://mcp.assistiv.ai/mcp/skills`. 2. Authenticate with the end-user's `sk-eu_*` key in the `Authorization` header. 3. Use `find_skill` first, then `fetch_skill` to retrieve the full folder, then run locally. ## SDK example ```ts // curl smoke-test curl -N -X POST https://mcp.assistiv.ai/mcp/skills \ -H "Authorization: Bearer ${END_USER_KEY}" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' ``` ## Errors to handle - `AssistivAuthError` — missing or invalid platform/end-user key - `AssistivPaymentRequiredError` (HTTP 402) — wallet exhausted or budget suspended - `AssistivRateLimitError` (HTTP 429) — exceeded the configured rate limit - `AssistivError` — generic 4xx/5xx with a structured `error.code` body --- # Assistiv — Skills (overview) API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Skills are agent-fetchable code packages. Public skills come from admin-vetted GitHub repos; private skills are uploaded by your platform and encrypted in GCS. Agents discover them via the `find_skill` MCP tool, then `fetch_skill` to download the full folder and run it locally. ## How to integrate 1. Toggle skills on for your platform: `PATCH /v1/platforms/{platformId}` with `{ skills_enabled: true }`. 2. Point your agent at `https://mcp.assistiv.ai/mcp/skills` for the five skill tools only, or `/mcp` to combine with connected apps. 3. Submit your own skill via `POST /v1/skills` (private to your platform until reviewed). ## SDK example ```ts // Toggle skills on. await fetch(`https://api.assistiv.ai/v1/platforms/${platformId}`, { method: "PATCH", headers: { Authorization: `Bearer ${platformKey}`, "Content-Type": "application/json" }, body: JSON.stringify({ skills_enabled: true }), }); ``` ## Errors to handle - `AssistivAuthError` — missing or invalid platform/end-user key - `AssistivPaymentRequiredError` (HTTP 402) — wallet exhausted or budget suspended - `AssistivRateLimitError` (HTTP 429) — exceeded the configured rate limit - `AssistivError` — generic 4xx/5xx with a structured `error.code` body --- # Assistiv — Wallet & top-ups API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Every platform has one wallet with a USD balance. Each LLM call atomically debits the wallet by actual token cost. Top-ups are Stripe-backed and happen in the dashboard. ## Where to do this Dashboard → Settings → Billing (https://www.assistiv.ai/dashboard/settings) 1. Open Settings → Billing in the dashboard to see the current balance. 2. Click "Top up", pick the USD amount, complete Stripe checkout in the new tab. 3. Balance updates within seconds; a ledger row is written. 4. Toggle "Auto top-up" to refill automatically when balance drops below a threshold. Auto top-up fires a `wallet.low_balance` webhook (if registered) BEFORE the Stripe charge, so your systems can react. ## Reading the balance programmatically Don't poll a REST endpoint. Subscribe to the `wallet.low_balance` and `wallet.topped_up` outbound webhooks (see webhooks.md). The current balance is in every payload. ## Errors to handle - `402 wallet_insufficient` on `/v1/chat/completions` or `/v1/responses` — wallet empty. Pause inference for this platform; surface empty-state to the platform admin; require a dashboard top-up before resuming. --- # Assistiv — Outbound Webhooks API: v0.1.0 — base URL https://api.assistiv.ai SDK: @assistiv/sdk@0.0.0 — `npm install @assistiv/sdk` ## What this does Receive real-time events (`budget.topped_up`, `budget.low_balance`, `budget.suspended`, `budget.unsuspended`, `budget.debited`, `wallet.low_balance`, `wallet.topped_up`). Delivery is Svix-backed with automatic retry and signed payloads. ## Where to register an endpoint Dashboard → Outbound Webhooks (https://www.assistiv.ai/dashboard/outbound-webhooks) 1. Open Outbound Webhooks in your dashboard. 2. Click "Add endpoint", paste the HTTPS URL your server is listening on, and check the event types you want. 3. Reveal the signing secret from the table and store it in your server's env vars (`ASSISTIV_WEBHOOK_SECRET`). It is required to verify incoming payloads. 4. Failed deliveries are retried automatically. Click into an endpoint to see the delivery log and replay events from the Svix portal. ## How to handle incoming events Your server receives signed POST requests at the URL you registered. Verify the signature with the SDK's `verifyWebhook` helper, then branch on `event_type`. ## SDK example ```ts import express from "express"; import { verifyWebhook } from "@assistiv/sdk"; const app = express(); app.post("/assistiv-events", express.raw({ type: "application/json" }), (req, res) => { const ok = verifyWebhook( req.body, req.headers, process.env.ASSISTIV_WEBHOOK_SECRET!, ); if (!ok) return res.status(400).end(); const event = JSON.parse(req.body.toString()); if (event.event_type === "budget.low_balance") { // notify the end-user, top up, or pause } res.status(200).end(); }); ``` ## Errors to handle - Signature mismatch — respond `400`. The Svix dashboard treats this as a delivery failure and retries. - Slow handler — respond `200` quickly; do heavy work async. Svix retries on >10s. - `wallet_insufficient` 402s from inference are NOT delivered via webhooks (they're synchronous API errors). Webhooks only fire on balance-change events.