# Endpoints Source: https://docs.relayer.fi/agent/endpoints Reference card for the Agent Kit API surface All endpoints below live under `/v1/agents/*`. Auth is either `Authorization: ApiKey ` (integrator-side) or `X-Agent-Auth` HMAC header (agent-side, built by the SDK). For full request/response schemas see the [API Reference](/api-reference/introduction) — the Agents group has every endpoint with an interactive playground. ## Lifecycle | Method | Path | Caller | What it does | | -------- | ------------------------------- | ---------- | ----------------------------------------------------------------------------- | | `POST` | `/agents/prepare` | Dashboard | Round 1 — prepare `CREATE_USERS_V3` for passkey signing | | `POST` | `/agents/confirm-user` | Dashboard | Round 2 — forward stamped `CREATE_USERS`, return unsigned `CREATE_POLICIES` | | `POST` | `/agents/confirm-policies` | Dashboard | Round 3 — forward stamped `CREATE_POLICIES`, reveal agent secret (once) | | `POST` | `/agents/{id}/prepare-policies` | Dashboard | Resume — regenerate unsigned `CREATE_POLICIES` for a `pending_policies` agent | | `DELETE` | `/agents/{id}` | Integrator | Abandon a `pending_policies` agent or delete one no longer needed | The `prepare` / `confirm-user` / `confirm-policies` / `prepare-policies` endpoints require a passkey ceremony in the browser — only the dashboard runs them today. The full list above is shown for understanding the lifecycle; the operator-callable subset (read, control, funding, runtime) is documented in the [API Reference](/api-reference/introduction) under the **Agents** group. ## Read | Method | Path | Caller | What it does | | ------ | ----------------------------- | ------------------- | --------------------------------------------- | | `GET` | `/agents` | Integrator | List all agents for the integrator | | `GET` | `/agents/{id}` | Integrator | Get agent details | | `GET` | `/agents/{id}/status` | Integrator or Agent | Agent status + kill-switch flag | | `GET` | `/agents/{id}/wallet` | Integrator | Wallet info + Solana explorer URL | | `GET` | `/agents/{id}/wallet-balance` | Integrator or Agent | USDC balance on the agent wallet | | `GET` | `/agents/{id}/budget` | Integrator or Agent | Current budget state across all 3 layers | | `GET` | `/agents/analytics/summary` | Integrator | Aggregated analytics across all agents | | `GET` | `/agents/{id}/analytics` | Integrator or Agent | Per-agent analytics | | `GET` | `/agents/{id}/audit` | Integrator | Paginated audit history | | `GET` | `/agents/approvals` | Integrator | Pending approval requests for this integrator | ## Control (backend-callable) | Method | Path | What it does | | ------ | --------------------- | --------------------- | | `POST` | `/agents/{id}/pause` | Reversible pause | | `POST` | `/agents/{id}/resume` | Resume a paused agent | ## Control (passkey-stamped, dashboard-driven) These operations are destructive and require a passkey ceremony today — they run from the dashboard. The API endpoints exist but aren't documented in the API Reference until a passkey-stamp prepare/confirm pair ships. | Method | Path | What it does | | ------ | --------------------- | ------------------------------------- | | `POST` | `/agents/{id}/budget` | Configure / reconfigure budget layers | | `POST` | `/agents/{id}/kill` | Irreversible emergency stop | | `POST` | `/agents/{id}/rotate` | Issue new HMAC secret, revoke old | ## Funding | Method | Path | What it does | | ------ | --------------------------- | ---------------------------------------------- | | `POST` | `/agents/{id}/fund/prepare` | Prepare USDC transfer integrator → agent | | `POST` | `/agents/{id}/fund/confirm` | Forward passkey-stamped activity and broadcast | Funding also requires a passkey ceremony — dashboard-driven today. ## Agent runtime (HMAC X-Agent-Auth) | Method | Path | What it does | | ------ | ------------------------------- | ------------------------------------------ | | `POST` | `/agents/wallets` | Create a wallet as the authenticated agent | | `POST` | `/agents/{id}/sign-transaction` | Sign a Solana transaction | | `POST` | `/agents/{id}/x402-pay` | Pay an x402-gated resource with USDC | | `POST` | `/agents/events/batch` | Batch ingest up to 100 events | # Flow Guide Source: https://docs.relayer.fi/agent/flow-guide Create-agent → fund → sign → x402-pay lifecycle The Agent Kit lifecycle has four stages: **provision**, **fund**, **operate**, and **respond to approvals**. Every passkey-signed step uses prepare/confirm so private key material never leaves the secure enclave. **Dashboard-driven stages** (passkey-stamped today): provision, fund, configure budget, kill, rotate. The endpoints exist but require a passkey ceremony bound to RP `relayer.fi`, so the dashboard orchestrates them — third-party SDK path is planned (see [RFC #21](https://github.com/relayerfi/relayer/pull/21) and [RFC #23](https://github.com/relayerfi/relayer/pull/23)). **Backend/SDK-driven stages**: operate (runtime HMAC), pause/resume, all reads. ## 1. Provision an agent (3-round passkey flow) ```mermaid theme={null} sequenceDiagram participant FE as Dashboard participant API as Relayer API participant SE as Secure Enclave FE->>API: POST /v1/agents/prepare API->>SE: Build unsigned user-create activity API-->>FE: {activity, payload to sign} FE->>FE: Stamp with passkey FE->>API: POST /v1/agents/confirm-user API->>SE: Forward stamped user-create API-->>FE: {agentId, unsigned policies activity} FE->>FE: Stamp with passkey FE->>API: POST /v1/agents/confirm-policies API->>SE: Forward stamped policies activity API-->>FE: {agentId, agentSecret} ← shown ONCE ``` If the flow is interrupted between rounds 2 and 3, the dashboard resumes via `POST /v1/agents/{id}/prepare-policies`. To abandon, call `DELETE /v1/agents/{id}` while status is `pending_policies`. ## 2. Fund the agent wallet ```text theme={null} POST /v1/agents/{id}/fund/prepare { amountUSDC } → unsigned USDC transfer activity from integrator wallet → agent wallet POST /v1/agents/{id}/fund/confirm { stamped activity } → broadcasts the transfer ``` ## 3. Configure budgets From the dashboard's agent settings (under the hood, a passkey-stamped `POST /v1/agents/{id}/budget`): ```text theme={null} { infrastructure: { monthlyUSD: 50 }, tokens: { monthlyUSD: 200 }, payments: { monthlyUSDC: 1000, perTxUSDC: 100, approvalThresholdUSDC: 50 } } ``` Three layers are enforced independently: * **infra** — every API call (cheap, frequent) * **tokens** — every LLM token usage event * **payments** — every USDC outflow When the SDK is going to pay, it calls `checkPaymentBudget()` which queries all 3 + the kill switch. When emitting telemetry, it calls `checkBudget()` which queries layers 1+2 only (fail-open on layer 3). ## 4. Agent operates (HMAC X-Agent-Auth) The agent SDK signs every request with `HMAC-SHA256(method + path + body, secret)`: ```ts theme={null} import { RelayerSDK } from "@relayerfi/agent-sdk"; const sdk = new RelayerSDK({ agentId: process.env.RELAYER_AGENT_ID!, agentSecret: process.env.RELAYER_AGENT_SECRET!, apiUrl: "https://api.relayer.fi", }); // Pay an x402-gated endpoint const data = await sdk.x402fetch("https://paid-api.example.com/v1/run", { method: "POST", body: JSON.stringify({ input }), }); // Sign a Solana transaction const sig = await sdk.http.request("POST", `/agents/${sdk.agentId}/sign-transaction`, { unsignedTx: base64Tx, }); ``` ## 5. Approvals (above threshold) If a payment exceeds `approvalThresholdUSDC`, the API responds **HTTP 202** with `{ approvalId }`. The SDK suspends and polls `/v1/signing/approvals/{id}` every 5s until a CFO approves with passkey (or until the 5-minute deadline). A human approver in the dashboard sees the request in their queue (`GET /v1/signing/approvals?status=pending`) and stamps approval with `POST /v1/signing/approvals/{id}/approve-with-passkey`. ## 6. Emergency stop + maintenance **Backend-callable (your own automation can hit these):** ```text theme={null} POST /v1/agents/{id}/pause → reversible. Use for routine maintenance or anomaly response. POST /v1/agents/{id}/resume → reversible. Resume from pause. ``` **Dashboard-driven (passkey-stamped):** ```text theme={null} POST /v1/agents/{id}/kill → irreversible. Wallet stays funded; further txs rejected. POST /v1/agents/{id}/rotate → issues a new secret. Old one revoked instantly. ``` The SDK polls `GET /v1/agents/{id}/status` every 30s. On `killed` or API unreachable, payment ops block (fail-safe); telemetry continues (fail-open). # Getting Started Source: https://docs.relayer.fi/agent/getting-started From zero to running agent — sign up, activate the Agent Kit module, provision your first agent, install the SDK, and ship This is the full path from "I just heard about Relayer" to "my agent is running, spending USDC, and tracked by 3-layer budget enforcement". Roughly 30 minutes if you have your wallet funded. Sign up at the [Relayer dashboard](https://relayer.fi) with your email. You'll receive a verification link; once confirmed, you land in your **workspace** — the multi-tenant container that holds your wallets, API keys, team, and agents. A workspace is free to create. Billing kicks in only when you activate a Kit module that has paid usage (Payouts) or when your agents start spending. The dashboard walks you through registering a passkey on your device (Face ID, Touch ID, Windows Hello, or a hardware security key). The passkey is the **only thing** that can authorize signing operations on your wallets — neither Relayer nor your password can. You can register more than one passkey per workspace (recommended: one per device + one hardware key for recovery). In Dashboard → **Modules**, toggle **Agent Kit** on. This: * Enables the `/v1/agents/*` endpoints for your workspace * Provisions an internal queue for budget enforcement * Activates the `agent` permission scope on your API keys You can also activate Signing Kit (required for any wallet operations), Payout Kit (fiat rails), and Widget Kit (embedded UI) independently. Agent Kit depends on Signing Kit — the dashboard activates both if needed. In Dashboard → **API Keys**, click **Create key**. Pick scope `integrator` (read+write) or `integrator:write` (write only) and give it a name (e.g. "agent-provisioning-prod"). The key is shown **once** — `rk_client_key_v1_...`. Save it in your password manager or secret store; you can't retrieve it later, only revoke and re-issue. Never commit API keys to version control. Use environment variables or a secret manager (Vercel envs, AWS Secrets Manager, Doppler, etc.). The agent's USDC funds come from your **operator wallet** — a self-custodial wallet your workspace owns. Create it from the dashboard's **Wallets** tab (wallet creation is passkey-stamped, so it runs in the dashboard today). Fund it with USDC on Solana via: * Direct transfer from your existing wallet (Phantom, Solflare, etc.) * On-ramp from fiat via `POST /v1/payout/onramp/deposit-accounts` (Payout Kit) * A test airdrop (sandbox only) Go to the dashboard → **Agents → New agent**. Provisioning is a 3-round passkey flow (the dashboard runs it end-to-end): 1. The dashboard prepares an unsigned user-create activity, you stamp it with your passkey 2. The dashboard submits the stamped activity, prepares a second activity for the agent's policies, you stamp again 3. The dashboard submits the policies and shows you the **`agentSecret`** The `agentSecret` is shown **once**. Copy it directly into your agent's secret store. There is no recovery — if lost, rotate via `POST /v1/agents/{id}/rotate`. Each agent gets: * A unique `agentId` (UUID) * Its own Solana USDC wallet (separate from your operator wallet) * An HMAC secret for SDK runtime auth From the dashboard's agent settings, define the spend limits (the dashboard runs a passkey-stamped `POST /v1/agents/{id}/budget` under the hood): ```json theme={null} { "infrastructure": { "monthlyUSD": 50 }, "tokens": { "monthlyUSD": 200 }, "payments": { "monthlyUSDC": 1000, "perTxUSDC": 100, "approvalThresholdUSDC": 50 } } ``` Three layers enforced independently and atomically server-side: | Layer | What it caps | Failure mode | | ------------ | --------------------------------------------- | -------------------------------- | | **infra** | API calls (cheap, frequent) | Fail-open on payments-only check | | **tokens** | LLM token spend (Anthropic / OpenAI / Google) | Fail-open on payments-only check | | **payments** | On-chain USDC transfers and x402 payments | Always blocks; never bypassable | Transactions above `approvalThresholdUSDC` require a CFO/operator to stamp approval with their passkey (delivered via the dashboard's Approvals queue). From the dashboard's agent detail view, move USDC from your operator wallet to the agent wallet. The dashboard runs a 2-round passkey flow under the hood: it prepares the transfer activity, you stamp with your passkey, it broadcasts to Solana. Once funded, the agent can spend up to its `payments` budget. In the project where your agent runs: ```bash pnpm theme={null} pnpm add @relayerfi/agent-sdk ``` ```bash npm theme={null} npm install @relayerfi/agent-sdk ``` ```bash yarn theme={null} yarn add @relayerfi/agent-sdk ``` Requirements: * **Node.js ≥ 22.13.0** — `fetch`, `AbortSignal.timeout`, `node:crypto` HMAC * **ESM only** — no CJS shim * **TypeScript ≥ 5.0** if you want type definitions Mastra integration (if you build agents with Mastra): ```bash theme={null} pnpm add @mastra/core # The SDK exposes hooks via the @relayerfi/agent-sdk/mastra entry point. # @mastra/core is an optional peer dep — installed only if you import the hooks. ``` Three env vars are required: ```text .env theme={null} RELAYER_AGENT_ID=agt_ RELAYER_AGENT_SECRET= RELAYER_API_URL=https://api.relayer.fi # or for sandbox testing: # RELAYER_API_URL=https://testnet.relayer.fi ``` Drop them in `.env`, your platform's secret store, or your runtime config. **Never log `RELAYER_AGENT_SECRET`** — it's the only thing that can sign API calls as this agent. Minimal init in your agent runtime: ```typescript src/sdk.ts theme={null} import { RelayerSDK } from "@relayerfi/agent-sdk"; export const sdk = new RelayerSDK({ agentId: process.env.RELAYER_AGENT_ID!, secret: process.env.RELAYER_AGENT_SECRET!, apiUrl: process.env.RELAYER_API_URL!, }); ``` That's it. The constructor: * Validates the 3 required fields * Starts the kill-switch poller (every 30s) * Starts the event batcher (flushes every 10s or on 50 events) * Registers `SIGTERM` / `beforeExit` handlers for graceful shutdown See the [SDK Reference](/agent/sdk) for the full constructor config (retries, timeouts, custom intervals). The simplest paid action is calling any x402-protected HTTP endpoint: ```typescript theme={null} import { sdk } from "./sdk"; import { x402fetch } from "@relayerfi/agent-sdk"; const response = await x402fetch(sdk, "https://paid-api.example.com/v1/run", { method: "POST", body: JSON.stringify({ input: "What's the current ETH price?" }), }); const result = await response.json(); ``` `x402fetch` is the one-liner that ties everything together: payment-budget check, HMAC auth, x402 detection, payment, and retry — all behind a normal `fetch` API. If you're building with Mastra: ```typescript src/agent.ts theme={null} import { Agent } from "@mastra/core/agent"; import { sdk } from "./sdk"; import { relayerMastraHook } from "@relayerfi/agent-sdk/mastra"; const agent = new Agent({ /* your config */ }); const hook = relayerMastraHook(sdk); await agent.generate("Generate a financial report", { onStepFinish: hook.onStepFinish, }); // Token usage automatically tracked and emitted as llm_call events. ``` ## What you've built By the end of step 12, you have: * A **workspace** in the Relayer dashboard with at least one passkey * An **API key** to call `/v1/*` from your backend * An **operator wallet** with USDC, owned by your workspace passkey * An **agent** with its own wallet, HMAC secret, and 3-layer budget caps * A **deployed agent runtime** that uses the SDK to pay for resources, with budget enforcement that cannot be bypassed — not even by a misbehaving LLM in your agent code ## Where to go next Exhaustive reference for every class, method, event type, and error in `@relayerfi/agent-sdk`. The same flow as above but focused on the API endpoints — useful when you're not using the dashboard. Reference implementations: BI Agent (daily reports), OTC Agent (operator workflow), Outbound Agent (B2B sales). # Agent Kit Source: https://docs.relayer.fi/agent/overview Budget-enforced AI agents that can sign transactions and pay for on-chain resources The Agent Kit lets you ship AI agents that can hold a wallet, spend USDC, and sign Solana transactions — without ever being able to spend money they were not authorized to spend. ## Core guarantee **Budget enforcement that cannot be bypassed.** If the SDK says "no budget", the payment does not happen. Enforcement is server-side and atomic — three independent budget layers plus a kill switch. ## What you get * **Programmable agent lifecycle** — create, fund, pause, resume, rotate, and kill an agent * **Per-agent USDC wallet** — an isolated wallet workspace on Solana, signed by passkey at provisioning time * **3-layer budget guard** — infrastructure spend, token usage, and on-chain payments — enforced independently * **Kill switch** — global stop that fails-safe (blocks payments, lets infra/tokens fail-open) * **x402-pay** — pay HTTP 402 protected endpoints with USDC, with approval flow for amounts above threshold * **Event batching** — `llm_call`, `api_call`, `payment` telemetry batched and flushed every 10s ## Authentication Two modes coexist on `/v1/agents/*`: | Caller | Header | Used by | | ---------------- | ----------------------------------------------------------------- | --------------------------------- | | Integrator (you) | `Authorization: Bearer ` or `ApiKey ` | Dashboard, your backend | | Agent (the SDK) | `X-Agent-Auth: :` | `@relayerfi/agent-sdk` at runtime | The HMAC header is built inside `HttpClient.buildAuthHeaders(method, path, body)` — agents never pass the plain `agentId:secret` outside that boundary. ## SDK The agent runtime is `@relayerfi/agent-sdk` — `RelayerSDK` facade, `HttpClient`, `BudgetGuard`, `KillSwitch`, `EventBatcher`, `X402Handler`, `ApprovalHandler`, `x402fetch`, and LLM wrappers for Anthropic / OpenAI / Google. See [Flow Guide](/agent/flow-guide) for the create-agent → fund → spend lifecycle, and [Endpoints](/agent/endpoints) for the API surface. # SDK Reference Source: https://docs.relayer.fi/agent/sdk Exhaustive reference for @relayerfi/agent-sdk — classes, methods, events, errors `@relayerfi/agent-sdk` is the runtime SDK your agent loads. It signs every API call with HMAC-SHA256, enforces 3-layer budgets, polls the kill switch, batches telemetry, and orchestrates x402 payments + human approvals — all behind a small public surface. New to the Agent Kit? Start with [Getting Started](/agent/getting-started) for the full journey from workspace signup to running agent. ## Package | Package | Version | Purpose | | ---------------------- | ------- | ----------------------------------------------------------------------------------------- | | `@relayerfi/agent-sdk` | `1.0.0` | RelayerSDK facade + HMAC, budget, kill switch, x402, approvals, LLM wrappers, Mastra hook | Install: ```bash pnpm theme={null} pnpm add @relayerfi/agent-sdk # Optional, for Mastra integration: pnpm add @mastra/core ``` ```bash npm theme={null} npm install @relayerfi/agent-sdk npm install @mastra/core ``` **Requirements:** Node.js ≥ 22.13.0, ESM, TypeScript ≥ 5.0 for typings. ## Entry points ```typescript theme={null} // Core SDK import { RelayerSDK, x402fetch, wrapAnthropic, wrapOpenAI, wrapGoogle } from "@relayerfi/agent-sdk"; // Errors import { RelayerError, RelayerApiError, BudgetExhaustedError, KillSwitchActiveError, ApiUnreachableError, X402PaymentError, ApprovalTimeoutError, ApprovalRejectedError, } from "@relayerfi/agent-sdk"; // Mastra integration (optional peer dep on @mastra/core) import { relayerMastraHook } from "@relayerfi/agent-sdk/mastra"; ``` *** ## RelayerSDK The main facade. Construct once per agent process and reuse. ### Constructor ```typescript theme={null} new RelayerSDK(config: RelayerSDKConfig) ``` The agent UUID, issued by `POST /v1/agents/confirm-policies` at provisioning time. Typically: `process.env.RELAYER_AGENT_ID`. The agent's HMAC secret, also issued by `POST /v1/agents/confirm-policies` — **shown once**, never retrievable. Typically: `process.env.RELAYER_AGENT_SECRET`. Base URL of the Relayer API. `https://api.relayer.fi` for production, `https://testnet.relayer.fi` for sandbox. Typically: `process.env.RELAYER_API_URL`. Number of retry attempts on HTTP failure. Retries `429` and `5xx`. Non-`429` `4xx` errors never retry (caller bug, not transient). Base backoff in ms. Exponential with jitter, capped at 10s. Per-request timeout in ms (via `AbortSignal.timeout`). How often the kill-switch poller hits `GET /v1/agents/{id}/status`. Lower = faster reaction to a kill, higher = less API noise. How often the event batcher flushes to `POST /v1/agents/events/batch`. Flushes earlier if the buffer reaches `eventBatchSize` or if a `payment` event arrives (immediate). Max events per flush. The batcher splices the whole buffer when flushing. How often `x402fetch` polls `GET /v1/signing/approvals/{id}` while waiting for human approval. Max time to wait for an approval before throwing `ApprovalTimeoutError`. Default 5 minutes. Register `SIGTERM` / `beforeExit` handlers that call `shutdown()` and flush pending events. Disable if you manage shutdown manually. Optional logger with `info(msg, data?)`, `warn(msg, data?)`, `error(msg, data?)`. Undefined by default — no logs are printed. ### Minimal init ```typescript theme={null} import { RelayerSDK } from "@relayerfi/agent-sdk"; export const sdk = new RelayerSDK({ agentId: process.env.RELAYER_AGENT_ID!, secret: process.env.RELAYER_AGENT_SECRET!, apiUrl: process.env.RELAYER_API_URL!, }); ``` The constructor: 1. Validates the 3 required fields (throws synchronously if missing) 2. Builds an `HttpClient` with HMAC-SHA256 signing 3. Starts the kill-switch poller (immediate first call, then interval) 4. Starts the event batcher's interval flush 5. Registers shutdown handlers (unless `autoShutdown: false`) ### Methods #### `checkBudget(): Promise` Checks budget layers **1+2 only** (infra + tokens). Use for telemetry-only operations that should still proceed if only payments are exhausted. ```typescript theme={null} await sdk.checkBudget(); // throws BudgetExhaustedError if infra OR tokens exhausted ``` Implementation: hits `GET /v1/agents/{id}/budget`, throws `BudgetExhaustedError([...failed])` with the array of exhausted layers. Layer 3 (payments) is not checked here. #### `checkPaymentBudget(): Promise` Checks **all 3 layers** plus the kill switch. Use before any USDC outflow. ```typescript theme={null} await sdk.checkPaymentBudget(); // throws KillSwitchActiveError if kill switch is active // throws BudgetExhaustedError(["infra"|"tokens"|"payments"]) if any layer is empty ``` Order: kill switch check first, then budget query. The kill switch is `isBlocked` if `agent.killSwitch === true` **or** the API is unreachable (fail-safe). #### `isKillSwitchActive: boolean` (getter) `true` if the kill switch is currently active OR the API was unreachable on the last poll. ```typescript theme={null} if (sdk.isKillSwitchActive) { // Skip payment operations; safe to continue with infra-only work. } ``` #### `emitEvent(event: RelayerEvent): void` Adds an event to the batch buffer. Returns immediately. The batch flushes: * On the configured interval (default 10s) * When buffer length reaches `eventBatchSize` (default 50) * **Immediately** if `event.type === "payment"` ```typescript theme={null} sdk.emitEvent({ type: "api_call", timestamp: new Date().toISOString(), agentId: sdk.agentId, data: { endpoint: "https://example.com/api", cost_usd: "0.001" }, }); ``` Event types: `payment`, `token_usage`, `llm_call`, `api_call`, `budget_check`, `kill_switch`, `error`. Failure handling inside the batcher: * `429` and `5xx` from event batch endpoint → re-enqueue with 60s backoff * `4xx` (non-429) → drop the batch (permanent failure, caller bug) * Network error / timeout → re-enqueue with 60s backoff #### `createUserWallet(input): Promise<{ wallet_id, addresses }>` Asks the API to stamp a `CREATE_WALLET` activity using the agent's own P-256 key. Requires the agent's policies to include `Allow: Agent — Create Wallets`. ```typescript theme={null} const { wallet_id, addresses } = await sdk.createUserWallet({ walletName: "Operations wallet", // optional — defaults to Solana ED25519 accounts: [{ curve: "ed25519", pathFormat: "...", path: "m/44'/501'/0'/0'", addressFormat: "solana" }], }); ``` Calls `POST /v1/agents/wallets`. The wallet is owned by the agent itself, not by the agent's parent workspace. #### `buildAuthHeaders(method, path, body?): Record` Returns the 4 headers the API expects for HMAC-authenticated requests. Useful if you bypass the SDK's HTTP client (e.g. WebSocket auth): ```typescript theme={null} const headers = sdk.buildAuthHeaders("POST", "/v1/agents/self-bookkeeping", { foo: "bar" }); // { // "x-agent-id": "agt_abc123", // "x-agent-auth": "ad3f...", // hex HMAC-SHA256 // "x-request-timestamp": "1715772600", // "X-SDK-Version": "@relayerfi/agent-sdk@1.0.0" // } ``` **Payload**: `${method}${path}${timestamp}${sha256(body)}`. The signature is HMAC-SHA256 over that string with the agent secret. Empty body → `sha256("")`. The server validates the timestamp window (±60s). #### `http: HttpClient` (getter) Direct access to the underlying HTTP client. Use for endpoints the SDK doesn't expose as first-class methods: ```typescript theme={null} const data = await sdk.http.post("/v1/agents/" + sdk.agentId + "/sign-transaction", { unsignedTx: base64Tx, }); ``` `HttpClient` exposes `get(path)`, `post(path, body)`, `agentId`, `apiUrl`, and `buildAuthHeaders(...)`. #### `agentId: string` and `apiUrl: string` (getters) Convenience accessors: ```typescript theme={null} sdk.agentId; // "agt_abc123" sdk.apiUrl; // "https://api.relayer.fi" ``` #### `budget` (object) Tiny accessor for the cached budget snapshot. The cache is populated automatically when API responses include `budget_status` (server-pushed updates). ```typescript theme={null} sdk.budget.get(); // BudgetStatus | null // sdk.budget.refresh() // not implemented yet — throws NotImplementedError ``` #### `shutdown(): Promise` Stops the kill-switch poll, stops the approval handler, flushes pending events, stops the event batcher. Idempotent. Called automatically on `SIGTERM` and `beforeExit` if `autoShutdown` is `true` (default). ```typescript theme={null} await sdk.shutdown(); ``` *** ## x402fetch A `fetch`-compatible wrapper that ties together: budget check, HMAC auth, x402 detection, payment, approval, and retry. ```typescript theme={null} import { x402fetch } from "@relayerfi/agent-sdk"; const response = await x402fetch(sdk, "https://paid-api.example.com/v1/run", { method: "POST", body: JSON.stringify({ input: "..." }), }); const data = await response.json(); ``` ### What it does, step by step 1. **Budget check** — `sdk.checkPaymentBudget()`. Throws `BudgetExhaustedError` or `KillSwitchActiveError` before any network call. 2. **Auth headers** — builds HMAC headers for the external URL (path-only, since the host is external). 3. **First request** — sends with the original body and headers. 4. **402 handling** — if the server returns `402 Payment Required`, extracts the `paymentRequirement` from the body, calls `POST /v1/agents/{id}/x402-pay`, gets a `paymentHeader`, retries the request with that header. Emits a `payment` event on success. 5. **202 handling** — if the server returns `202 Accepted` with an `approvalId`, suspends and polls `GET /v1/signing/approvals/{id}` every 5s. On approval, retries the original request. On rejection, throws `ApprovalRejectedError`. On timeout, throws `ApprovalTimeoutError`. 6. **Everything else** — passes through unchanged. ### When to use * Calling any third-party API that's x402-gated * Any operation where you want budget + approval + payment in one call If you only need HMAC auth without payment handling, use `sdk.http.post(...)` directly. *** ## LLM Wrappers ES Proxy wrappers that intercept LLM provider calls and emit `llm_call` events with token counts. **Zero overhead until the first LLM call** — the proxy is lazy. ### `wrapAnthropic(client: T, sdk: RelayerSDK): T` ```typescript theme={null} import Anthropic from "@anthropic-ai/sdk"; import { wrapAnthropic } from "@relayerfi/agent-sdk"; const raw = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }); const anthropic = wrapAnthropic(raw, sdk); // Use exactly like the original — emits llm_call events automatically const response = await anthropic.messages.create({ model: "claude-opus-4-5", max_tokens: 1024, messages: [{ role: "user", content: "Hello" }], }); ``` What gets tracked: `provider: "anthropic"`, `model`, `tokens_input`, `tokens_output` extracted from `response.usage`. ### `wrapOpenAI(client: T, sdk: RelayerSDK): T` Same pattern. Intercepts `client.chat.completions.create()` and extracts `response.usage.{prompt_tokens, completion_tokens}`. ### `wrapGoogle(client: T, sdk: RelayerSDK): T` Same pattern. Intercepts the Gemini SDK's generation calls. ### Type safety The wrappers return the **same type** as the input client. So `wrapAnthropic(new Anthropic(...))` returns an `Anthropic` instance from your IDE's perspective. No type juggling. ### Failure mode Tracking errors are swallowed silently — if event emission fails, the LLM call still succeeds. Logged via the SDK logger if one is configured. *** ## Mastra Integration For agents built with [Mastra](https://mastra.ai). The hook plugs into `agent.generate({ onStepFinish })`. ### `relayerMastraHook(sdk: RelayerSDK)` ```typescript theme={null} import { Agent } from "@mastra/core/agent"; import { relayerMastraHook } from "@relayerfi/agent-sdk/mastra"; import { sdk } from "./sdk"; const agent = new Agent({ /* your config */ }); const hook = relayerMastraHook(sdk); await agent.generate("Generate a financial summary", { onStepFinish: hook.onStepFinish, }); ``` Returns `{ onStepFinish: (step) => void }`. Reads `step.usage` (Mastra's standardized usage object) and emits `llm_call` events with provider and model parsed from `step.modelId` (format: `"provider/model"`, e.g. `"anthropic/claude-opus-4-5"`). Tracking errors are swallowed — broken telemetry never breaks the agent. *** ## Errors All SDK errors extend `RelayerError`. Use `instanceof` for dispatch. ```typescript theme={null} import { RelayerError, // base class RelayerApiError, // any non-2xx response with statusCode + endpoint + body BudgetExhaustedError, // failedLayers: ("infra" | "tokens" | "payments")[] KillSwitchActiveError,// payment operations blocked because kill switch is active ApiUnreachableError, // payment operations blocked because API is unreachable (fail-safe) X402PaymentError, // x402 payment failed after retries ApprovalTimeoutError, // approval not resolved within `approvalTimeout` ApprovalRejectedError,// approval was actively rejected by a human approver } from "@relayerfi/agent-sdk"; ``` ### Dispatch example ```typescript theme={null} try { await x402fetch(sdk, url, init); } catch (err) { if (err instanceof BudgetExhaustedError) { console.error(`Budget out on: ${err.failedLayers.join(", ")}`); return; } if (err instanceof KillSwitchActiveError) { console.warn("Agent is killed — stopping"); process.exit(0); } if (err instanceof ApprovalRejectedError) { console.error(`Approval ${err.approvalId} was rejected`); return; } if (err instanceof ApprovalTimeoutError) { console.warn(`Approval ${err.approvalId} timed out — try smaller amount`); return; } if (err instanceof RelayerApiError) { console.error(`API ${err.statusCode} on ${err.endpoint}:`, err.body); return; } throw err; } ``` ### Fail-safe vs fail-open * **Payment operations** (anything that moves USDC) — **fail-safe**: if anything is wrong (budget out, kill switch, API unreachable), the operation is blocked. * **Infrastructure operations** (telemetry, status polls, non-payment API calls) — **fail-open**: if the API is unreachable, the agent continues. Telemetry buffers; reconnects retry automatically. This guarantee is the spine of the Agent Kit: a misbehaving LLM in your agent code cannot bypass payment budgets, even if it has full control of the runtime. *** ## Events The batcher posts events to `POST /v1/agents/events/batch`. Server fills in `timestamp` and `agentId` server-side; the SDK maps to a minimal shape before send. | Type | When emitted | Auto-emitted by | Manual? | | -------------- | --------------------------------------------- | ------------------------------------------------------------------- | ------- | | `payment` | After a successful x402 payment | `x402fetch` | Yes | | `llm_call` | After an LLM call returns | `wrapAnthropic` / `wrapOpenAI` / `wrapGoogle` / `relayerMastraHook` | Yes | | `token_usage` | Alias usable for explicit token accounting | Caller | Yes | | `api_call` | Any non-LLM external HTTP call worth metering | Caller | Yes | | `budget_check` | Diagnostic for budget queries | Caller | Yes | | `kill_switch` | Diagnostic for kill switch state changes | Caller | Yes | | `error` | Caller-chosen error checkpoints | Caller | Yes | Manual emit: ```typescript theme={null} sdk.emitEvent({ type: "api_call", timestamp: new Date().toISOString(), agentId: sdk.agentId, data: { endpoint: "https://example.com/lookup", cost_usd: "0.0008" }, }); ``` *** ## Configuration cheat sheet | Default | Knob | When to change | | -------------------------------- | ------------------------- | ---------------------------------------------------------------------------- | | `retries: 3` | HTTP retry attempts | Lower for hot loops (avoid amplification on outages); higher for batch jobs | | `retryBaseMs: 1_000` | Base backoff | Raise to spread retries across longer windows | | `timeoutMs: 30_000` | Per-request timeout | Lower for snappy UX; raise for large payloads | | `killSwitchPollInterval: 30_000` | Status poll interval | Lower for faster kill reaction (more API noise); raise for quieter agents | | `eventFlushInterval: 10_000` | Telemetry flush cadence | Raise if your telemetry is non-critical; lower if you want faster dashboards | | `eventBatchSize: 50` | Max events per flush | Raise for very chatty agents to reduce flush count | | `approvalPollInterval: 5_000` | Approval queue poll | Raise if approvals are rare; never lower below 5s (API rate limit) | | `approvalTimeout: 300_000` | Approval wait timeout | Match the human approver's response SLA (CFO availability) | | `autoShutdown: true` | Register SIGTERM handlers | Disable if you manage shutdown manually | *** ## Production checklist `RELAYER_AGENT_SECRET` lives in your platform's secret store. Not in `.env` committed to git. Not in environment variables logged by your runtime. Run `POST /v1/agents/{id}/rotate` quarterly (or after any suspected leak). The old secret is revoked instantly; push the new one to your secret store. Pass a `logger` to `RelayerSDK` so kill-switch state changes, retries, and event-batch failures are surfaced. The SDK is silent by default. Poll `GET /v1/agents/{id}/wallet-balance` and alert when it drops below a refill threshold. Top up via `POST /v1/agents/{id}/fund/prepare` + `/fund/confirm`. From the dashboard, kill the agent once in staging. Verify your runtime gracefully stops payment operations within \~30s and that infra operations continue. `approvalThresholdUSDC` should be high enough to not interrupt routine work but low enough that one bad LLM decision is caught. $10–$50 is a reasonable range for many use cases. ## See also The full path from signup to running agent. Agent lifecycle, funding, budget, and approvals — focused on the API endpoints. Every endpoint with interactive playground and cURL / Node.js / Python samples. The three auth modes, including the HMAC payload spec the SDK implements. # Generate metadata for Crosschain Bridge application Source: https://docs.relayer.fi/api-reference/action:-builders/generate-metadata-for-crosschain-bridge-application /api-reference/openapi.json post /action/builders/crosschain-bridge Creates metadata configuration for bridging assets between different blockchain networks. # Generate metadata for Crosschain Transfer application Source: https://docs.relayer.fi/api-reference/action:-builders/generate-metadata-for-crosschain-transfer-application /api-reference/openapi.json post /action/builders/crosschain-transfer Creates metadata configuration for transferring native tokens across different blockchain networks. Converts native currency from source chain to native currency on destination chain (e.g., AVAX -> ETH, AVAX -> MATIC). # Generate metadata for Native Token Transfer application Source: https://docs.relayer.fi/api-reference/action:-builders/generate-metadata-for-native-token-transfer-application /api-reference/openapi.json post /action/builders/transfer-native Creates metadata configuration for transferring native tokens (like AVAX, ETH, MATIC) on a single blockchain network. # Generate metadata for Swap application Source: https://docs.relayer.fi/api-reference/action:-builders/generate-metadata-for-swap-application /api-reference/openapi.json post /action/builders/swap Creates metadata configuration for a token swap application. Currently supports LFJ and Pangolin protocols. The generated metadata can be used to create widgets for token swaps on supported blockchain networks. # Get the Directory data Source: https://docs.relayer.fi/api-reference/action:-directory/get-the-directory-data /api-reference/openapi.json get /action/directory > **Auth: public.** No authentication required. # Retrieve protocol token list for a chain Source: https://docs.relayer.fi/api-reference/action:-tokens/retrieve-protocol-token-list-for-a-chain /api-reference/openapi.json get /action/tokens/{chain}/{protocol} Returns the token list for a given protocol on a specific chain. Use 'all' for the chain parameter to fetch tokens on every supported chain. # Batch ingest agent events (up to 100) Source: https://docs.relayer.fi/api-reference/agents/batch-ingest-agent-events-up-to-100 /api-reference/openapi.json post /agents/events/batch > **Auth: agent HMAC.** Called by the agent SDK at runtime with `x-agent-id`, `x-agent-auth`, `x-request-timestamp` headers. **Not callable from backend ApiKey.** See [Authentication → Agent HMAC](/get-started/authentication). # Get agent budget state (HMAC or API key) Source: https://docs.relayer.fi/api-reference/agents/get-agent-budget-state-hmac-or-api-key /api-reference/openapi.json get /agents/{id}/budget > **Auth: agent HMAC or API key.** Accepts the agent's own HMAC credentials (runtime self-call) **or** the integrator's ApiKey (backend query on the agent's behalf). # Get agent details Source: https://docs.relayer.fi/api-reference/agents/get-agent-details /api-reference/openapi.json get /agents/{id} # Get agent status and kill-switch state (HMAC or API key) Source: https://docs.relayer.fi/api-reference/agents/get-agent-status-and-kill-switch-state-hmac-or-api-key /api-reference/openapi.json get /agents/{id}/status > **Auth: agent HMAC or API key.** Accepts the agent's own HMAC credentials (runtime self-call) **or** the integrator's ApiKey (backend query on the agent's behalf). Returns the agent's current lifecycle status and a `killSwitch` boolean (`true` iff status === 'killed'). Polled every 30s by `@relayerfi/agent-sdk` KillSwitch to gate payment operations. Accepts the agent's own HMAC headers or an integrator API key. # Get agent wallet balance with budget context (HMAC or API key) Source: https://docs.relayer.fi/api-reference/agents/get-agent-wallet-balance-with-budget-context-hmac-or-api-key /api-reference/openapi.json get /agents/{id}/balance > **Auth: agent HMAC or API key.** Accepts the agent's own HMAC credentials (runtime self-call) **or** the integrator's ApiKey (backend query on the agent's behalf). Returns the agent wallet financial snapshot: address, on-chain USDC balance, budget committed/free amounts, runway estimation, and a Solana explorer URL. Use this from the agent SDK to check fund availability before a payment, or from your backend to monitor agent health. Replaces the deprecated GET /wallet and /wallet-balance. # List all agents for the integrator Source: https://docs.relayer.fi/api-reference/agents/list-all-agents-for-the-integrator /api-reference/openapi.json get /agents # Pause an agent (reversible) Source: https://docs.relayer.fi/api-reference/agents/pause-an-agent-reversible /api-reference/openapi.json post /agents/{id}/pause # Resume a paused agent Source: https://docs.relayer.fi/api-reference/agents/resume-a-paused-agent /api-reference/openapi.json post /agents/{id}/resume # Sign a Solana transaction for an agent Source: https://docs.relayer.fi/api-reference/agents/sign-a-solana-transaction-for-an-agent /api-reference/openapi.json post /agents/{id}/sign-transaction > **Auth: agent HMAC.** Called by the agent SDK at runtime with `x-agent-id`, `x-agent-auth`, `x-request-timestamp` headers. **Not callable from backend ApiKey.** See [Authentication → Agent HMAC](/get-started/authentication). # Widget Actions — overview Source: https://docs.relayer.fi/api-reference/flows/actions-intro The metadata + catalog endpoints that power the Widget Kit. Use these to configure widget instances dynamically and discover what protocols/tokens are supported. These endpoints power the [Widget Kit](/widget/overview). They split into two families: `POST /action/builders/*` — return a **JSON configuration** the widget renders as an Action (swap, transfer, cross-chain transfer, cross-chain bridge). Useful when you want your backend to dynamically configure widget instances per user, per region, or per offer. `GET /action/directory` and `GET /action/tokens/{chain}/{protocol}` — discover what protocols and tokens are supported. Used internally by the widget to populate pickers; also useful for "what can my user do?" backend checks. ## What metadata generators actually return Despite the URL prefix `builders/`, these endpoints **do not build transactions** — they return widget configuration JSON: ```json theme={null} { "success": true, "metadata": { /* widget-renderable Action schema */ } } ``` The widget consumes the `metadata` payload to render the swap/transfer/bridge UI for the end user. Signing and broadcasting happen via the widget's signing flow, not via these endpoints. ## Why this isn't named "Builders" anymore The URL prefix `/action/builders/*` predates the consolidation of ActionKit into Widget Kit. The endpoints generate **widget metadata**, not transaction calldata. We've kept the URL stable to avoid breaking existing integrations, but the docs label reflects what they actually do. ## Not covered here * **Direct DEX swap execution** (`/action/execute/swap/*`) — these exist and use the modern passkey-stamped activity pattern, but they require a passkey ceremony in the browser on the `confirm` step. The API Reference documents only endpoints a backend can complete with the api key alone, so these live in the [Widget Kit](/widget/overview) — it owns the passkey UX end-to-end. * **Lending / yield / earn** — not exposed via API at this time. * **Approval flows** (ERC-20 allowances) — handled inside the widget. If you build your own UX from the metadata, you handle approvals yourself. ## Related * [Widget Kit overview](/widget/overview) — the prebuilt UI consumer for these endpoints. * [Widget SDK reference](/widget/sdk) — JS/TS methods for embedding. * [Auth & Custody model](/shared/auth-model) — explains why end-user actions need a passkey on the device. # Pay for an x402-gated resource Source: https://docs.relayer.fi/api-reference/flows/agent-x402-pay POST /v1/agents/{id}/x402-pay The agent pays for a remote resource that requires USDC via the x402 protocol. Budget-checked, kill-switch-gated, atomically enforced. This is the **payment primitive** of the Agent Kit. The agent presents a payment requirement (received from a 402-responding service), the API validates against all 3 budget layers + the kill switch, executes the on-chain USDC transfer, and returns a payment header the agent can attach to retry the original request. Most users should use the SDK's `x402fetch()` helper instead of calling this endpoint directly. `x402fetch` handles the full 402 → pay → retry loop transparently. ## When to call this directly vs use `x402fetch` | Use case | Recommended | | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | | Calling a third-party API that may return 402 | **`x402fetch`** — handles the loop for you | | Building a custom client (non-Node.js, no SDK) | **This endpoint** — replicate the loop yourself | | Paying for a resource without retrying (e.g. pre-paying for batch credit) | **This endpoint** — caller controls what to do with the payment header | | Inspecting payment intent before executing | **This endpoint** — you can re-render the requirement to the user before submission | ## How the loop works (with `x402fetch`) ```mermaid theme={null} sequenceDiagram participant Agent as Agent Runtime participant Remote as Remote Remote participant API as Relayer API Agent->>Remote: Original request (no payment) Remote-->>Agent: 402 + paymentRequirement Agent->>API: POST /agents/{id}/x402-pay { paymentRequirement } Note over API: Check budgets + kill switch Note over API: Execute USDC transfer API-->>Agent: { paymentHeader, transactionId } Agent->>Remote: Original request + X-Payment header Remote-->>Agent: 200 + content ``` The whole loop is one `await x402fetch(sdk, url, init)` call in the SDK. This page covers what happens server-side during the middle step. ## Authentication This endpoint accepts **either** auth mode: * **HMAC** (`X-Agent-Auth` + `x-agent-id` + `x-request-timestamp`) — when called by the agent SDK at runtime. See [Authentication](/get-started/authentication) for the payload spec. * **API key** (`Authorization: ApiKey ...`) — when called by your backend on behalf of the agent. The `{id}` path param must match the calling agent (if HMAC) or be a valid agent in the API key's workspace (if API key). ## Request body The payment terms received in the 402 response from the resource. Schema below. The `paymentRequirement` object: Decimal USDC amount as a string (e.g. `"0.05"`). String to avoid float precision issues. Always `"USDC"` in v1. Reserved for future expansion. Stablecoin network. Today: `"solana"`. EVM chain support is planned. The address that should receive the USDC. The API validates this matches the 402 response's intent. Optional human-readable description from the resource. Logged in the audit trail. Optional URL of the resource being paid for. Logged in the audit trail. ## Response The opaque value to attach to your retry request as the `X-Payment` header. The resource will validate it and serve the protected content. Optional. Set if the payment resulted in an on-chain transaction (vs. a pre-funded credit pull). Use with chain explorers to confirm. ## What the API checks before executing 1. **Authenticates** the request (HMAC or API key). 2. **Looks up** the agent and validates it's not `killed` or `paused`. 3. **Polls** the kill switch state — refuses on active kill switch. 4. **Validates** all 3 budget layers atomically (Lua script in Redis): * `infra` — does the agent have infrastructure budget left? * `tokens` — does the agent have LLM token budget left? (informational here) * `payments` — does the requested `amount` fit under `monthlyUSDC` AND `perTxUSDC`? 5. **Checks approval threshold**: if `amount >= approvalThresholdUSDC`, holds the payment and returns **HTTP 202** with `{ approvalId }` instead of executing. Wait for human approval, then retry. 6. **Validates wallet balance**: rejects if the agent wallet doesn't have enough USDC. 7. **Executes** the on-chain USDC transfer atomically. 8. **Emits** a `payment` event to the agent event stream (visible at `GET /v1/agents/{id}/audit`). 9. **Returns** the payment header. The budget deduction (step 4) is **atomic**: even if the agent makes 10 parallel `x402-pay` calls, the Lua deduction ensures no double-spend past the cap. The cap cannot be bypassed by code path manipulation in the agent. ## Approval gate — HTTP 202 If the payment amount triggers the approval threshold, the response is: ```json theme={null} { "success": true, "statusCode": 202, "data": { "approvalId": "apr_xyz789", "status": "pending_approval", "expiresAt": "2026-05-15T12:05:00.000Z" } } ``` The SDK's `x402fetch` handles this transparently — it polls `/v1/signing/approvals/{id}` every 5s until resolved or `approvalTimeout` (default 5 minutes), then retries the original request. If you're calling this endpoint directly, you must implement the same loop. Approvals can resolve in one of three states: * `approved` — proceed with the original retry using the payment header * `rejected` — surface the error to your agent's reasoning; do not retry * `expired` — same as rejected; the threshold was set for a reason ## Common errors | Status | Cause | Fix / Recovery | | --------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------- | | `401 invalid_auth` | HMAC headers missing or signature mismatch | Verify the SDK is correctly initialized; check secret rotation | | `401 agent_killed` | Agent in `killed` state | Provision a new agent; this one is dead | | `402 budget_exhausted` | One or more budget layers empty. Response body includes `failedLayers` | Top up the budget via `POST /v1/agents/{id}/budget` and retry | | `402 insufficient_wallet_balance` | Budget OK but wallet has no USDC | Fund via `POST /v1/agents/{id}/fund/prepare` + `/fund/confirm` | | `503 kill_switch_active` | Global kill switch tripped | Wait for resolution; do not retry until kill switch clears | | `202 approval_required` | Above threshold | Poll the approval ID | ## Working with `paymentRequirement` The exact format of `paymentRequirement` depends on the 402-responding service. The SDK normalizes common variants: * HTTP 402 with `WWW-Authenticate: x402` header * HTTP 402 with `paymentRequirement` JSON body * HTTP 402 with `Payment-Required` body field If you're calling this endpoint without the SDK, parse the 402 response yourself and pass the requirement here as-is. ## Recommended pattern Use the SDK's `x402fetch` wrapper. It handles 402 detection, this endpoint call, header injection, and retry transparently — your agent code calls a normal `fetch` and never deals with payment plumbing directly. See [SDK Reference — `x402fetch`](/agent/sdk#x402fetch). ## Next steps * [SDK Reference — `x402fetch`](/agent/sdk#x402fetch) — the recommended way to do this * [Agent Kit Flow Guide](/agent/flow-guide) — full agent lifecycle context * `POST /v1/agents/{id}/budget` — adjust spend limits if you hit them often * `POST /v1/agents/{id}/fund/prepare` — top up the agent wallet # Set up a payout account — Step 1 Source: https://docs.relayer.fi/api-reference/flows/payout-setup POST /v1/payout/accounts/setup/liquidation-address Step 1 of the 2-step payout account setup. Creates a crypto withdrawal address linked to a recipient's bank account. This endpoint is **step 1 of the 2-step payout account setup** for off-ramp settlement. It links a recipient's bank account to a crypto withdrawal address. When stablecoins land at this address, the off-ramp transfer to the recipient's bank is triggered automatically. Creates or returns the withdrawal address for `(beneficiaryId, beneficiaryAccountId, chain, currency)`. Idempotent — calling twice returns the same address. Creates a fiat virtual account (e.g. a SPEI CLABE in Mexico) tied to the withdrawal address. Returns the account number your client deposits fiat into. Also idempotent. Use `POST /v1/payout/accounts/quote` and `POST /v1/payout/accounts/execute` to move money. Track via `GET /v1/orders/{orderId}`. Both setup endpoints are **idempotent**. You can call them on every order without worrying about duplicates. This is the recommended pattern: don't cache setup IDs in your backend, just call setup before each payment. ## Prerequisites * Workspace KYB completed (one-time, via dashboard) * Recipient created (`POST /v1/payout/recipients`) * Recipient's bank account added (`POST /v1/payout/recipients/{id}/accounts` or via invite link) ## Request body Recipient ID (from `POST /v1/payout/recipients`). The recipient's bank account ID (from `POST /v1/payout/recipients/{id}/accounts`). Stablecoin chain. Supported: `base`, `polygon`, `arbitrum`, `solana`. More on request. Stablecoin symbol on the chosen chain. Supported: `usdc`, `usdt`. ## Response Internal withdrawal address ID. You generally don't need to store this — call setup again to retrieve. The crypto address. Send stablecoins here to trigger an off-ramp settlement to the recipient's bank. Echo of the chain you requested. Echo of the stablecoin you requested. Each `(beneficiaryAccountId, chain, currency)` triple maps to **one withdrawal address forever**. Different recipients get different addresses. Don't reuse addresses across recipients. ## What happens after a deposit? When stablecoins arrive at the returned `address`, the off-ramp partner detects the deposit, settles the on-chain leg, and initiates the fiat transfer to the recipient's bank. Track the full lifecycle as an order: ``` GET /v1/orders — list all orders GET /v1/orders/{orderId} — get a single order with timeline GET /v1/payout/recipients/{id}/orders — orders for this recipient ``` Typical timeline: stablecoin deposit detected → confirmed on-chain → fiat transfer submitted → fiat received at recipient bank. End-to-end takes 1–2 business days via SPEI. ## Authentication `Authorization: ApiKey rk_client_key_v1_...` — workspace key with `payout` module enabled and `payout:write` scope. ## Common errors | Status | Cause | Fix | | ------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------ | | `400` | Unsupported `chain` + `currency` combination | See `/v1/action/tokens/{chain}/usdc` for supported pairs | | `403` | `payout` module not active for workspace | Activate Payout Kit in the dashboard | | `404` | `beneficiaryId` or `beneficiaryAccountId` not found, or belongs to another workspace | Cross-tenant lookups always return 404 (no existence leak) | | `422` | Recipient's bank account is not yet usable (pending registration) | Wait for the rails partner to confirm the bank account, then retry | ## Next steps * `POST /v1/payout/accounts/setup/virtual-account` — Step 2: create the fiat virtual account * [Payout Kit Flow Guide](/payout/flow-guide) — full end-to-end narrative with code samples * `POST /v1/payout/accounts/quote` then `POST /v1/payout/accounts/execute` — operate once setup is complete # Set up a payout account — Step 2 Source: https://docs.relayer.fi/api-reference/flows/payout-virtual-account POST /v1/payout/accounts/setup/virtual-account Step 2 of the 2-step payout setup. Creates a fiat virtual account (CLABE) tied to the withdrawal address. This endpoint **closes the 2-step payout account setup**. After you created a withdrawal address in step 1, this creates a permanent fiat virtual account (e.g. a CLABE in Mexico) that lets your client deposit fiat to trigger an off-ramp settlement. Created the crypto withdrawal address for `(beneficiaryId, beneficiaryAccountId, chain, currency)`. Returns the fiat virtual account (CLABE for SPEI, account number + routing for ACH, IBAN for SEPA) backed by the withdrawal address. `POST /v1/payout/accounts/quote` to get the rate, then `POST /v1/payout/accounts/execute` to trigger settlement. Track via `GET /v1/orders/{orderId}`. ## Prerequisites * The recipient exists (`POST /v1/payout/recipients`) * The recipient's bank account is registered (`POST /v1/payout/recipients/{id}/accounts`) * Step 1 (withdrawal address) succeeded for this `(beneficiaryId, beneficiaryAccountId)` pair If step 1 hasn't run for this pair, this endpoint returns `400`. ## Request body The recipient (from `POST /v1/payout/recipients`). The recipient's bank account ID. Must be the same pair you used in step 1. ## Response Internal virtual account ID. You don't need to store it — call setup again to retrieve. The 18-digit SPEI CLABE for Mexican fiat deposits. Share this with your client. Fiat currency. Today: `MXN` (CLABE/SPEI). Other rails (ACH USD, SEPA EUR) will surface here as those go live. Echo of the recipient. This call is **idempotent** — calling it twice with the same `(beneficiaryId, beneficiaryAccountId)` returns the same CLABE. Don't cache; just call setup before each payment if you want to be sure the account is current. ## What happens after a deposit When your client wires MXN to the returned CLABE: 1. The fiat rails partner detects the deposit and credits the workspace's float 2. An on-chain transfer of equivalent stablecoin is initiated from the withdrawal address 3. The stablecoin lands at the on-chain off-ramp partner, triggering the bank wire to the recipient 4. An order is created in `/v1/orders` with status transitioning: `awaiting_funds` → `funds_received` → `payment_submitted` → `completed` End-to-end settlement: typically 1–2 business days via SPEI. ## Authentication `Authorization: ApiKey rk_client_key_v1_...` — workspace key with `payout` module enabled and write scope. ## Common errors | Status | Cause | Fix | | ------ | --------------------------------------------------------------------------------- | ----------------------------------------------------- | | `400` | No withdrawal address exists for the `(beneficiaryId, beneficiaryAccountId)` pair | Run step 1 (`/setup/liquidation-address`) first | | `403` | Workspace lacks `payout` module or write scope | Activate Payout Kit in dashboard; check API key scope | | `404` | `beneficiaryId` or `beneficiaryAccountId` not found | Cross-tenant lookups return 404 (no existence leak) | | `422` | Recipient's bank account is not yet usable (pending rails partner approval) | Wait for the registration to confirm; retry | ## Next steps After step 2 you're operational. Recommended next reads: * `POST /v1/payout/accounts/quote` — get the exchange rate and fees for a payment * `POST /v1/payout/accounts/execute` — trigger settlement * `GET /v1/orders/{orderId}` — track the full timeline * [Payout Kit Flow Guide](/payout/flow-guide) — narrative end-to-end # API Reference Source: https://docs.relayer.fi/api-reference/introduction REST endpoints for backend integration. Interactive playground, 17 languages, sandbox + production. The Relayer API is a single REST surface. Every endpoint has its own page in the sidebar with an interactive playground that hits the live sandbox or production environment. ## Environments ``` https://testnet.relayer.fi/v1 ``` Use the sandbox for development. Same endpoints, same payloads — testnet wallets, no real fiat. ``` https://api.relayer.fi/v1 ``` Mainnet wallets, regulated fiat rails, real settlement. Use production once you've validated your integration in the sandbox. The playground in the sidebar lets you switch between environments per request. ## Authentication The API supports three authentication modes. **Which one you use depends on who is calling.** Your backend → Relayer. `Authorization: ApiKey rk_...` Agent SDK → Relayer. Four headers, HMAC-SHA256 signature. Browser → Relayer dashboard. `Authorization: Bearer ` See [Authentication](/get-started/authentication) for the full matrix, header formats, and security best practices. ## Response envelope Every response — success or error — uses the same shape so your client can always extract the same fields. ```json theme={null} { "success": true, "message": "Wallet created", "data": { "walletId": "wallet_abc123", "name": "My Wallet", "createdAt": "2026-05-15T12:00:00.000Z" }, "statusCode": 201, "timestamp": "2026-05-15T12:00:00.000Z", "path": "/v1/signing/wallets/confirm" } ``` ```json theme={null} { "success": false, "message": "Validation failed: amount must be positive", "statusCode": 400, "timestamp": "2026-05-15T12:00:00.000Z", "path": "/v1/payout/accounts/quote" } ``` ```json theme={null} { "success": true, "data": [ /* items */ ], "meta": { "page": 1, "limit": 50, "total": 247, "totalPages": 5 }, "statusCode": 200, "timestamp": "2026-05-15T12:00:00.000Z", "path": "/v1/orders" } ``` For the full error catalog (status codes, common business errors, retry guidance), see [Error Reference](/shared/error-reference). ## Idempotency Mutation endpoints that can be retried safely accept an `Idempotency-Key` header: ``` Idempotency-Key: ``` We store the result for 24 hours. Retrying the same key returns the original response without re-executing the side effect. Endpoints that are **idempotent by design** (no key needed) are marked in their summary — e.g. `POST /v1/payout/offramp/withdraw-addresses` returns the existing record on conflict. ## Rate limits | Tier | Limit | | -------------------------------------- | ------------------------------- | | Standard endpoints | 100 requests/minute per API key | | Financial endpoints (signing, payouts) | 20 requests/minute per API key | When exceeded, the API returns `429 Too Many Requests`. Back off with exponential delay (1s, 2s, 4s, 8s) and retry. ## How endpoints are organized The sidebar groups endpoints by **resource**, not by Kit. If you want to read a wallet balance, you look under **Wallets** — not under "Balance" or "Platform". If you want to assign a tool to an agent, you look under **Agents → Tools**. Every verb sits on its noun. Create, list, get, read balance, view activity. Generate addresses derived from a wallet's root key. List pending, broadcast signed transactions, retry failures, cancel before broadcast. Lifecycle, read, control, funding, runtime (HMAC), tools. Metadata generators + protocol/token catalog — what the Widget Kit consumes. Recipient CRUD, invites, bank-account registration. Quote → setup → execute the sandwich payment flow (off-ramp). Fiat deposit accounts (virtual accounts, SPEI, etc.) for on-ramping. For end-user flows that require a passkey ceremony (wallet creation, transaction signing, agent provisioning), see the [Signing Kit](/signing/overview), [Agent Kit](/agent/overview), and [Widget Kit](/widget/overview) — those Kits cover the full UX. ## Try it from the playground Every endpoint page has an **interactive playground** on the right side: Switch between Sandbox and Production from the server dropdown above the request panel. Paste your API key into the `Authorization` field. It prefixes `ApiKey` automatically. Path, query, and body parameters are auto-populated from the schema. Edit them inline. The response panel shows the real response, the status code, and copy-able cURL / language snippets for 17 languages (bash, Node.js, Python, Go, Java, Ruby, PowerShell, Swift, C#, .NET, TypeScript, Kotlin, Rust, Dart, C, C++, PHP). The playground sends real requests. Use the sandbox while exploring — production calls move real money. ## Use this documentation with AI This documentation is **MCP-ready**. AI tools like Claude Code, Cursor, ChatGPT, and Windsurf can connect directly to it and query endpoints, params, and examples without leaving your terminal or editor. Add Relayer docs to Claude Code, Cursor, or any MCP-compatible tool with one command. Click the **Ask AI** button in the page header to send the current page as context to your preferred LLM. The contextual menu (top of every page) also exposes **Copy as Markdown**, **View as Markdown**, and direct deeplinks to ChatGPT / Claude / Perplexity / Cursor / VS Code with the page content prefilled. ## Get help * **Email**: [dev@relayer.fi](mailto:dev@relayer.fi) * **GitHub**: [github.com/relayerfi](https://github.com/relayerfi) * **Status**: live on the production base URL — `GET /v1/` returns service health # Execute a fiat payment Source: https://docs.relayer.fi/api-reference/payout:-accounts/execute-a-fiat-payment /api-reference/openapi.json post /payout/accounts/execute Creates a pending fiat payment record. Automatically provisions a liquidation address and virtual account if they do not exist for this beneficiary/currency pair. # Get a payment quote (MXN → USD) Source: https://docs.relayer.fi/api-reference/payout:-accounts/get-a-payment-quote-mxn-→-usd /api-reference/openapi.json post /payout/accounts/quote Returns an estimated exchange rate and fee breakdown. Rates update every ~30 seconds — this is not a locked quote. # Get payment status by reference Source: https://docs.relayer.fi/api-reference/payout:-accounts/get-payment-status-by-reference /api-reference/openapi.json get /payout/accounts/{reference}/status # List all payments for the authenticated integrator Source: https://docs.relayer.fi/api-reference/payout:-accounts/list-all-payments-for-the-authenticated-integrator /api-reference/openapi.json get /payout/accounts Returns all fiat payments sorted by newest first, with beneficiary name. # Create or get a deposit account (idempotent) Source: https://docs.relayer.fi/api-reference/payout:-on-ramp/create-or-get-a-deposit-account-idempotent /api-reference/openapi.json post /payout/onramp/deposit-accounts Idempotent on (integrator_id, wallet_id, source_currency, destination_currency). Returns the existing deposit account if one matches, otherwise provisions a Bridge Virtual Account whose destination = the requested wallet and persists the result. Per ONR-04 + Phase 11 polish (multi-stablecoin). # Get a single deposit account by id Source: https://docs.relayer.fi/api-reference/payout:-on-ramp/get-a-single-deposit-account-by-id /api-reference/openapi.json get /payout/onramp/deposit-accounts/{id} Returns the full deposit account record (including source_deposit_instructions). Used by the form to re-hydrate the drawer when the user lands on /apps?flow=onramp&deposit_account=. Cross-tenant ids surface 404. # Get an on-ramp quote (fiat → stablecoin) Source: https://docs.relayer.fi/api-reference/payout:-on-ramp/get-an-on-ramp-quote-fiat-→-stablecoin /api-reference/openapi.json post /payout/onramp/quote Returns the live exchange rate from Bridge plus the developer_fee_percent applied to the conversion. Bridge's 0.75% commercial fee is BAKED INTO sell_rate (per Bridge docs); this endpoint surfaces the developer fee separately so it can be displayed transparently to the end user. Public — no auth required. # List lifecycle events for a deposit account Source: https://docs.relayer.fi/api-reference/payout:-on-ramp/list-lifecycle-events-for-a-deposit-account /api-reference/openapi.json get /payout/onramp/deposit-accounts/{id}/events Returns normalized lifecycle events for the deposit account, ordered by occurred_at ASC (oldest first). Events are sourced from the existing bridge-webhook ingestion (virtual_account.activity). Cross-tenant ids surface 404. Per ONR-05. # List my deposit accounts Source: https://docs.relayer.fi/api-reference/payout:-on-ramp/list-my-deposit-accounts /api-reference/openapi.json get /payout/onramp/deposit-accounts Returns every deposit account scoped to the calling integrator, newest first. Lets the form render a "Recent deposits" surface so users can return to any prior VA and copy instructions again. Cross-tenant isolation enforced by integrator_id filter at the service layer. # Broadcast a raw signed transaction Source: https://docs.relayer.fi/api-reference/transactions/broadcast-a-raw-signed-transaction /api-reference/openapi.json post /transactions/broadcast/raw Broadcasts a raw signed transaction directly to the blockchain without saving it. Use this for transactions signed outside of this API. # Broadcast a signed transaction Source: https://docs.relayer.fi/api-reference/transactions/broadcast-a-signed-transaction /api-reference/openapi.json post /transactions/broadcast/{transactionId} Broadcasts a previously signed transaction to the blockchain. Only transactions belonging to the integrator can be broadcasted. The transaction must be in pending status and not expired. # Cancel a pending transaction Source: https://docs.relayer.fi/api-reference/transactions/cancel-a-pending-transaction /api-reference/openapi.json delete /transactions/sign/{transactionId} Cancels a pending transaction. Only pending transactions can be cancelled. Only transactions belonging to the integrator can be cancelled. # Get broadcast status Source: https://docs.relayer.fi/api-reference/transactions/get-broadcast-status /api-reference/openapi.json get /transactions/broadcast/{transactionId} Retrieves the status of a broadcasted transaction including confirmations. Only transactions belonging to the integrator can be accessed. # Get signed transaction details Source: https://docs.relayer.fi/api-reference/transactions/get-signed-transaction-details /api-reference/openapi.json get /transactions/sign/{transactionId} Retrieves details of a signed transaction. Only transactions belonging to the integrator can be accessed. # List signed transactions Source: https://docs.relayer.fi/api-reference/transactions/list-signed-transactions /api-reference/openapi.json get /transactions/sign Lists signed transactions for the integrator. Can be filtered by status. # List transactions awaiting passkey signature Source: https://docs.relayer.fi/api-reference/transactions/list-transactions-awaiting-passkey-signature /api-reference/openapi.json get /transactions/pending-signature Returns transactions with awaiting_signature status for the integrator. These are transactions that have been prepared (or approved) and are waiting for the integrator to sign with their passkey. # Retry broadcasting a failed transaction Source: https://docs.relayer.fi/api-reference/transactions/retry-broadcasting-a-failed-transaction /api-reference/openapi.json post /transactions/broadcast/{transactionId}/retry Retries broadcasting a transaction that previously failed. Only failed transactions can be retried. Only transactions belonging to the integrator can be retried. # AI Assistants Source: https://docs.relayer.fi/get-started/ai-assistants Connect Relayer docs to Claude Code, Cursor, ChatGPT, and any MCP-compatible tool The Relayer documentation ships with first-class AI integration. You don't need to copy/paste pages into a chat anymore — your AI assistant can query the docs directly as a tool. Built into Mintlify: an MCP server, contextual menus, and `llms.txt` — all already live for this site. ## Why connect via MCP The **Model Context Protocol** lets AI clients (Claude Code, Cursor, ChatGPT, Windsurf, Devin, VS Code) search and read this documentation **as a tool call** during a conversation. Concretely: * Your AI assistant has up-to-date answers about every endpoint, every parameter, every flow — not outdated training data * The assistant decides when to consult the docs; you don't have to attach pages manually * Auth, search, and full-page retrieval all work out of the box ## The MCP server URL ``` https://docs.relayer.fi/mcp ``` This URL exposes two tools to any connected client: * **search** — finds relevant snippets across all docs, returns titles + links * **query docs filesystem** — retrieves full page content with shell-style commands ## Connect to Claude Code Run this in your terminal: ```bash theme={null} claude mcp add --transport http relayer-docs https://docs.relayer.fi/mcp ``` That's it. New Claude Code sessions will automatically discover and use the Relayer docs as a tool. Verify it's connected: ```bash theme={null} claude mcp list ``` ## Connect to Cursor Add this to your `~/.cursor/mcp.json` (or the project's `.cursor/mcp.json` for per-repo scope): ```json theme={null} { "mcpServers": { "relayer-docs": { "url": "https://docs.relayer.fi/mcp" } } } ``` Restart Cursor. The MCP server appears in Settings → MCP. ## Connect to VS Code VS Code's MCP integration is configured via the same `mcp.json` format inside your workspace: ```json theme={null} { "mcpServers": { "relayer-docs": { "url": "https://docs.relayer.fi/mcp" } } } ``` Or use the **Connect to VS Code** button in the contextual menu on any page — it opens VS Code with the server preinstalled. ## Connect to ChatGPT / Claude / Perplexity / Grok These web-based assistants don't use MCP directly. Instead, the documentation gives them context **per page** through the contextual menu: Navigate to the page you want to ask about. You'll see options for **Copy as Markdown**, **View as Markdown**, and direct deeplinks to ChatGPT / Claude / Perplexity / Grok / Google AI Studio. The chosen tool opens with the current page already attached as context. Ask your question — the assistant has the page content available immediately. ## Use `llms.txt` Every Mintlify-hosted docs site exposes a curated index of LLM-friendly Markdown at: ``` https://docs.relayer.fi/llms.txt ``` Feed this URL to any LLM workflow that wants a complete machine-readable summary of the documentation tree. It's an indexed table of contents with every page link in plain text — ideal for retrieval-augmented generation pipelines or one-shot doc ingestion. For full page content, individual pages are available as Markdown at `.md`: ``` https://docs.relayer.fi/api-reference/introduction.md https://docs.relayer.fi/signing/flow-guide.md ``` ## What the assistant can answer well Once connected, your AI tool can answer questions like: * *"How do I create an agent with a \$500 monthly USDC budget?"* — it queries `/agent/flow-guide` and `POST /v1/agents/{id}/budget` * *"What headers does the agent SDK send?"* — it pulls `get-started/authentication` * *"Generate a Node.js snippet to prepare and confirm a Solana transaction"* — it combines the signing flow guide with the endpoint params * *"Which endpoints in Payout are public vs dashboard-only?"* — it consults the API Reference tags and the architecture page ## What it can't answer (yet) The MCP server returns what's in the docs. It doesn't have access to: * Your workspace's specific API keys, agents, or wallet IDs (use the dashboard for those) * Live data from your account (balances, orders, etc.) — for that, the assistant would need to call the API itself with your credentials * Information not published in this site (internal roadmap, pricing for enterprise tiers, etc.) ## Get help * **MCP issues**: try `claude mcp remove relayer-docs` and re-add. If problems persist, [open a support email](mailto:dev@relayer.fi). * **AI assistants giving wrong answers**: this likely means the docs themselves are unclear or stale — please report at [dev@relayer.fi](mailto:dev@relayer.fi) and we'll fix the page. ## Next Steps Make your first API call in under 5 minutes. Browse the full endpoint surface with an interactive playground. # Authentication Source: https://docs.relayer.fi/get-started/authentication Three auth modes — API key for your backend, HMAC for agents, session JWT for the dashboard The Relayer API supports **three authentication modes**. Which one you use depends on who is making the call. **Caller**: your backend. Header: `Authorization: ApiKey rk_...` **Caller**: an agent at runtime. Four headers, HMAC-SHA256 signed. **Caller**: browser session in the Relayer dashboard. Header: `Authorization: Bearer ` For most B2B integrations you only need the **API Key** mode. Use HMAC if you're deploying an AI agent that calls the API at runtime. Session JWT is for the Relayer-hosted dashboard. ## 1. API Key (backend → Relayer) This is the primary mode for any server-side integration. Your backend authenticates to Relayer with a long-lived API key tied to your workspace. ### Key format ``` rk_client_key_v1_ ``` Generate keys from the dashboard. You can issue multiple keys per workspace (e.g., one per environment), each with its own IP allowlist and scope. ### Making a request Pass the key in the `Authorization` header with the `ApiKey` scheme: ```bash cURL theme={null} curl -X GET https://testnet.relayer.fi/v1/signing/wallets \ -H "Authorization: ApiKey rk_client_key_v1_your_key_here" \ -H "Content-Type: application/json" ``` ```typescript Node.js theme={null} const response = await fetch( "https://testnet.relayer.fi/v1/signing/wallets", { method: "GET", headers: { Authorization: `ApiKey ${process.env.RELAYER_API_KEY}`, "Content-Type": "application/json", }, }, ); ``` ```python Python theme={null} import os, requests response = requests.get( "https://testnet.relayer.fi/v1/signing/wallets", headers={ "Authorization": f"ApiKey {os.environ['RELAYER_API_KEY']}", "Content-Type": "application/json", }, ) ``` Keep your API key secret. Never expose it in frontend code, public repositories, or client-side JavaScript. Store it in environment variables or your platform's secret manager. ### Scopes API keys have scopes that limit what they can call: | Scope | What it accesses | | ------------------ | ----------------------------------------------------------------------- | | `integrator` | Standard SDK-grade endpoints (signing, payouts, agents, widgets) | | `integrator:read` | Read-only subset | | `integrator:write` | Write-only subset | | `internal` | Workspace-management endpoints (issued to Relayer's own dashboard only) | If you receive `403 Forbidden — Insufficient permissions`, your key's scope doesn't cover the endpoint you called. *** ## 2. Agent HMAC (agent SDK → Relayer) Agents authenticate with a **per-agent secret** that is provisioned at agent-creation time. The Relayer Agent SDK (`@relayerfi/agent-sdk`) signs every request automatically — you should not handcraft these headers unless you're writing your own client. ### Headers Every agent request must include: The agent's UUID (issued by `POST /v1/agents/confirm-policies`). The HMAC-SHA256 signature of the canonical request, hex-encoded. Unix epoch seconds. Must be within **±60 seconds** of server time. Optional. SDK version string (e.g. `@relayerfi/agent-sdk@0.4.1`) for telemetry. ### Signature payload The signed string is: ``` ${METHOD}${path}${timestamp}${sha256(body)} ``` * `METHOD` — uppercase HTTP method (`POST`, `GET`, …) * `path` — request path including `/v1` prefix (e.g. `/v1/agents/{id}/x402-pay`) * `timestamp` — same value as `x-request-timestamp` * `sha256(body)` — hex-encoded SHA-256 of the JSON body. Use `sha256('')` for empty bodies. Sign with HMAC-SHA256 using the agent secret. Result is the value of `x-agent-auth`. ### Reference implementation (for client authors) ```typescript Node.js theme={null} import crypto from "node:crypto"; function buildAgentAuthHeaders( agentId: string, agentSecret: string, method: string, path: string, body?: unknown, ) { const timestamp = Math.floor(Date.now() / 1000).toString(); const bodyHash = crypto .createHash("sha256") .update(body && Object.keys(body).length > 0 ? JSON.stringify(body) : "") .digest("hex"); const payload = `${method.toUpperCase()}${path}${timestamp}${bodyHash}`; const signature = crypto.createHmac("sha256", agentSecret).update(payload).digest("hex"); return { "x-agent-id": agentId, "x-agent-auth": signature, "x-request-timestamp": timestamp, "x-sdk-version": "custom/1.0", }; } ``` ```python Python theme={null} import hashlib, hmac, json, time def build_agent_auth_headers(agent_id: str, agent_secret: str, method: str, path: str, body: dict | None): timestamp = str(int(time.time())) body_str = json.dumps(body) if body else "" body_hash = hashlib.sha256(body_str.encode()).hexdigest() payload = f"{method.upper()}{path}{timestamp}{body_hash}" signature = hmac.new(agent_secret.encode(), payload.encode(), hashlib.sha256).hexdigest() return { "x-agent-id": agent_id, "x-agent-auth": signature, "x-request-timestamp": timestamp, "x-sdk-version": "custom/1.0", } ``` ### Failure modes | Response | Cause | | ----------------------- | --------------------------------------------------------------------------- | | `401 invalid_auth` | Missing one of the required headers, agent not found, or signature mismatch | | `401 expired_timestamp` | Timestamp drift exceeds 60 seconds | | `401 agent_killed` | The agent's status is `killed` — re-provision before retrying | In production, use `@relayerfi/agent-sdk` instead of building the headers yourself. The SDK handles signing, retries, kill-switch polling, and budget enforcement for you. *** ## 3. Session JWT (browser → Relayer dashboard) The Relayer dashboard (relayer.fi/app) authenticates browser sessions with a JWT in the `Authorization: Bearer` header. This mode is **for the dashboard product itself** — you only need it if you're building a UI that proxies the user's session JWT. ``` Authorization: Bearer ``` For external integrations, prefer the API Key mode above. *** ## Environments Relayer provides two environments: | Environment | Base URL | Purpose | | -------------- | ------------------------------- | ----------------------- | | **Sandbox** | `https://testnet.relayer.fi/v1` | Testing and development | | **Production** | `https://api.relayer.fi/v1` | Live operations | Both environments use the same auth scheme and endpoints. Workspace IDs, API keys, and agents are environment-scoped — a sandbox key won't authenticate against production. ## Response envelope Every response — success or error — uses the same envelope: ```json theme={null} { "success": true, "message": "Success", "data": { }, "statusCode": 200, "timestamp": "2026-05-15T12:00:00.000Z", "path": "/v1/signing/wallets" } ``` ```json theme={null} { "success": false, "message": "Authorization header is missing", "statusCode": 401, "timestamp": "2026-05-15T12:00:00.000Z", "path": "/v1/signing/wallets" } ``` ## Common auth errors | Status | Meaning | Fix | | ----------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------ | | `401 Unauthorized` | Missing or malformed key/JWT/HMAC | Verify header format. ApiKey requires the `ApiKey` prefix — `Bearer` is for JWT only | | `403 Forbidden` | Key is valid but scope doesn't cover this endpoint | Use a key with the correct scope, or check that the endpoint isn't internal-only | | `404 Not Found` | API key does not exist in this environment | Sandbox keys don't work against production and vice versa | | `401 expired_timestamp` | Agent HMAC timestamp drift > 60s | Sync your clock (NTP); regenerate timestamp on retry | See [Error Reference](/shared/error-reference) for the full catalog. ## Security best practices Store keys and agent secrets in `.env` files or your platform's secret manager (Vercel, AWS Secrets Manager, Doppler). Never hardcode them. Never include an `ApiKey` in client-side JavaScript, mobile apps, or browser requests. For browser-side use, generate a short-lived client key from the dashboard with limited scope. Rotate API keys every 90 days, or immediately if a leak is suspected. Old keys can be revoked from the dashboard. Per-key IP allowlists are configurable from the dashboard. Restrict production keys to your backend's egress IPs. Agent secrets can be rotated with `POST /v1/agents/{id}/rotate` — the old secret is revoked instantly. Run this on a schedule for long-running agents. ## Next Steps Make your first API call in under 5 minutes. Connect Relayer docs to Claude Code, Cursor, ChatGPT, and other AI tools via MCP. # Changelog Source: https://docs.relayer.fi/get-started/changelog Release history for the Relayer API This changelog covers API and platform changes from v1.0 onward. Changes are grouped by milestone. Breaking changes are marked with a warning. ## v2.8 — Curated Widget Kit, Agent Kit GA, Exchange Kit removed **Released:** 2026-05-15 The platform consolidates around four Kits — Signing, Agent, Payout, Widget — and ships Agent Kit as a first-class product. ### Added * **Agent Kit (`/v1/agents/*`)** — Budget-enforced AI agents with USDC wallets on Solana and x402 payments. SDK: `@relayerfi/agent-sdk`. * **Payout: On-ramp** (`/v1/payout/onramp/*`) and **Off-ramp** (`/v1/payout/offramp/*`) — directional fiat ↔ stablecoin flows, formerly bundled in Accounts. * **Payout: Orders** (`/v1/orders`) — unified order tracking across all rails (list, get, cancel). * **Signing - Passkeys** and **Signing - Recovery** — explicit endpoints for WebAuthn registration and email-based recovery. * **AI-ready docs** — the documentation site exposes an MCP server at `/mcp`, an `llms.txt` index, and contextual deeplinks to ChatGPT / Claude / Cursor / VS Code. ### Changed (breaking) The Exchange Kit (`/v1/exchange/*`) has been removed entirely. Operators who used OTC quotes/trades should migrate to direct stablecoin → fiat flows via the Payout Kit. Widget creation is now **curated** — the `POST /v1/action`, `PATCH /v1/action/{id}`, and `POST /v1/action/widgets/swap` endpoints are no longer publicly accessible. Use the four canonical builders (`/v1/action/builders/{swap,transfer-native,crosschain-transfer,crosschain-bridge}`) instead. * The legacy `POST /v1/transactions/sign/transfer/{native,token}` endpoints were removed. Use `POST /v1/transactions/prepare` + `POST /v1/transactions/confirm` (passkey-stamped) instead. * Workspace-management endpoints (`/v1/integrators/*`) are now `INTERNAL` scope — accessible from the dashboard only. *** ## v2.0 — Routes, Roles, Modules & Permissions **Released:** 2026-03-25 Role-based access control, dynamic sidebar, and team management. Introduced workspace-scoped permissions and multi-team support. ### Added * **RBAC guard** — API endpoints are now protected by role-based access control. Requests require an API key with sufficient permissions for the target resource. * **Dynamic sidebar** — The dashboard sidebar is built from the workspace's active modules and role permissions, not a static list. * **Team management** — Workspace owners can invite team members, assign roles (Admin, Manager, Viewer), and control module access per team. * **Module permissions** — Each Kit module can be enabled or disabled per workspace. * **Route restructuring** — All API routes are now under `/v1/{kit}/{resource}` with consistent naming across Kits. ### Changed * Auth guard applied globally — previously some endpoints were unprotected. * Signing wallet endpoints moved from `/v1/wallets` to `/v1/signing/wallets`. *** ## v1.2 — Platform Rename **Released:** 2026-03-25 The platform was renamed from Sherry to Relayer. Repository, package, and API references updated. ### Changed * `sherry-api` → `relayer-api` * `sherry-dashboard` → `relayer-dashboard` * Base production URL changed from `https://api.sherry.fi` to `https://api.relayer.fi` * npm packages renamed to `@relayer/*` namespace If you were using `https://api.sherry.fi`, update your base URL to `https://api.relayer.fi`. Old URLs are not redirected. *** ## v1.1 — Cleanup & Module Mapping **Released:** 2026-03-25 Internal module paths cleaned up. No breaking changes to the external API. ### Changed * Internal module structure reorganized: `common/` → `api/`, `shared/` → `blockchain/` * Action Kit split: GTM (go-to-market) logic separated from core Action Kit * Rewards endpoint moved to Action Kit from a standalone module *** ## v1.0 — Kit Architecture **Released:** 2026-03-21 Initial Kit-based architecture. The Relayer API is organized into purpose-built Kits, each covering a distinct domain. ### Added * **Signing Kit** (`/v1/signing/*`, `/v1/transactions/*`) — Self-custodial wallets, transaction prepare/confirm, passkey-based signing * **Payout Kit** (`/v1/payout/*`, `/v1/orders`) — Fiat on/off-ramp rails, recipient management, settlement tracking * **Agent Kit** (`/v1/agents/*`) — Budget-enforced AI agents with USDC wallets and x402 payments * **Widget Kit** — Embeddable React components for canonical protocols (swap, transfer, crosschain) * Consistent API response envelope: `{ success, message, data, statusCode, timestamp, path }` * API key authentication via `Authorization: ApiKey ` header # What is Relayer? Source: https://docs.relayer.fi/get-started/overview Self-custodial stablecoin infrastructure — built for the operators of today and the agents of tomorrow Relayer is the financial platform for **stablecoin operators today, and the AI agents they'll deploy tomorrow**. Both layers run on the same self-custodial backend, with the same wallet workspaces, the same compliance, and the same budget enforcement. ## The two layers ### Today — stablecoin operators If your business moves money in stablecoins — OTC desks, payroll, B2B remittance, treasury, payment processors — you need a backend that gives you: * **Self-custodial wallets** with passkey-based signing, so you never hold raw private keys and your users sign every transaction with biometrics * **Regulated fiat rails** with virtual accounts (CLABE/SPEI in Mexico, ACH in the US, more coming), KYB built in, recipient onboarding handled * **A workspace** where your team has individual passkeys, signing policies, approval thresholds, and full audit trail This is what the [Signing Kit](/signing/overview) and [Payout Kit](/payout/overview) deliver. You can ship a compliant stablecoin product without writing a single line of custody, KYB, or compliance glue. ### Tomorrow — AI agents that hold wallets Your team will eventually deploy AI agents alongside human operators. Those agents will need to spend money — LLM tokens, third-party APIs, on-chain payments, x402-gated resources — and they'll need a wallet to do it from. The hard problem is **budget control**: how do you let an LLM-driven agent transact autonomously without risking a runaway spend? The [Agent Kit](/agent/overview) solves it with three layers of budget enforcement (infrastructure, tokens, payments) plus a kill switch, all enforced atomically server-side. **If the SDK says "no budget", the payment does not happen.** No prompt-injection escape, no model-side override. The agent's wallet is the same primitive as your team's wallet. The signing policy that protects your CFO's wallet also protects your agent's. The approval flow that gates your operations team's high-value transactions also gates the agent's. **One platform, two operators: humans and machines.** ## Shared primitives What makes both layers work on the same backend: | Primitive | Used by humans for | Used by agents for | | -------------------- | ------------------------------------- | -------------------------------------------- | | **Wallet workspace** | Team's funds, with team passkeys | Agent's funds, with HMAC-signed runtime auth | | **Signing policies** | Restrict where the team can send | Restrict where the agent can send | | **Approvals** | CFO signs off on high-value transfers | CFO signs off on agent's threshold spends | | **Budgets** | Per-wallet caps | Per-agent caps across 3 layers | | **Orders** | Track every fiat settlement | Track every x402 payment | | **Audit log** | Compliance and review | Same compliance and review | ## Kit architecture You only integrate the Kits you need. They share authentication, workspace, and audit infrastructure underneath. ```mermaid theme={null} graph LR A[Your Backend / Dashboard] --> B[Relayer API /v1] F[Your Frontend / Widget] --> B G[AI Agent SDK] --> B B --> C[Signing Kit] B --> D[Payout Kit] B --> E[Agent Kit] B --> H[Widget Kit] ``` Self-custodial wallets, prepare/confirm signing, passkeys, recovery, policies, approvals. Fiat on/off-ramp rails, virtual accounts, recipient management, and unified orders. Budget-enforced AI agents with USDC wallets, signing, and x402 payments. Embeddable React components for canonical protocols (swap, transfer, crosschain). ## API design All endpoints follow consistent patterns: * **REST + JSON** — standard HTTP methods and JSON request/response bodies * **Versioned** — all endpoints are prefixed with `/v1` * **Consistent envelope** — every response uses the same structure: ```json theme={null} { "success": true, "message": "Success", "data": { }, "statusCode": 200, "timestamp": "2026-03-28T12:00:00.000Z", "path": "/v1/signing/wallets" } ``` * **Three auth modes** — `Authorization: ApiKey ` for backend integrations, `Authorization: Bearer ` for browser callers from the dashboard, and `X-Agent-Auth: ` for runtime calls from the agent SDK. See [Authentication](/get-started/authentication) for the full matrix. ## Next Steps Set up your API key, understand the three auth modes, and learn about environments. Make your first API call in under 5 minutes. # Quickstart Source: https://docs.relayer.fi/get-started/quickstart Make your first API call in under 5 minutes This guide walks you through making your first Relayer API call. You will verify connectivity with the health endpoint, then make an authenticated request. ## Prerequisites * An API key from your operator dashboard (see [Authentication](/get-started/authentication)) * `curl` or Node.js installed ## Environments Relayer provides two environments. Choose the one that matches your stage: | Environment | Base URL | Use for | | -------------- | ------------------------------- | ----------------------- | | **Sandbox** | `https://testnet.relayer.fi/v1` | Testing and development | | **Production** | `https://api.relayer.fi/v1` | Live operations | Sandbox runs on Render — the first request after idle may take 30-60 seconds to cold-start. ## Step 1: Verify connectivity Start with the unauthenticated root endpoint to confirm the API is reachable. ```bash cURL theme={null} curl https://testnet.relayer.fi/v1/ ``` ```typescript Node.js theme={null} const response = await fetch("https://testnet.relayer.fi/v1/"); const data = await response.json(); console.log(data); ``` ```bash cURL theme={null} curl https://api.relayer.fi/v1/ ``` ```typescript Node.js theme={null} const response = await fetch("https://api.relayer.fi/v1/"); const data = await response.json(); console.log(data); ``` You should receive a `200` with a basic service descriptor. ## Step 2: Set up your environment Store your API key and base URL as environment variables: ```bash Terminal theme={null} export RELAYER_BASE_URL="https://testnet.relayer.fi/v1" export RELAYER_API_KEY="rk_client_key_v1_your_key_here" ``` ```text .env file theme={null} RELAYER_BASE_URL=https://testnet.relayer.fi/v1 RELAYER_API_KEY=rk_client_key_v1_your_key_here ``` ```bash Terminal theme={null} export RELAYER_BASE_URL="https://api.relayer.fi/v1" export RELAYER_API_KEY="rk_client_key_v1_your_production_key" ``` ```text .env file theme={null} RELAYER_BASE_URL=https://api.relayer.fi/v1 RELAYER_API_KEY=rk_client_key_v1_your_production_key ``` ## Step 3: Make an authenticated request Now make an authenticated call. This example lists the wallets in your workspace — a safe, read-only operation that confirms auth, scope, and module activation in one go. ```bash cURL theme={null} curl -X GET $RELAYER_BASE_URL/signing/wallets \ -H "Authorization: ApiKey $RELAYER_API_KEY" \ -H "Content-Type: application/json" ``` ```typescript Node.js theme={null} const baseUrl = process.env.RELAYER_BASE_URL; const apiKey = process.env.RELAYER_API_KEY; const response = await fetch(`${baseUrl}/signing/wallets`, { headers: { Authorization: `ApiKey ${apiKey}`, "Content-Type": "application/json", }, }); const result = await response.json(); console.log(JSON.stringify(result, null, 2)); ``` ```python Python theme={null} import os, requests base_url = os.environ["RELAYER_BASE_URL"] api_key = os.environ["RELAYER_API_KEY"] response = requests.get( f"{base_url}/signing/wallets", headers={ "Authorization": f"ApiKey {api_key}", "Content-Type": "application/json", }, ) print(response.json()) ``` A successful response looks like: ```json theme={null} { "success": true, "message": "Wallets retrieved", "data": [], "statusCode": 200, "timestamp": "2026-05-15T12:00:00.000Z", "path": "/v1/signing/wallets" } ``` An empty `data` array is expected on a fresh workspace. It means the API authenticated your request successfully — you just haven't created any wallets yet. ## Step 4: Explore the Kits Now that you are authenticated, explore the Kit that matches your use case: Create self-custodial wallets and sign transactions with passkeys. Set up fiat on/off-ramp rails and manage recipients. Ship budget-enforced AI agents that can sign and pay for resources. Embed canonical swap, transfer, and crosschain widgets in your app. ## Troubleshooting | Problem | Solution | | -------------------- | ------------------------------------------------------------------------------------------------------------ | | `Connection refused` | Check `RELAYER_BASE_URL` — sandbox: `https://testnet.relayer.fi/v1`, production: `https://api.relayer.fi/v1` | | `401 Unauthorized` | Verify your `Authorization` header uses `ApiKey` prefix (not `Bearer`) | | `404 Not Found` | Check the endpoint path starts with `/v1/` | | Slow first response | Sandbox cold-starts after idle (30-60s). Production does not have this delay. | # SDKs Source: https://docs.relayer.fi/get-started/sdks Published TypeScript/React packages for the Relayer platform — what each one does and when to use it. Relayer ships five public packages on npm. Pick the ones that match what you're building. ## At a glance | Package | Use it when | Detail | | ---------------------------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------- | | [`@relayerfi/widget-kit-react`](#widget-kit-react) | You're building a React app and want the prebuilt swap / transfer / bridge UI | React component library | | [`@relayerfi/widget-kit-native`](#widget-kit-native) | Same, but for React Native | Native components | | [`@relayerfi/widget-kit-core`](#widget-kit-core) | You're building a custom widget for a framework that isn't React | Framework-agnostic core logic | | [`@relayerfi/action-kit`](#action-kit) | You're generating widget action metadata server-side or want to validate metadata schemas | Metadata builder + validator | | [`@relayerfi/agent-sdk`](#agent-sdk) | You're running an AI agent that needs budget enforcement and x402 payments | Agent runtime SDK | ***

Widget Kit (React)

The prebuilt React widget for end-user swap, transfer, and bridge flows. Renders the UI, manages wagmi connectivity, and orchestrates signing in both Metadata Mode (user's own wallet) and Passkey Signing Mode. ```bash theme={null} npm install @relayerfi/widget-kit-react wagmi viem @tanstack/react-query framer-motion ``` See [Widget Kit → Installation](/widget/installation) and [Widget Kit → Integration](/widget/integration) for setup.

Widget Kit (Native)

The same UI patterns as Widget Kit (React) but for React Native — drop into your mobile app. ```bash theme={null} npm install @relayerfi/widget-kit-native ```

Widget Kit (Core)

Framework-agnostic core logic shared by `widget-kit-react` and `widget-kit-native`. Use directly if you're building a custom widget for Vue, Svelte, or any other framework that isn't covered. ```bash theme={null} npm install @relayerfi/widget-kit-core ```

Action Kit

TypeScript library for building and validating widget action metadata. Defines the action schema, validates payloads before submission, and provides template helpers for common parameter types (token amounts, chain selectors, recipient addresses). ```bash theme={null} npm install @relayerfi/action-kit viem ``` See [Widget Kit → SDK reference](/widget/sdk) for the full API.

Agent SDK

The runtime SDK for AI agents you operate via Relayer. Handles three-layer budget enforcement (infra / tokens / payments), x402 payment execution, kill-switch polling, and event reporting. Mastra-compatible. ```bash theme={null} npm install @relayerfi/agent-sdk ``` See [Agent Kit → Getting Started](/agent/getting-started) and [Agent Kit → SDK reference](/agent/sdk). *** ## Source code All packages are open source under the relayerfi GitHub organization: Mono-repo for `@relayerfi/widget-kit-react`, `widget-kit-core`, `widget-kit-native`, and `action-kit`. Source for `@relayerfi/agent-sdk` lives in `packages/` of the Relayer monorepo. # Payout Endpoints Source: https://docs.relayer.fi/payout/endpoints Accounts (virtual accounts, payments), on-ramp, off-ramp, recipients, and orders reference Reference for all Payout Kit endpoints. For interactive schemas and try-it functionality, see the [API Reference](/api-reference/introduction). Business onboarding (KYB) is completed through the Relayer dashboard before you can call any of these endpoints. Once your workspace is approved, every endpoint below is available. ## Payout Accounts Account setup and payment execution for the cross-currency flow (stablecoin → fiat). ### Step 1 — Create Withdrawal Address (Liquidation Address) Link a recipient's bank account to a crypto chain for off-ramp. Idempotent — returns the existing address if already configured for this recipient. ``` POST /v1/payout/accounts/setup/liquidation-address ``` **Request body:** ```json theme={null} { "beneficiaryId": "ben_abc123", "beneficiaryAccountId": "acc_xyz789", "chain": "polygon", "currency": "usdc" } ``` | Field | Type | Description | | ---------------------- | ------ | -------------------------------------------- | | `beneficiaryId` | string | Relayer recipient ID | | `beneficiaryAccountId` | string | Recipient's bank account ID | | `chain` | string | Blockchain network (e.g., `polygon`, `base`) | | `currency` | string | Stablecoin (e.g., `usdc`, `usdt`) | **Response:** ```json theme={null} { "success": true, "data": { "id": "liq_def456", "address": "0xWithdrawalAddress", "chain": "polygon", "currency": "usdc" } } ``` *** ### Step 2 — Create Virtual Account Create an MXN SPEI virtual account with a CLABE tied to the withdrawal address. Requires a withdrawal address to exist first. Idempotent. ``` POST /v1/payout/accounts/setup/virtual-account ``` **Request body:** ```json theme={null} { "beneficiaryId": "ben_abc123", "beneficiaryAccountId": "acc_xyz789" } ``` **Response:** ```json theme={null} { "success": true, "data": { "id": "va_ghi789", "clabe": "646180123456789012", "currency": "MXN", "beneficiaryId": "ben_abc123" } } ``` The `clabe` is the SPEI account number your client deposits MXN to. *** ### Get Payment Quote Get the exchange rate and fees for a payment before executing (MXN → USD). ``` POST /v1/payout/accounts/quote ``` **Request body:** ```json theme={null} { "beneficiaryId": "ben_abc123", "amount": 1000, "currency": "MXN" } ``` **Response:** ```json theme={null} { "success": true, "data": { "quoteId": "qte_jkl012", "amount": 1000, "currency": "MXN", "rate": 17.25, "fees": 2.50, "netAmount": 997.50, "expiresAt": "2026-03-28T12:10:00.000Z" } } ``` *** ### Execute Payment Trigger a payment using an accepted quote. Initiates off-ramp settlement. ``` POST /v1/payout/accounts/execute ``` **Request body:** ```json theme={null} { "quoteId": "qte_jkl012", "beneficiaryId": "ben_abc123" } ``` Payment execution is irreversible once initiated. Verify quote and recipient details before calling this endpoint. *** ### List Payments ``` GET /v1/payout/accounts ``` Returns all payments for the authenticated workspace. ### Get Payment Status by Reference ``` GET /v1/payout/accounts/{reference}/status ``` *** ## On-ramp (Fiat → Stablecoin) Move fiat from a client deposit into a stablecoin wallet. ### Get On-ramp Quote ``` POST /v1/payout/onramp/quote ``` Returns the current fiat → stablecoin rate, fees, and net amount. ### Create / Get Deposit Account Create a permanent fiat deposit account (e.g. a CLABE) tied to a stablecoin wallet. Idempotent. ``` POST /v1/payout/onramp/deposit-accounts ``` ### List Deposit Accounts ``` GET /v1/payout/onramp/deposit-accounts ``` ### Get a Deposit Account ``` GET /v1/payout/onramp/deposit-accounts/{id} ``` ### List Lifecycle Events Track deposit, conversion, and settlement events for a single deposit account. ``` GET /v1/payout/onramp/deposit-accounts/{id}/events ``` *** ## Off-ramp (Stablecoin → Fiat) Move stablecoins from a deposit into a recipient's bank account. ### Get Off-ramp Quote ``` POST /v1/payout/offramp/quote ``` Returns the current stablecoin → fiat rate, fees, and net amount. ### Create / Get Withdrawal Address Create a permanent crypto withdrawal address linked to a recipient's bank account. Idempotent. ``` POST /v1/payout/offramp/withdraw-addresses ``` ### Get a Withdrawal Address ``` GET /v1/payout/offramp/withdraw-addresses/{id} ``` ### List Drain History Stablecoin deposits to the withdrawal address that triggered fiat settlements. ``` GET /v1/payout/offramp/withdraw-addresses/{id}/drains ``` *** ## Recipients (Beneficiaries) Recipient and bank account management. ### Create Recipient Create a recipient record. Stored in Relayer — no fiat-rails call at creation. ``` POST /v1/payout/recipients ``` **Request body:** ```json theme={null} { "name": "Maria Garcia", "email": "maria@example.com", "ownerType": "individual", "address": { "street": "Paseo de la Reforma 123", "city": "Mexico City", "country": "MX", "postalCode": "06600" } } ``` | Field | Type | Description | | ----------- | ------------------------------ | ---------------------- | | `name` | string | Recipient display name | | `email` | string | Contact email | | `ownerType` | `"individual"` \| `"business"` | Entity type | | `address` | object | Physical address | ### List Recipients ``` GET /v1/payout/recipients ``` ### List Recipients with Bank Accounts Returns recipients with their linked bank accounts embedded — single query, no N+1. ``` GET /v1/payout/recipients/with-accounts ``` ### Update Recipient Update the name and/or email of a recipient. At least one field is required. ``` PATCH /v1/payout/recipients/{id} ``` ### Update Recipient Status Archive or restore a recipient. Archived recipients cannot receive payouts. ``` PATCH /v1/payout/recipients/{id}/status ``` **Request body:** ```json theme={null} { "status": "archived" } ``` Valid values: `active`, `archived`. ### Get Invite Link ``` GET /v1/payout/recipients/{id}/invite ``` ### Generate Invite Link Create a new invite token (valid 7 days). Replaces any existing invite. ``` POST /v1/payout/recipients/{id}/invite ``` ### Add Bank Account Register a bank account with the fiat rails partner and link it to the recipient. One recipient can have multiple accounts. ``` POST /v1/payout/recipients/{id}/accounts ``` **Request body (US ACH example):** ```json theme={null} { "accountNumber": "1234567890", "routingNumber": "021000021", "accountType": "checking", "bankName": "Bank of America" } ``` `account_number`, `clabe`, and `iban` are immutable once set. Use `PATCH /payout/recipients/{id}/accounts/{accountId}` to update other fields. ### Update Bank Account ``` PATCH /v1/payout/recipients/{id}/accounts/{accountId} ``` Editable fields: `routing_number`, `checking_or_savings`, `address`. Immutable fields: `account_number`, `clabe`, `iban`. ### List Bank Accounts ``` GET /v1/payout/recipients/{id}/accounts ``` ### Recent Orders for a Recipient ``` GET /v1/payout/recipients/{id}/orders ``` *** ## Orders Every transfer is represented as an order. The orders endpoints are unified across all rails — list, get, and cancel are the same regardless of direction. ### List Orders ``` GET /v1/orders ``` ### Get Order ``` GET /v1/orders/{id} ``` ### Cancel an Awaiting Order Idempotent. Only valid while the order is still in `awaiting` status. ``` POST /v1/orders/{id}/cancel ``` *** ## Full API Reference All endpoints include interactive schemas and a try-it playground in the [API Reference](/api-reference/introduction). Payout endpoints are grouped under **Payout: Accounts**, **Payout: On-ramp**, **Payout: Off-ramp**, **Payout: Recipients**, and **Payout: Orders**. # Payout Flow Guide Source: https://docs.relayer.fi/payout/flow-guide Execute an end-to-end fiat payout — from recipient onboarding to bank settlement This guide walks through a complete off-ramp payout: onboarding a recipient, setting up their virtual account, getting a quote, and executing a payment that settles to their bank. ## Prerequisites * A Relayer API key (see [Authentication](/get-started/authentication)) * Your workspace activated with the Payout module enabled * KYB completed for your workspace (handled through the Relayer dashboard — one-time setup) ```bash Environment theme={null} export RELAYER_API_KEY="rk_client_key_v1_your_key_here" export RELAYER_BASE_URL="https://testnet.relayer.fi/v1" # sandbox; use https://api.relayer.fi/v1 for production ``` Business onboarding (KYB) is completed through the dashboard before you can use the Payout API. Once approved, every endpoint below becomes available. ## Steps Register the person or business you want to pay. This creates a recipient record in Relayer — no fiat-rails call yet. ```bash cURL theme={null} curl -X POST $RELAYER_BASE_URL/v1/payout/recipients \ -H "Authorization: ApiKey $RELAYER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Maria Garcia", "email": "maria@example.com", "ownerType": "individual", "address": { "street": "Paseo de la Reforma 123", "city": "Mexico City", "country": "MX", "postalCode": "06600" } }' ``` ```typescript Node.js theme={null} const response = await fetch(`${RELAYER_BASE_URL}/v1/payout/recipients`, { method: "POST", headers: { "Authorization": `ApiKey ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ name: "Maria Garcia", email: "maria@example.com", ownerType: "individual", address: { street: "Paseo de la Reforma 123", city: "Mexico City", country: "MX", postalCode: "06600", }, }), }); const { data } = await response.json(); const beneficiaryId = data.id; ``` Generate an invite link. The recipient visits this URL to submit their bank details securely. The token is valid for 7 days. ```bash cURL theme={null} curl -X POST $RELAYER_BASE_URL/v1/payout/recipients/$BENEFICIARY_ID/invite \ -H "Authorization: ApiKey $RELAYER_API_KEY" ``` ```typescript Node.js theme={null} const response = await fetch( `${RELAYER_BASE_URL}/v1/payout/recipients/${beneficiaryId}/invite`, { method: "POST", headers: { "Authorization": `ApiKey ${apiKey}` }, } ); const { data } = await response.json(); // data.inviteUrl — send this to the recipient console.log(`Send invite to recipient: ${data.inviteUrl}`); ``` Once the recipient submits their bank details, the account is registered with the regulated fiat rails partner. You can list bank accounts at any time via `GET /v1/payout/recipients/{id}/accounts`. If you already have the recipient's bank details on file, you can skip the invite and call `POST /v1/payout/recipients/{id}/accounts` directly. Link the recipient's bank account to a crypto chain. Stablecoins sent to this address trigger the off-ramp transfer. This call is idempotent. ```bash cURL theme={null} curl -X POST $RELAYER_BASE_URL/v1/payout/accounts/setup/liquidation-address \ -H "Authorization: ApiKey $RELAYER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "beneficiaryId": "ben_abc123", "beneficiaryAccountId": "acc_xyz789", "chain": "polygon", "currency": "usdc" }' ``` ```typescript Node.js theme={null} const response = await fetch( `${RELAYER_BASE_URL}/v1/payout/accounts/setup/liquidation-address`, { method: "POST", headers: { "Authorization": `ApiKey ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ beneficiaryId: "ben_abc123", beneficiaryAccountId: "acc_xyz789", chain: "polygon", currency: "usdc", }), } ); const { data } = await response.json(); const withdrawalAddress = data.address; ``` Create an MXN SPEI virtual account tied to the withdrawal address. Returns the CLABE your client uses for fiat deposits. This call is idempotent. ```bash cURL theme={null} curl -X POST $RELAYER_BASE_URL/v1/payout/accounts/setup/virtual-account \ -H "Authorization: ApiKey $RELAYER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "beneficiaryId": "ben_abc123", "beneficiaryAccountId": "acc_xyz789" }' ``` ```typescript Node.js theme={null} const response = await fetch( `${RELAYER_BASE_URL}/v1/payout/accounts/setup/virtual-account`, { method: "POST", headers: { "Authorization": `ApiKey ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ beneficiaryId: "ben_abc123", beneficiaryAccountId: "acc_xyz789", }), } ); const { data } = await response.json(); console.log(`Client CLABE: ${data.clabe}`); ``` Share the CLABE with your client. When they deposit MXN to this CLABE, the funds settle through the withdrawal address to the recipient's bank. Before executing a settlement, get a quote to confirm the rate and fees. ```bash cURL theme={null} curl -X POST $RELAYER_BASE_URL/v1/payout/accounts/quote \ -H "Authorization: ApiKey $RELAYER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "beneficiaryId": "ben_abc123", "amount": 1000, "currency": "MXN" }' ``` ```typescript Node.js theme={null} const response = await fetch(`${RELAYER_BASE_URL}/v1/payout/accounts/quote`, { method: "POST", headers: { "Authorization": `ApiKey ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ beneficiaryId: "ben_abc123", amount: 1000, currency: "MXN", }), }); const { data } = await response.json(); // data.rate — exchange rate applied // data.fees — settlement fees // data.netAmount — recipient receives this amount const quoteId = data.quoteId; ``` Trigger the settlement. This initiates the off-ramp transfer to the recipient's bank. ```bash cURL theme={null} curl -X POST $RELAYER_BASE_URL/v1/payout/accounts/execute \ -H "Authorization: ApiKey $RELAYER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "quoteId": "qte_ghi789", "beneficiaryId": "ben_abc123" }' ``` ```typescript Node.js theme={null} const response = await fetch(`${RELAYER_BASE_URL}/v1/payout/accounts/execute`, { method: "POST", headers: { "Authorization": `ApiKey ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ quoteId: "qte_ghi789", beneficiaryId: "ben_abc123", }), }); const { data } = await response.json(); // data.reference — payment reference for tracking // data.status — "pending" initially console.log(`Payment initiated: ${data.reference}`); ``` Payment execution is irreversible once initiated. Confirm the quote details and recipient bank account before calling this endpoint. Settlement typically completes within 1-2 business days via SPEI. Every payment is represented as an order. Track status via the unified orders endpoint: ```bash cURL theme={null} curl $RELAYER_BASE_URL/v1/orders/$ORDER_ID \ -H "Authorization: ApiKey $RELAYER_API_KEY" ``` To list recent orders for this recipient: ```bash cURL theme={null} curl $RELAYER_BASE_URL/v1/payout/recipients/$BENEFICIARY_ID/orders \ -H "Authorization: ApiKey $RELAYER_API_KEY" ``` You can also check payment status by reference: ```bash cURL theme={null} curl $RELAYER_BASE_URL/v1/payout/accounts/$REFERENCE/status \ -H "Authorization: ApiKey $RELAYER_API_KEY" ``` ## On-ramp variant (fiat → stablecoin) If instead of off-ramp you want to receive stablecoins from a fiat deposit, use the on-ramp endpoints: ``` POST /v1/payout/onramp/quote — Get fiat → stablecoin rate POST /v1/payout/onramp/deposit-accounts — Create or get a deposit account (idempotent) GET /v1/payout/onramp/deposit-accounts/{id}/events — Track lifecycle events ``` The deposit account is a permanent fiat account (e.g. a CLABE in Mexico) tied to a stablecoin wallet. When the client deposits fiat, the stablecoin lands at the wallet. ## Cancelling an order An order in `awaiting` status can be cancelled before settlement: ``` POST /v1/orders/{id}/cancel ``` The call is idempotent — calling it twice on the same awaiting order is safe. Once the order has moved past `awaiting`, cancellation is no longer possible. ## Next Steps Full endpoint reference for accounts, on/off-ramp, recipients, and orders. Interactive schemas and try-it playground. Settlement, on-ramp, off-ramp, and other domain terms. API error codes and handling patterns. # Payout Kit Source: https://docs.relayer.fi/payout/overview Fiat on/off-ramp — virtual accounts, recipient management, and settlement The Payout Kit gives operators regulated fiat rails for moving money between stablecoins and bank accounts. Clients deposit stablecoins to a deposit account and receive fiat at the recipient bank — or deposit fiat and receive stablecoins at a withdrawal address. You manage the full cycle: business onboarding, recipient configuration, account setup, and settlement execution. ## Core Concepts ### Off-ramp and On-ramp Two directions, same primitives: * **Off-ramp** — stablecoins in, fiat out. Money flows from a withdrawal address to a recipient's bank account. * **On-ramp** — fiat in, stablecoins out. Money flows from a deposit account (e.g. a CLABE) to a stablecoin wallet. Both flows use quote → setup → execute as the canonical lifecycle. ### Business Onboarding (KYB) Before sending or receiving fiat, your workspace must complete KYB. KYB is a one-time setup managed through the Dashboard. Once approved, your workspace can create deposit accounts, withdrawal addresses, and process transfers. ### Recipients (Beneficiaries) A **recipient** (beneficiary) is the person or business you want to pay — identified by name, address, and one or more bank accounts. Recipients are managed under `/v1/payout/recipients`. Recipient lifecycle: 1. Create the recipient record (name, address, owner type) 2. Generate an invite link — the recipient submits their bank details via a hosted form 3. Add the bank account to the recipient (registered with the fiat rails partner) 4. Recipient is ready to receive payouts ### Withdrawal Addresses (Off-ramp) A **withdrawal address** is a crypto address linked to a recipient's bank account. When stablecoins are sent to this address, the off-ramp transfer to the linked bank is initiated automatically. ``` POST /v1/payout/offramp/withdraw-addresses ``` The call is idempotent. ### Deposit Accounts (On-ramp) A **deposit account** is a permanent fiat account (e.g. a SPEI CLABE in Mexico) tied to a stablecoin wallet. Your client sends fiat to this account and the funds settle to the linked wallet. ``` POST /v1/payout/onramp/deposit-accounts ``` The call is idempotent. ### Quotes and Settlement Before executing a transfer, get a quote to see the rate and fees. Then execute to trigger the actual transfer. ``` POST /v1/payout/offramp/quote — Get stablecoin → fiat rate POST /v1/payout/onramp/quote — Get fiat → stablecoin rate POST /v1/payout/accounts/quote — Get cross-currency rate (e.g. MXN → USD) POST /v1/payout/accounts/execute — Trigger settlement ``` ### Orders Every transfer is represented as an **order** in `/v1/orders`. Unified across rails — list, get, and cancel are the same regardless of direction. ## Endpoint Summary | Group | Prefix | Purpose | | ---------- | ----------------------- | ----------------------------------------------- | | Accounts | `/v1/payout/accounts` | Cross-currency quotes and payment execution | | On-ramp | `/v1/payout/onramp` | Fiat → stablecoin: deposit accounts, quotes | | Off-ramp | `/v1/payout/offramp` | Stablecoin → fiat: withdrawal addresses, quotes | | Recipients | `/v1/payout/recipients` | Beneficiary CRUD and bank account registration | | Orders | `/v1/orders` | Unified order list, status, and cancel | ## Integration Overview ```mermaid theme={null} graph TD A[KYB Approved in Dashboard] --> B[Create Recipient] B --> C[Generate Invite Link] C --> D[Recipient Adds Bank Account] D --> E[Create Withdrawal Address] E --> F[Create Deposit Account / CLABE] F --> G[Client Deposits to CLABE] G --> H[Quote + Execute] H --> I[Fiat Arrives at Recipient Bank] ``` ## Next Steps Execute an end-to-end payout step by step. Accounts, on/off-ramp, and recipient endpoint reference. # Architecture Overview Source: https://docs.relayer.fi/shared/architecture How Relayer Kits relate to each other and to your application Relayer is organized into Kits. Each Kit covers a distinct domain and exposes a set of REST endpoints under the `/v1` prefix. This page shows how the Kits relate to each other and to your application. ## Kit Structure ```mermaid theme={null} graph TB subgraph "Your Application" APP[Your Backend / Dashboard] FE[Your Frontend] end subgraph "Relayer API /v1" SI[Signing Kit
/v1/signing/*] AG[Agent Kit
/v1/agents/*] PA[Payout Kit
/v1/payout/*] WI[Widget Kit
/v1/action/builders/* + /execute/*] end APP -->|Bearer JWT or ApiKey| SI APP -->|Bearer JWT or ApiKey| AG APP -->|Bearer JWT or ApiKey| PA APP -->|Bearer JWT or ApiKey| WI FE -->|npm install @relayer-fi/widgets| WI ``` ## Kit Responsibilities | Kit | Prefix | What It Does | | -------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------- | | **Signing** | `/v1/signing`, `/v1/transactions` | Self-custodial wallets, transaction prepare/confirm, passkey signing, approval policies | | **Agent Kit** | `/v1/agents` | Budget-enforced AI agents that can sign transactions and pay for x402 resources | | **Payout** | `/v1/payout`, `/v1/orders` | Fiat on/off-ramp, recipient management, virtual accounts, payment settlement | | **Widget Kit** | `/v1/action/builders`, `/v1/action/execute`, npm packages | Embeddable widgets for canonical protocols (swap, transfer, bridge) | ## Request Flow Every API request follows the same pattern: ```mermaid theme={null} sequenceDiagram participant App as Your App participant API as Relayer API participant Kit as Kit Module App->>API: POST /v1/{kit}/{resource}
Authorization: ApiKey rk_... API->>API: Validate API key + module + permissions API->>Kit: Route to Kit module Kit-->>API: Result API-->>App: { success, data, statusCode, ... } ``` ## Authentication Model Every request authenticates with an API key in the `Authorization` header: ``` Authorization: ApiKey rk_client_key_v1_your_key_here ``` The API key identifies the integrator (your workspace) and determines which Kits and resources are accessible based on the workspace's active modules and the caller's role. Some surfaces also accept a session JWT (`Authorization: Bearer `) for browser-side callers from the Relayer dashboard, and the Agent Kit accepts an HMAC-signed `X-Agent-Auth` header for runtime calls from the agent SDK. See [Authentication](/get-started/authentication) for the full matrix. ## Environments | Environment | Base URL | Use | | ----------- | ------------------------------- | ----------------------- | | Sandbox | `https://testnet.relayer.fi/v1` | Development and testing | | Production | `https://api.relayer.fi/v1` | Live operations | All Kit endpoints and authentication patterns are identical across environments. # Auth & Custody Model Source: https://docs.relayer.fi/shared/auth-model Self-custodial guarantee + two dimensions of choice (key holder × pre-authorization scope). Read this before designing your integration. This page is the architectural reference for **what you can and cannot do with each kind of credential**. Read it before designing your integration — the wrong assumption here is a security incident. ## The guarantee **Relayer never holds keys that anyone at Relayer can use without your explicit authorization.** The cryptographic authority to sign always lives in one of two places: * **Your wallet stack** — MPC, HSM, MetaMask, Phantom, Fireblocks, Privy server-side. You picked it; you sign with it. * **A hardware enclave (HSM) gated by your passkey** — Relayer/Turnkey runs the infrastructure, but the key material is **inert without a WebAuthn stamp from your registered passkey**. Nobody at Relayer can use it. In the second case, the keys *physically reside* on Relayer-managed infrastructure, but they are **functionally self-custodial**: no Relayer employee, no rogue code, no compromised server can move funds. The only thing that can authorize signing is the passkey on your (or your user's) device. This is the distinction between **custody** (someone else can move your funds) and **enclave-hosted self-custody** (only your passkey can move your funds, even though Relayer runs the enclave). ## The two dimensions Every signing decision is the product of two choices: ### Dimension 1 — Who holds the key? | Choice | What it means | | -------------------------- | --------------------------------------------------------------------------------------- | | **Your wallet stack** | You bring MPC, HSM, browser wallet, or any signer. Relayer never sees private material. | | **Enclave + your passkey** | HSM-protected key, only usable with your WebAuthn stamp. Functionally self-custodial. | ### Dimension 2 — What's the scope of pre-authorization? | Choice | What it means | | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Per-transaction stamping** | The passkey holder approves each transaction at the moment it happens. Common for consumer-facing flows. | | **Pre-authorized bounded delegation** | The passkey holder signs once to grant a bounded mandate (policies + budgets + threshold). An agent operates autonomously within those bounds. Above-threshold actions still require a stamp. | ## The combinations | Key holder | Scope | What it looks like | Backend-only flows possible? | | --------------------- | -------------- | --------------------------------------------------------------------------- | ------------------------------- | | **Your wallet stack** | Per-tx | You sign every tx with your MPC/HSM/wallet | ✅ Yes | | **Your wallet stack** | Pre-authorized | Your wallet stack runs autonomously per your own policies | ✅ Yes | | **Enclave + passkey** | Per-tx | End-user stamps each tx in browser (Widget Kit default) | ❌ No — user must be present | | **Enclave + passkey** | Pre-authorized | Agent Kit: passkey-stamped at provisioning, HMAC at runtime within policies | ✅ Yes — until threshold crossed | There is no scenario where Relayer holds custody in the legacy sense. There is no scenario where someone at Relayer can move user funds. ## What each credential type can do The API supports three credential types. **Each is necessary but not always sufficient.** ### `Authorization: ApiKey rk_client_key_v1_...` Backend integrations. Issued from the dashboard, scoped (integrator / integrator:read / integrator:write). | ApiKey **can** | ApiKey **cannot** | | -------------------------------------------------------------- | -------------------------------------------------------------- | | Read any non-sensitive resource in your workspace | Sign transactions on its own | | Create recipients, withdrawal addresses, virtual accounts | Approve above-threshold transactions | | Quote and execute payments (after on-chain funding is settled) | Register new passkeys for other users | | Configure agent budgets and policies | Rotate another user's passkey | | Issue invites, manage team (via internal scope) | Bypass signing policies on a wallet | | Initiate `prepare` half of any prepare/confirm flow | Complete the `confirm` half without a passkey-stamped activity | | List pending approvals | Mark an approval as approved | The recurring pattern: **ApiKey can prepare, configure, and observe — but the act of signing is always gated by a passkey or by an agent's HMAC.** ### `X-Agent-Auth` HMAC headers Agent SDK runtime. HMAC-SHA256 signature derived from the agent secret issued at provisioning time. | HMAC **can** | HMAC **cannot** | | ------------------------------------------------------------------ | --------------------------------------------------------------------- | | Sign Solana transactions for the agent's own wallet | Spend more than the agent's `payments` budget allows | | Pay x402-gated resources within budget | Spend above the agent's `approvalThresholdUSDC` without a human stamp | | Emit telemetry events | Change the agent's own policies or budget | | Create wallets if policies allow (`Allow: Agent — Create Wallets`) | Provision a new agent or rotate another agent's secret | | Call x402-paid resources within the agent's allowlist | Touch resources outside the agent's own scope | The agent's authority is **whatever the human granted at provisioning time, bounded by policies + budgets**. The HMAC secret doesn't grant cryptographic authority — it proves identity. The actual authorization comes from the policies signed by a human passkey when the agent was created. ### Passkey (WebAuthn) The cryptographic root of all signing authority for enclave-hosted wallets. Lives on the user's device (Face ID, Touch ID, hardware security key, Windows Hello). | Passkey **can** | Passkey **cannot** | | ------------------------------------------------------------- | ------------------------------------------------------------------- | | Sign any transaction the user's wallet supports | Be used remotely — must be physically present on the device | | Stamp prepare activities to create wallets, addresses, agents | Be copied off the device | | Approve above-threshold transactions on behalf of approvers | Be exported to a different device — recovery requires re-enrollment | | Authorize agent provisioning with policies | Sign for a wallet it wasn't enrolled to | | Recover access via the dashboard's email flow | Be impersonated by an API key or HMAC secret | The passkey is **the only thing that can authorize signing**. Everything else — ApiKey, HMAC, JWTs — is identity or session, not authority. ## When you need a human passkey, period No matter what mode you're in, **a transaction above `approvalThresholdUSDC` requires a human to stamp it with a passkey**. That's not a limitation — it's the design. If you want a higher autonomy ceiling, raise the threshold consciously. If you want a kill switch on autonomy, lower it. For Agent Kit specifically, the approval response is HTTP 202 with an `approvalId`. The SDK polls until resolution (default 5-minute timeout, configurable). For raw API calls, you implement the same polling pattern manually. ## Off-ramp specifically A common question: **can my off-ramp run 100% backend, no humans?** The off-ramp flow has 4 steps: 1. **Setup** (`/payout/accounts/setup/*`, `/payout/recipients/*`) — ApiKey only ✅ 2. **Move stablecoins to the withdrawal address** — depends on who signs that on-chain transfer ⬇ 3. **Trigger settlement** (`/payout/accounts/execute`) — ApiKey only ✅ 4. **Fiat to recipient bank** — handled by the rails partner, out of band Step 2 is the question. Three patterns: * **Your wallet stack** signs the on-chain transfer → fully backend ✅ * **Agent Kit** with policies that allow transfers to the withdrawal address → fully backend within bounds ✅ * **End-user passkey wallet** signs the transfer per-tx → user must be present ❌ There is **no scenario where Relayer prevents you from a backend-only off-ramp** — it depends on your key-holder model, not on us. ## How to choose your model **When**: you already have MPC, HSM, or sufficient signing infrastructure; you want maximum control; you don't want end-users in the loop. **Use**: ApiKey for everything Relayer does; sign on-chain transactions with your own stack. **When**: you want backend automation but don't want to run signing infrastructure; budget and policy boundaries are sufficient governance. **Use**: provision agents via passkey ceremony (one-time), then HMAC at runtime. **When**: end-users own their funds; each user is in the loop for every transaction; consumer-facing UX. **Use**: Widget Kit Passkey Signing. Each end-user enrolls a passkey; each transaction prompts biometrics. **When**: end-users want autonomous helpers acting on their behalf within bounds. **Use**: end-user enrolls passkey + grants policies → agent operates with HMAC within those bounds. Above threshold → user stamps. ## Common mistakes to avoid **Mistake 1**: Treating ApiKey as authority. ApiKey lets you administer; it does not let you sign. If your backend needs to move funds, you need to either (a) hold your own keys, (b) run an agent, or (c) involve a user with a passkey. **Mistake 2**: Confusing "Relayer hosts the enclave" with "Relayer holds custody". The enclave hosts the key material; the passkey on the user's device is the only thing that can use it. Relayer cannot move funds, period. **Mistake 3**: Setting `approvalThresholdUSDC` to a large number "to avoid friction". That removes the human gate on large transactions — exactly the case where you want human confirmation. Default low; raise only with eyes open. **Mistake 4**: Building a flow that assumes ApiKey can complete a `confirm` step. The prepare/confirm split is intentional: prepare returns an activity, the passkey stamps it, confirm submits the stamped result. There is no shortcut. ## See also Header formats, key scopes, HMAC payload spec, and code snippets per language. How the Kits fit together and where each credential type is accepted. The prepare/confirm flow in detail — and why it exists. Bounded delegation: passkey-stamped provisioning, HMAC at runtime. # Error Reference Source: https://docs.relayer.fi/shared/error-reference HTTP status codes, error response format, common business errors, and retry guidance All Relayer API errors follow a consistent response envelope. This page covers the error format, HTTP status codes, common business errors, and how to handle them. ## Error response format Every error response uses the same envelope as successful responses, with `success: false`: ```json theme={null} { "success": false, "message": "Validation failed: amount must be positive", "statusCode": 400, "timestamp": "2026-05-15T12:00:00.000Z", "path": "/v1/payout/accounts/quote" } ``` | Field | Type | Description | | ------------ | --------- | ---------------------------------------------- | | `success` | `boolean` | Always `false` for errors | | `message` | `string` | Human-readable error description | | `statusCode` | `number` | HTTP status code (mirrors the response status) | | `timestamp` | `string` | ISO 8601 timestamp of when the error occurred | | `path` | `string` | The request path that returned the error | Some agent endpoints add structured error codes inside the body for client-side dispatch — see [Agent-specific errors](#agent-specific-errors) below. ## HTTP status codes ### 4xx — client errors | Status | Name | Meaning | | ------ | -------------------- | --------------------------------------------------------------------------------- | | `400` | Bad Request | Request body or query parameters are invalid or missing required fields | | `401` | Unauthorized | API key / JWT / HMAC is missing, malformed, expired, or invalid | | `402` | Payment Required | An x402-gated resource requires a USDC payment (Agent Kit only) | | `403` | Forbidden | Caller is authenticated but lacks scope or permissions for this endpoint | | `404` | Not Found | The requested resource does not exist (wrong ID, deleted, or wrong path) | | `409` | Conflict | Operation conflicts with existing state (e.g., duplicate resource) | | `422` | Unprocessable Entity | Request is well-formed but fails business validation (e.g., insufficient balance) | | `429` | Too Many Requests | Rate limit exceeded — back off and retry | ### 5xx — server errors | Status | Name | Meaning | | ------ | --------------------- | --------------------------------------------------------------- | | `500` | Internal Server Error | Unexpected server-side error — contact support if persistent | | `502` | Bad Gateway | An upstream service returned an error | | `503` | Service Unavailable | API is temporarily unavailable — retry with exponential backoff | ### 2xx with caveats | Status | Meaning | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `202 Accepted` | Returned by signing endpoints when the action is queued for human approval. Body contains `{ approvalId }`. Poll `/v1/signing/approvals/{id}` until resolved | ## Authentication errors ```json theme={null} { "success": false, "message": "Authorization header is missing", "statusCode": 401, "timestamp": "2026-05-15T12:00:00.000Z", "path": "/v1/signing/wallets" } ``` **Fix:** Add the `Authorization: ApiKey rk_...` header to your request. ```json theme={null} { "success": false, "message": "API key not found", "statusCode": 404, "timestamp": "2026-05-15T12:00:00.000Z", "path": "/v1/signing/wallets" } ``` **Fix:** Verify your API key is correct and was not revoked. Sandbox keys don't work in production — and vice versa. ```json theme={null} { "success": false, "message": "Insufficient permissions for this resource", "statusCode": 403, "timestamp": "2026-05-15T12:00:00.000Z", "path": "/v1/signing/wallets" } ``` **Fix:** Your API key's scope (e.g. `integrator:read`) does not cover this endpoint. Generate a new key with the right scope, or check whether the endpoint is dashboard-only (in which case it's not callable from external integrations). ## Agent-specific errors Agent endpoints use structured error codes inside the body so the SDK can dispatch programmatically: | `error` code | HTTP | Cause | | ----------------------- | --------- | ---------------------------------------------------------------------------------------------------------- | | `invalid_auth` | 401 | Missing HMAC headers, agent not found, or signature mismatch | | `expired_timestamp` | 401 | `x-request-timestamp` drift exceeds ±60 seconds | | `agent_killed` | 401 | Agent status is `killed` — re-provision before retrying | | `budget_exhausted` | 402 / 403 | One or more budget layers exhausted. Response includes `failedLayers: ["infra" \| "tokens" \| "payments"]` | | `kill_switch_active` | 503 | Global kill switch tripped — payments blocked (fail-safe) | | `x402_payment_required` | 402 | The downstream x402 resource requires payment — handled by `x402fetch` | | `approval_required` | 202 | Payment exceeds approval threshold — body contains `approvalId` to poll | ## Validation errors `400` responses include the specific field and constraint that failed in `message`: ```json theme={null} { "success": false, "message": "Validation failed: amount must be a positive number", "statusCode": 400, "timestamp": "2026-05-15T12:00:00.000Z", "path": "/v1/payout/accounts/quote" } ``` For requests with multiple validation failures, `message` typically lists the first offending field. Check your request payload against the schema in the [API Reference](/api-reference/introduction). ## Common business errors `422 Unprocessable Entity` is returned when a request is well-formed but conflicts with business rules: | `message` (representative) | Kit | Cause | | ------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------- | | `Quote has expired` | Payout | The quote's validity window has passed — request a new quote | | `Wallet already exists` | Signing | Attempting to create a duplicate wallet | | `Recipient not found` | Payout | The recipient ID does not exist in this workspace | | `Insufficient balance` | Payout | Withdrawal address doesn't have enough stablecoin to settle | | `Order already settled` | Payout | Trying to cancel an order past `awaiting` status | | `Agent has insufficient budget` | Agent | One of the 3 budget layers is exhausted | | `Pending policies — finish the create flow` | Agent | Agent is mid-provisioning. Complete with `confirm-policies` or abandon with `DELETE /v1/agents/{id}` | | `Passkey signature invalid` | Signing | The stamped activity does not match the unsigned payload | ## Error handling patterns `4xx` errors are your responsibility — inspect the `message` and fix the request. `5xx` errors are server-side — retry with backoff. The `message` is human-readable and explains the specific problem. Log it for debugging and surface a user-appropriate message in your UI. `401` means unauthenticated (missing or invalid credentials). `403` means authenticated but not authorized (scope or permissions issue). These require different resolution paths. Rate limit (`429`) and server errors (`502`, `503`) are transient. Retry with exponential backoff: 1s, 2s, 4s, 8s. Give up after 3-4 attempts and surface the error to the user. A `202` response means the action was accepted but is awaiting human approval (signing flows) or polling resolution (agent x402). It's not a failure — poll the relevant resource until you get a final state. ## Getting help If you encounter a persistent `500` error or unexpected behavior, contact [dev@relayer.fi](mailto:dev@relayer.fi) with: * The full error response body * The request path, method, and (sanitized) payload * Your API key prefix (**first 20 characters only** — never share the full key) * The `timestamp` from the error envelope (we use it to locate server-side logs) # Glossary Source: https://docs.relayer.fi/shared/glossary Domain terminology for Relayer and crypto B2B infrastructure Reference for domain terms used throughout the Relayer documentation. ## A **Agent Kit** The Relayer Kit that lets you ship budget-enforced AI agents — each agent holds a USDC wallet, can sign Solana transactions, and pays for x402-gated resources. Enforces a 3-layer budget guard plus a kill switch. Accessed via `/v1/agents/*`. **API Key** A secret credential that identifies an integrator workspace and authorizes requests to the Relayer API. Format: `rk_client_key_v1_...`. Passed in the `Authorization: ApiKey ` header. **Approval** A human-in-the-loop gate for signing. Transactions above a configured threshold enter a pending queue and must be approved (passkey-stamped) by an authorized team member before they are signed. ## I **Integrator** A workspace (operator or developer) building a product on top of Relayer. Each integrator has its own wallets, API keys, team, and signing policies. Multiple integrators can sit under the same operator account. ## K **Kit** A domain-scoped module in the Relayer API. Each Kit covers a distinct area of functionality (Signing, Agent, Payout, Widget) and exposes its own set of endpoints under the `/v1` prefix. ## M **Metadata Mode** The Widget Kit integration mode where the widget returns transaction routing and calldata to your application, and you sign and broadcast with your own wallet stack (MPC, HSM, MetaMask, etc.). Compare with **Passkey Signing**. ## O **Operator** A business or developer who has an account with Relayer and integrates the API into their product. Operators receive an API key and configure which Kits and modules are active for their workspace. ## P **Passkey** A WebAuthn credential bound to a user's device (Face ID, fingerprint, security key). Used to authorize signing and approval actions. The passkey is the **only thing that can authorize signing** — it cannot be impersonated by an API key or HMAC secret. See [Auth & Custody](/shared/auth-model). **Passkey Signing** The Widget Kit integration mode where the user stamps each transaction with a passkey via WebAuthn. Keys live in a hardware enclave but are **inert without the user's passkey** — Relayer does not custody. Compare with **Metadata Mode**. **Payout Kit** The Relayer Kit that handles fiat on/off-ramp, virtual accounts, recipient management, and settlement. Accessed via `/v1/payout/*` and `/v1/orders`. **Policy** See **Signing Policy**. **Pre-authorization** A bounded mandate granted by a passkey at setup time (typically by configuring policies + budgets + threshold for an agent). The agent can then act autonomously within those bounds via HMAC at runtime. Above the threshold, a fresh passkey stamp is required. Distinguishes Agent Kit from per-tx passkey signing. ## S **Settlement** The final transfer of funds that completes a transaction. In payout flows, settlement confirms fiat delivery to the recipient bank. **Signing Kit** The Relayer Kit that handles self-custodial wallet creation and transaction signing via passkeys. Private keys live inside a hardware-secured enclave — neither Relayer nor the integrator sees raw key material. Accessed via `/v1/signing/*` and `/v1/transactions/*`. **Signing Policy** A set of rules attached to a wallet or workspace that controls which transactions can be signed. Policies can restrict by value, destination address, token type, or time window. Enforced before any signing operation. ## W **Widget Kit** A set of React components and adapters that embed Relayer's canonical protocol widgets (swap, transfer, crosschain) into a frontend application. Installable via npm — `@relayerfi/widget-kit-react`. ## X **x402** HTTP status code 402 "Payment Required" — used by remote services to request a USDC payment before returning data. The Agent Kit ships an `x402fetch` helper that handles 402 detection, payment, and retry transparently. # Signing Endpoints Source: https://docs.relayer.fi/signing/endpoints Wallet management, transaction prepare/confirm, passkeys, recovery, policies, and approval flows All signing and broadcast operations interact with live blockchain networks. Transactions are irreversible once broadcast. Verify destination addresses and amounts carefully before calling any broadcast endpoint. Reference for Signing Kit endpoints. For interactive schemas and try-it functionality, see the [API Reference](/api-reference/introduction). ## How signing works Every signing operation uses a **two-step prepare/confirm flow** so private key material never leaves the secure enclave: 1. **Prepare** — the API returns an unsigned activity (for wallet creation) or an unsigned transaction (for signing). 2. **Stamp** — the client stamps the payload with a registered passkey (WebAuthn, browser-side). 3. **Confirm** — the API validates the stamped payload, processes it inside the enclave, and persists / broadcasts the result. The same pattern applies to wallet creation, address creation, and transaction signing. ## Wallets ### Prepare wallet creation Build an unsigned wallet-creation activity. The response contains the payload to stamp with a passkey on the client. ``` POST /v1/signing/wallets/prepare ``` ### Confirm wallet creation Submit the passkey-stamped activity. The signing enclave processes it and returns the persisted wallet. ``` POST /v1/signing/wallets/confirm ``` **Response (shape):** ```json theme={null} { "success": true, "data": { "walletId": "wallet_abc123", "name": "My Wallet", "createdAt": "2026-03-28T12:00:00.000Z" } } ``` *** ### List wallets ``` GET /v1/signing/wallets ``` ### Get wallet ``` GET /v1/signing/wallets/{walletId} ``` ### Prepare wallet accounts (addresses) Build an unsigned `CREATE_WALLET_ACCOUNTS` activity. ``` POST /v1/signing/wallets/accounts/prepare ``` ### Confirm wallet accounts (addresses) Submit the passkey-stamped activity. Generates new addresses derived from the wallet's root key. ``` POST /v1/signing/wallets/accounts/confirm ``` ### Get wallet addresses ``` GET /v1/signing/wallets/{walletId}/addresses GET /v1/signing/wallets/{walletId}/addresses/{addressId} ``` ### Get wallet transactions and denials ``` GET /v1/signing/wallets/{walletId}/transactions GET /v1/signing/wallets/{walletId}/denials ``` *** ## Transactions ### Prepare a transaction Build an unsigned transaction and store it with `awaiting_signature` status. The client signs the returned unsigned hex with the user's passkey and submits via confirm. ``` POST /v1/transactions/prepare ``` ### Confirm a prepared transaction Submit the signed transaction hex. The API validates the signature against the original unsigned transaction via hash comparison, persists the signed transaction, and broadcasts to the blockchain. ``` POST /v1/transactions/confirm ``` Confirm broadcasts the transaction by default. If you need to gate the broadcast separately (multi-step approval, scheduled submission), use the broadcast endpoints below with a transaction prepared via the same flow. *** ### List transactions ``` GET /v1/transactions/sign — All signed transactions GET /v1/transactions/sign/{transactionId} — One signed transaction GET /v1/transactions/pending-signature — Transactions awaiting passkey signature ``` ### Cancel a pending transaction Cancel a transaction in `awaiting_signature` status (before passkey stamping). ``` DELETE /v1/transactions/sign/{transactionId} ``` Cancellation is only possible before the transaction has been broadcast. Once it is in the mempool, this endpoint cannot reverse it. *** ## Broadcasting ### Broadcast a signed transaction Submit a previously signed transaction to the blockchain. Use this when you want to gate the broadcast step separately from confirm. ``` POST /v1/transactions/broadcast/{transactionId} ``` **Response:** ```json theme={null} { "success": true, "data": { "transactionId": "tx_def456", "txHash": "0xabc...def", "status": "broadcast", "network": "sepolia" } } ``` ### Broadcast a raw signed transaction Submit a pre-signed raw transaction payload — useful when you have an externally-signed transaction. ``` POST /v1/transactions/broadcast/raw ``` ### Get broadcast status ``` GET /v1/transactions/broadcast/{transactionId} ``` **Status values:** `broadcast`, `confirmed`, `failed` ### Retry a failed broadcast ``` POST /v1/transactions/broadcast/{transactionId}/retry ``` *** ## Passkeys Passkeys are WebAuthn credentials registered to a wallet workspace. They authorize every signing operation. ### Generate a challenge ``` POST /v1/signing/passkeys/challenge ``` ### Register a passkey (creates the wallet workspace) ``` POST /v1/signing/passkeys/register ``` ### Add a passkey to an existing workspace ``` POST /v1/signing/passkeys/add ``` ### List passkeys ``` GET /v1/signing/passkeys ``` ### Rename / delete passkey ``` PATCH /v1/signing/passkeys/{id} — Rename DELETE /v1/signing/passkeys/{id} — Delete ``` *** ## Recovery If a user loses access to their passkey, recovery re-binds a new passkey to the existing wallet without rotating the underlying keys. ``` POST /v1/signing/recovery/initiate — Start email-based recovery POST /v1/signing/recovery/sync — Sync FE-completed recovery passkey GET /v1/signing/recovery/context — Recovery context for the iframe flow GET /v1/signing/recovery/migration-eligibility — Check whether the wallet can migrate ``` *** ## Signing Policies Signing policies restrict which transactions a wallet will sign. Policies are enforced inside the signing enclave, before any transaction is processed. ### List policies ``` GET /v1/signing/policies ``` ### Create a policy ``` POST /v1/signing/policies ``` **Request body (example):** ```json theme={null} { "name": "Max transfer limit", "walletId": "wallet_abc123", "rules": { "maxValueUsd": 10000, "allowedDestinations": ["0xApprovedAddress1", "0xApprovedAddress2"] } } ``` ### Update / delete a policy ``` PATCH /v1/signing/policies/{id} DELETE /v1/signing/policies/{id} ``` *** ## Approval Flows Require human approval for transactions above a threshold before the signing enclave processes them. ### Get / update approval config ``` GET /v1/signing/approval-config PATCH /v1/signing/approval-config ``` **Update body (example):** ```json theme={null} { "enabled": true, "thresholdUsd": 5000, "requiredApprovers": 1 } ``` ### List pending approvals ``` GET /v1/signing/approvals ``` ### Approve a request ``` POST /v1/signing/approvals/{id}/approve POST /v1/signing/approvals/{id}/approve-with-passkey ``` Use `/approve-with-passkey` when the approval itself must be passkey-stamped (the default for agent transaction approvals). ### Reject a request ``` POST /v1/signing/approvals/{id}/reject ``` *** ## Key Rotation To rotate keys for a wallet, generate a new address from the same wallet using the prepare/confirm accounts flow. The new address has a new derived key while the wallet root key remains unchanged inside the secure enclave. For workspace-level API key rotation, generate a new key from the dashboard and revoke the old one. *** ## Full API Reference All endpoints include interactive schemas and a try-it playground in the [API Reference](/api-reference/introduction). Signing Kit endpoints are grouped under **Wallets**, **Transactions**, **Passkeys**, **Recovery**, **Signing Approvals**, and **Signing Policies**. # Signing Flow Guide Source: https://docs.relayer.fi/signing/flow-guide Register a passkey, create a wallet, sign a transaction, and broadcast — using the prepare/confirm pattern This guide results in real on-chain transactions when using production credentials. Use the sandbox environment (`https://testnet.relayer.fi/v1`) while testing. Blockchain transactions are irreversible once broadcast. The Signing Kit uses a **prepare → stamp → confirm** pattern. Every signing operation (wallet creation, address creation, transaction signing) follows the same three steps: 1. **Prepare** — your backend calls the API to get an unsigned activity/transaction 2. **Stamp** — the user signs the payload with a passkey (WebAuthn, browser-side) 3. **Confirm** — your backend sends the stamped payload back; the enclave validates and processes it The passkey stamping in step 2 must happen in a browser context that has access to WebAuthn. Server-only flows (cURL) can call prepare and confirm, but the stamping itself requires user interaction. ## Prerequisites * A Relayer API key with the Signing module enabled (see [Authentication](/get-started/authentication)) * A user with a registered passkey in your workspace (see step 1 below) ```bash Environment theme={null} export RELAYER_API_KEY="rk_client_key_v1_your_key_here" export RELAYER_BASE_URL="https://testnet.relayer.fi/v1" # sandbox; use https://api.relayer.fi/v1 for production ``` ## Step 1 — Register a passkey (one-time setup) The first time a user enrolls in your workspace, they register a passkey. This creates the wallet workspace and binds the user's WebAuthn credential to it. The full flow happens in the browser via the Relayer dashboard or your own WebAuthn-capable frontend. The API calls involved: ``` POST /v1/signing/passkeys/challenge → returns WebAuthn challenge POST /v1/signing/passkeys/register → registers the credential and creates the workspace ``` For end-user onboarding (Embedder integrators), the Widget Kit handles passkey enrollment automatically. See [Widget Kit](/widget/overview). ## Step 2 — Create a wallet Your backend asks the API for an unsigned wallet-creation activity. ```bash cURL theme={null} curl -X POST $RELAYER_BASE_URL/v1/signing/wallets/prepare \ -H "Authorization: ApiKey $RELAYER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "My Wallet" }' ``` The response includes the unsigned activity payload your frontend needs to stamp. Your frontend prompts the user with a WebAuthn biometric challenge (Face ID, fingerprint, security key). The browser produces a stamped activity. ```typescript Frontend (browser) theme={null} // Pseudo-code — exact stamping API depends on the WebAuthn library const stamped = await passkey.stamp(unsignedActivity); ``` Your backend sends the stamped activity back to the API. ```bash cURL theme={null} curl -X POST $RELAYER_BASE_URL/v1/signing/wallets/confirm \ -H "Authorization: ApiKey $RELAYER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "stampedActivity": "..." }' ``` The response contains the `walletId`. Save it. ## Step 3 — Generate an address Same prepare/stamp/confirm pattern, this time for `CREATE_WALLET_ACCOUNTS`: ``` POST /v1/signing/wallets/accounts/prepare { walletId, curve, addressFormat } → user stamps the returned activity with their passkey POST /v1/signing/wallets/accounts/confirm { stampedActivity } → returns { address, addressId, walletId } ``` Use `addressFormat: "ADDRESS_FORMAT_ETHEREUM"` for all EVM-compatible networks (Ethereum, Polygon, Arbitrum, Base, etc.). Use `addressFormat: "ADDRESS_FORMAT_SOLANA"` for Solana. ## Step 4 — Sign a transaction ```bash cURL theme={null} curl -X POST $RELAYER_BASE_URL/v1/transactions/prepare \ -H "Authorization: ApiKey $RELAYER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "walletId": "wallet_abc123", "from": "0xYourWalletAddress", "to": "0xRecipientAddress", "value": "10000000000000000", "network": "sepolia" }' ``` The response includes: * `transactionId` — store this for later * `unsignedTransaction` (hex) — pass to the frontend for passkey signing The transaction is persisted with status `awaiting_signature`. Your frontend signs the unsigned transaction hex with the user's passkey: ```typescript Frontend (browser) theme={null} const signedTx = await passkey.signTransaction(unsignedTransaction); ``` Send the signed hex to confirm. The API validates the signature against the original unsigned transaction via hash comparison, persists the signed transaction, and broadcasts to the blockchain. ```bash cURL theme={null} curl -X POST $RELAYER_BASE_URL/v1/transactions/confirm \ -H "Authorization: ApiKey $RELAYER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "transactionId": "tx_def456", "signedTransaction": "0x..." }' ``` The response contains `txHash` once the transaction is in the mempool. Poll the broadcast status to confirm on-chain confirmation. ```bash cURL theme={null} curl $RELAYER_BASE_URL/v1/transactions/broadcast/$TRANSACTION_ID \ -H "Authorization: ApiKey $RELAYER_API_KEY" ``` **Status values:** `broadcast`, `confirmed`, `failed`. Once confirmed, the response includes `blockNumber`. ## Separating signing from broadcasting If you need to gate the broadcast step (multi-step approval, scheduled submission), don't use confirm-with-broadcast. Instead: 1. `POST /v1/transactions/prepare` → unsigned tx 2. User stamps with passkey on the frontend 3. `POST /v1/transactions/confirm` → signs and persists (no broadcast yet) — TODO: confirm whether your tenant supports this gated mode 4. `POST /v1/transactions/broadcast/{transactionId}` later, when conditions are met Alternatively, sign externally and submit raw: ```bash cURL theme={null} curl -X POST $RELAYER_BASE_URL/v1/transactions/broadcast/raw \ -H "Authorization: ApiKey $RELAYER_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "rawSignedTransaction": "0x...", "network": "mainnet" }' ``` ## Cancelling before broadcast A transaction in `awaiting_signature` status can be cancelled: ```bash cURL theme={null} curl -X DELETE $RELAYER_BASE_URL/v1/transactions/sign/$TRANSACTION_ID \ -H "Authorization: ApiKey $RELAYER_API_KEY" ``` Once `POST /v1/transactions/confirm` has broadcast the transaction, cancellation is no longer possible. The transaction is in the mempool. ## Approval gate (high-value transactions) If your workspace has approval enabled and the transaction value exceeds the threshold, `POST /v1/transactions/confirm` returns **HTTP 202** with `{ approvalId }` instead of broadcasting. The transaction stays pending until an authorized team member stamps approval: ``` GET /v1/signing/approvals — Approver lists pending requests POST /v1/signing/approvals/{id}/approve-with-passkey — Approver stamps approval ``` Once approved, the transaction is broadcast automatically. ## Next Steps Full endpoint reference for wallets, transactions, passkeys, recovery, policies, and approvals. Interactive schemas and try-it playground. Wallet, passkey, policy, and other domain terms. API error codes and handling patterns. # Signing Kit Source: https://docs.relayer.fi/signing/overview Self-custodial wallets, transaction prepare/confirm, and passkey-based signing The Signing Kit manages cryptographic private keys and submits transactions to live blockchain networks. Signed transactions and broadcasts are irreversible. Always verify destination addresses and amounts before signing or broadcasting. The Signing Kit gives operators a self-custodial wallet stack with passkey-based signing. You create wallets, generate addresses, set signing policies, and sign or broadcast transactions — **Relayer never holds or has access to your raw private keys**. Signing happens inside a hardware-secured enclave, gated by a passkey biometric prompt on the user's device. ## Core Concepts ### Wallets and Addresses A **wallet** is the root container for a set of derived addresses. Each wallet can generate multiple addresses across different networks (Ethereum, Polygon, Solana, etc.). ```mermaid theme={null} graph TD A[Integrator] --> B[Wallet] B --> C[ETH Address] B --> D[MATIC Address] B --> E[USDC Address] B --> F[... more addresses] ``` * One wallet → multiple addresses * Addresses are deterministically derived (HD wallet) * Network is specified per address ### Prepare / Confirm Signing Signing is a two-step flow so the private key material never has to leave the secure enclave: 1. **Prepare** — `POST /v1/transactions/prepare` returns an unsigned transaction payload 2. **Confirm** — the user stamps it with their passkey, then `POST /v1/transactions/confirm` validates the stamped activity and signs You can sign without broadcasting immediately. Broadcasting is a separate step (`POST /v1/transactions/broadcast/{id}`) so multi-step approval flows can gate the final submission. ### Signing Policies A signing policy is a set of rules attached to a wallet or workspace that controls which transactions can be signed. Policies can restrict: * **Value limits** — maximum transaction value in USD or token amount * **Destination addresses** — allowlist or denylist of recipients * **Token types** — which tokens are permitted to transfer * **Time windows** — signing only allowed during certain hours Policies are enforced **before** the signing enclave processes the transaction. A transaction that violates policy never gets signed. ### Approval Flows For high-value or sensitive operations, you can require human approval before a transaction is signed. Configure an approval threshold via `PATCH /v1/signing/approval-config`. Transactions above the threshold enter a pending approval queue (`GET /v1/signing/approvals`) where an authorized team member stamps approval with their passkey (`POST /v1/signing/approvals/{id}/approve-with-passkey`). ### Recovery If a user loses access to their passkey, the Signing Kit supports an email-based recovery flow (`POST /v1/signing/recovery/initiate`). Recovery re-binds a new passkey to the existing wallet without rotating the underlying keys. ## Endpoint Summary | Group | Endpoints | Purpose | | --------- | ---------------------------------------------------------------------- | --------------------------- | | Wallets | `POST /v1/signing/wallets/prepare`, `POST /v1/signing/wallets/confirm` | Create wallets with passkey | | Addresses | `POST /v1/signing/wallets/{id}/addresses` | Generate wallet addresses | | Sign | `POST /v1/transactions/prepare`, `POST /v1/transactions/confirm` | Self-custodial signing flow | | Broadcast | `POST /v1/transactions/broadcast/{id}` | Submit to blockchain | | Passkeys | `POST /v1/signing/passkeys/register` | Enroll a passkey | | Recovery | `POST /v1/signing/recovery/initiate` | Recover access via email | | Policies | `POST /v1/signing/policies` | Restrict signing rules | | Approvals | `GET /v1/signing/approvals` | Review pending approvals | ## Security Model * Private keys live inside a hardware-secured enclave — neither Relayer nor the integrator ever sees raw key material * Every signing operation is gated by a passkey biometric prompt on the user's device * Signing policies provide a second layer of control independent of API authentication * Approval flows add a human-in-the-loop gate for sensitive operations ## Next Steps Create a wallet and sign your first transaction. Configure signing policies, approvals, and recovery. # Installation Source: https://docs.relayer.fi/widget/installation Install @relayerfi/widget-kit-react and configure providers for Passkey Signing or Metadata Mode integration Install the widget library and its peer dependencies, then configure providers based on your integration mode. Both Passkey Signing and Metadata Mode start with the same base install. ## Install Packages ```bash npm theme={null} npm install @relayerfi/widget-kit-react wagmi viem @tanstack/react-query framer-motion ``` ```bash pnpm theme={null} pnpm add @relayerfi/widget-kit-react wagmi viem @tanstack/react-query framer-motion ``` ```bash yarn theme={null} yarn add @relayerfi/widget-kit-react wagmi viem @tanstack/react-query framer-motion ``` `@relayerfi/action-kit` and `@relayerfi/widget-kit-core` are peer packages bundled internally — you do not install them separately. ## Choose Your Integration Mode Passkey Signing is for integrators using Relayer's managed wallet stack. Each integrator gets a dedicated wallet workspace with passkey-based signing. In addition to the base packages, Passkey Signing integrators should be aware that: * The widget handles passkey prompts internally — no additional signing SDK install is needed in your frontend * Your users will see a biometric prompt (Face ID, fingerprint, security key) when signing transactions * Wallet creation and passkey registration are handled through the Relayer API and dashboard Proceed to the wagmi configuration below — the Widget component manages the signing flow automatically. Metadata Mode is for integrators who bring their own wallet stack (MPC, HSM, MetaMask, WalletConnect, or any EVM-compatible signer). No additional packages are needed beyond the base install. In Metadata Mode: * The widget requests routing and calldata from the Relayer API * Your application receives the transaction metadata * You build, sign, and broadcast using your own wallet stack The wagmi configuration below is still recommended for chain management and provider connectivity, but transaction signing is handled by your infrastructure. ## Configure Wagmi Create a wagmi config file. This example uses `mainnet` with the browser injected connector: ```typescript src/wagmi.ts theme={null} import { createConfig, http } from 'wagmi'; import { mainnet } from 'wagmi/chains'; import { injected } from 'wagmi/connectors'; export const wagmiConfig = createConfig({ chains: [mainnet], connectors: [injected()], transports: { [mainnet.id]: http(), }, }); ``` Add more chains and connectors (WalletConnect, Coinbase Wallet) as needed. See the [wagmi docs](https://wagmi.sh/react/config) for the full configuration API. ## Wrap Your App wagmi v2 requires `WagmiProvider` and `QueryClientProvider` at the root of your component tree. Create a client component for providers: ```typescript src/app/providers.tsx theme={null} 'use client'; import { WagmiProvider } from 'wagmi'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { wagmiConfig } from '@/wagmi'; const queryClient = new QueryClient(); export function Providers({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` Then use `` in your root layout: ```typescript src/app/layout.tsx theme={null} import { Providers } from './providers'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` Wrap your app entry point: ```typescript src/main.tsx theme={null} import React from 'react'; import ReactDOM from 'react-dom/client'; import { WagmiProvider } from 'wagmi'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { wagmiConfig } from './wagmi'; import App from './App'; const queryClient = new QueryClient(); ReactDOM.createRoot(document.getElementById('root')!).render( , ); ``` ## Import CSS Widget components require the bundled stylesheet. Import it once in your entry point: ```typescript theme={null} import '@relayerfi/widget-kit-react/index.css'; ``` For Next.js, add this import to `app/layout.tsx` or `pages/_app.tsx`. ## Verify Installation Once providers are configured and the CSS is imported, you can render a `` component. See the [Integration Guide](/widget/integration) for a full working example. ## Next Steps Embed and theme your first widget. Understand the package ecosystem. # Integration Guide Source: https://docs.relayer.fi/widget/integration Integrate widgets using Passkey Signing (passkey) or Metadata Mode (bring your own wallet) — with examples for Swap, Transfer, and Crosschain This guide covers two integration paths: **Passkey Signing** where the widget handles passkey-based signing for you, and **Metadata Mode** where you receive transaction metadata to sign with your own stack. Both modes use the same Widget component. **Prerequisites:** wagmi providers configured per [Installation](/widget/installation). An understanding of your integration mode — see the [Overview](/widget/overview) for architecture details. ## Passkey Signing Integration In Passkey Signing, the widget manages the full transaction lifecycle: prepare, sign via passkey, and confirm. You provide a wallet adapter and an action URL or widget ID. Use the `useWagmiAdapter` hook and render a `Widget` with your action URL: ```tsx src/components/SwapWidget.tsx theme={null} 'use client'; import { useWagmiAdapter, Widget } from '@relayerfi/widget-kit-react'; export function SwapWidget() { const adapter = useWagmiAdapter(); return ( ); } ``` The Widget component handles the entire signing flow automatically. When a user initiates an action, the widget calls `POST /v1/transactions/prepare` to get an unsigned transaction, prompts the user with a passkey biometric challenge, and then calls `POST /v1/transactions/confirm` with the signed result. The widget renders a biometric prompt (Face ID, fingerprint, or security key). Signing happens inside a secure enclave — the private key never leaves the hardware-protected environment. No additional code is needed on your side. After the user signs, the widget automatically submits the signed transaction to Relayer for broadcast. The widget shows a success state with the transaction hash. In Passkey Signing, the Widget component manages the full prepare → sign → confirm lifecycle. You only need to provide the adapter and the action URL. ## Metadata Mode Integration In Metadata Mode, the widget handles UI and user input, but signing is delegated to your stack. You receive transaction metadata (routing, calldata, gas estimates) and handle signing and broadcast yourself. The Widget renders the same UI as Passkey Signing. Provide a wallet adapter and action URL: ```tsx src/components/SwapWidget.tsx theme={null} 'use client'; import { useWagmiAdapter, Widget } from '@relayerfi/widget-kit-react'; export function SwapWidget() { const adapter = useWagmiAdapter(); return ( ); } ``` The Widget renders the same UI, but in Metadata Mode the signing step is delegated to your infrastructure. When your integrator is configured for Metadata Mode, the Relayer API returns routing and calldata for your application to sign: ```typescript theme={null} const response = await fetch('https://api.relayer.fi/v1/action/execute/swap/quote', { method: 'GET', headers: { 'Authorization': `ApiKey ${apiKey}`, }, }); const { routing, calldata, gasEstimate } = await response.json(); // Build and sign with your own wallet infrastructure const tx = buildTransaction(routing, calldata, gasEstimate); const signedTx = await yourSigner.signTransaction(tx); const txHash = await yourProvider.sendTransaction(signedTx); ``` You are responsible for broadcasting the signed transaction to the blockchain. Use your own RPC provider or infrastructure. In Metadata Mode, the widget UI still renders action cards and handles user input. The difference is in how signing is handled — your backend receives the transaction parameters instead of completing the flow inside the widget. ## Widget Examples Widget Kit ships first-party widgets for the canonical protocols Relayer supports. All examples use the same Widget component pattern — only the action URL differs. ```tsx theme={null} import { useWagmiAdapter, Widget } from '@relayerfi/widget-kit-react'; export function SwapWidget() { const adapter = useWagmiAdapter(); return ( ); } ``` Swap widget renders a token-to-token swap card. Backed by `POST /v1/action/builders/swap` and executed via `/v1/action/execute/swap/*`. ```tsx theme={null} import { useWagmiAdapter, Widget } from '@relayerfi/widget-kit-react'; export function TransferWidget() { const adapter = useWagmiAdapter(); return ( ); } ``` Transfer widget handles native token and ERC20 transfers. Backed by `POST /v1/action/builders/transfer-native`. ```tsx theme={null} import { useWagmiAdapter, Widget } from '@relayerfi/widget-kit-react'; export function CrosschainTransferWidget() { const adapter = useWagmiAdapter(); return ( ); } ``` Crosschain transfer widget moves the same asset across supported networks. Backed by `POST /v1/action/builders/crosschain-transfer`. ```tsx theme={null} import { useWagmiAdapter, Widget } from '@relayerfi/widget-kit-react'; export function BridgeWidget() { const adapter = useWagmiAdapter(); return ( ); } ``` Bridge widget moves assets between supported chains. Backed by `POST /v1/action/builders/crosschain-bridge`. ## Embedder: End-User Onboarding For Embedder integrators using Passkey Signing, each end user gets their own wallet and passkey. The onboarding flow: When a new end user first interacts with a widget, they are prompted to create a passkey (biometric or security key). This registers a WebAuthn credential tied to your application's wallet workspace. A wallet is automatically created in the user's workspace. The private key is generated and stored inside a hardware-secured enclave — neither you nor Relayer can access it. For future transactions, the user simply approves with their passkey. No seed phrases, no browser extensions. Passkey recovery is available through Relayer's email recovery flow (`POST /v1/signing/recovery/initiate`). Ensure you communicate recovery options to your users during onboarding. For workspace-level integrations (not end-user facing), team members register passkeys through the Relayer dashboard. Policies define which team members can approve which transaction types. ## WidgetProps Reference | Prop | Type | Required | Description | | ----------------- | --------------------- | --------------------- | ------------------------------------------------------------------------ | | `adapter` | `WidgetAdapter` | Yes | Wallet adapter — use `useWagmiAdapter()` or `createWagmiAdapter(config)` | | `url` | `string` | Yes\* | URL to fetch widget metadata from | | `metadata` | `ValidatedMetadata` | Yes\* | Pass pre-validated metadata instead of `url` | | `securityState` | `WidgetSecurityState` | Yes (with `metadata`) | Security context for pre-validated flow | | `stylePreset` | `'x' \| undefined` | No | Apply Twitter/X visual preset | | `enableAnalytics` | `boolean` | No | Track interaction events (default: `true`) | | `player` | `boolean` | No | Enable player mode for embedded contexts | | `className` | `string` | No | Additional CSS class on the root element | | `startDisabled` | `boolean` | No | Render widget in disabled state | Either `url` or `metadata` + `securityState` must be provided, not both. ## Theming Widgets read `data-x-theme="dark|light"` from the document root to determine the active theme. ### Automatic Theme Detection (Twitter/X) Call `applyXTheme()` once on mount to read the Twitter/X night mode cookie and set the theme attribute automatically: ```typescript theme={null} import { applyXTheme } from '@relayerfi/widget-kit-react'; // Call once on mount applyXTheme(); ``` ### React Hook Use the `useTheme` hook to read the current theme in your components: ```typescript theme={null} import { useTheme } from '@relayerfi/widget-kit-react'; function MyComponent() { const theme = useTheme(); // 'dark' | 'light' return
; } ``` ### Manual Override Set the attribute directly on `document.documentElement` for custom toggle UIs: ```typescript theme={null} document.documentElement.setAttribute('data-x-theme', 'dark'); ``` ## Twitter/X stylePreset Pass `stylePreset="x"` to apply the Twitter/X visual preset — rounded card, correct font sizing, consistent with the embedded appearance on social platforms: ```tsx theme={null} ``` ## Platform Observers The library ships observers for automatic widget detection on Twitter/X, YouTube, and Twitch. These observers scan the DOM for canonical action URLs and render Widget components inline. For custom embedding in your own app, use the `Widget` component directly as shown above. See the [Widget Kit Overview](/widget/overview) for architecture details. ## Next Steps The metadata schema for canonical protocols. Package ecosystem and architecture. # Widget Kit Source: https://docs.relayer.fi/widget/overview Embed self-custodial blockchain widgets — Swap, Transfer, Bridge — with hosted passkey signing or your own wallet stack Widget Kit is a set of React components and adapters that let you embed blockchain interactions — swaps, transfers, cross-chain bridges — directly in React and Next.js apps. It is built on a self-custodial architecture: **Relayer never holds your private keys**. Transaction signing happens either through Relayer's hosted passkey flow or through your own wallet stack, depending on which integration mode you choose. ## Two Integration Modes Widget Kit supports two integration modes. The mode is determined by your wallet stack, not the widget type — every widget works with both modes. **For integrators using Relayer's managed wallet stack.** In Passkey Signing, each integrator gets a dedicated wallet workspace with passkey-based signing. The widget handles the full signing flow internally: 1. Widget sends the transaction to the Relayer API for preparation 2. User sees a biometric prompt (Face ID, fingerprint, security key) 3. The secure enclave signs the transaction using the user's passkey 4. Relayer API validates and broadcasts the signed transaction Each integrator has their own isolated wallet workspace. Team members have individual passkeys. Policies define approval rules. For **Embedder** integrators, each end user gets their own wallet and passkey — the widget guides them through passkey registration and signing. **For integrators who bring their own wallet stack (MPC, HSM, MetaMask, WalletConnect, or any EVM-compatible signer).** In Metadata Mode, the widget requests routing and calldata from the Relayer API, then hands the transaction metadata back to your application: 1. Widget sends the action to the Relayer API 2. API returns routing, calldata, and gas estimates 3. Your application builds the transaction from the returned calldata 4. Your signer signs the transaction (MPC, HSM, browser wallet, etc.) 5. Your application broadcasts to the blockchain No managed signing is involved. You control the entire signing and broadcast pipeline. ## Self-Custodial Architecture Relayer never holds your private keys. All transaction signing happens either through Relayer's hosted passkey flow (Passkey Signing) or through your own wallet stack (Metadata Mode). ### Passkey Signing Flow ```mermaid theme={null} sequenceDiagram participant User participant Widget as Widget (React) participant API as Relayer API participant SE as Secure Enclave User->>Widget: Initiate action (e.g., Swap) Widget->>API: POST /transactions/prepare API-->>Widget: { transactionId, unsignedTransaction } Widget->>User: Passkey prompt (biometric) User->>SE: Sign with passkey SE-->>Widget: signedTransaction Widget->>API: POST /transactions/confirm API->>API: Validate + broadcast API-->>Widget: { txHash, status } Widget-->>User: Success confirmation ``` ### Metadata Mode Flow ```mermaid theme={null} sequenceDiagram participant User participant App as Integrator App participant API as Relayer API participant Chain as Blockchain User->>App: Initiate action App->>API: Request routing + calldata API-->>App: { routing, calldata, gasEstimate } App->>App: Build transaction App->>User: Sign with own wallet User-->>App: signedTransaction App->>Chain: Broadcast Chain-->>App: txHash App-->>User: Success ``` ## Supported Protocols Widget Kit ships with first-party widgets for the canonical protocols Relayer supports. Custom widget metadata is not user-uploadable — every widget surface comes from this curated set. | Widget | What It Does | | ----------------------- | --------------------------------------- | | **Swap** | Token-to-token swaps via smart routing | | **Transfer** | Native and ERC20 token transfers | | **Crosschain Transfer** | Transfer the same asset across networks | | **Crosschain Bridge** | Bridge assets between supported chains | Each widget works with both Passkey Signing and Metadata Mode. The mode is determined by the integrator's configuration, not the widget type. ## Package Ecosystem The Widget Kit is composed of three core packages: | Package | npm | Description | Audience | | -------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------- | | **widget-kit-react** | `@relayerfi/widget-kit-react` | Main integration library. React components (`Widget`), wallet adapters, platform observers for Twitter/X, YouTube, and Twitch. | App developers embedding widgets | | **widget-kit-core** | `@relayerfi/widget-kit-core` | Framework-agnostic runtime. Widget directory, metadata fetching, security classification. Consumed internally. | Internal — no direct install needed | | **action-kit** | `@relayerfi/action-kit` | Shared type and validator contract used by widget-kit. Defines metadata schemas for the canonical protocols. | Internal — no direct install needed | You only install `@relayerfi/widget-kit-react` directly. The other two packages are peer dependencies bundled internally. ## The Widget Component `Widget` is the main embedding surface. It accepts a URL pointing to canonical action metadata (or a pre-validated metadata object) and renders an interactive widget card. The component handles metadata fetching, schema validation, wallet connection, and transaction signing automatically — you provide a URL and a wallet adapter, and the Widget does the rest. In Passkey Signing, the Widget handles the passkey prompt automatically. In Metadata Mode, the Widget returns transaction metadata for the integrator to sign externally. ## Platform Observers The package ships observers for automatic widget detection and rendering on Twitter/X, YouTube, and Twitch. These observers scan the DOM for canonical action URLs and render Widget components inline. A browser extension built on the same primitives is also available. For custom embedding in your own app, use the `Widget` component directly. ## Next Steps Install and configure the widget packages. Embed and theme your first widget. The metadata schema for canonical protocols. # Relayer SDK Source: https://docs.relayer.fi/widget/sdk TypeScript SDK for building blockchain action metadata — works identically in both Passkey Signing and Metadata Mode The Relayer SDK (`@relayerfi/action-kit`) is the TypeScript library for building blockchain action metadata. It defines the action schema, validates metadata before submission, and provides template helpers for common parameter types. Use it to create the metadata object that powers a Relayer Trigger. The Relayer SDK metadata layer is **mode-agnostic**. Whether your integration uses Passkey Signing (passkey) or Metadata Mode (bring your own wallet), the metadata schema and validation are identical. The SDK defines *what* action to perform — the integration mode determines *how* signing happens. ## Install ```bash npm theme={null} npm install @relayerfi/action-kit viem ``` ```bash pnpm theme={null} pnpm add @relayerfi/action-kit viem ``` `viem` is a required peer dependency. ## Core Concepts * Blockchain action metadata is a typed `Metadata` object with `url`, `icon`, `title`, `description`, and an `actions` array. * Each action in the array is one of: `transfer`, `blockchain`, `http`, `dynamic`, or a nested `flow`. * Call `createMetadata(metadata)` to validate and process the metadata. It throws if the input is invalid. * Call `validateMetadata(metadata)` for non-throwing validation that returns a detailed result object with an `errors` array. ## Action Types | Type | Purpose | Key Fields | | ------------ | ------------------------------------------------------------ | ------------------------------------------ | | `transfer` | Send native tokens to an address | `to`, `amount`, `chains` | | `blockchain` | Call a smart contract function | `address`, `abi`, `functionName`, `params` | | `http` | POST to a REST endpoint for server-side logic | `path`, `params` | | `dynamic` | Advanced server-side processing with external services | `url`, `params` | | `flow` | Multi-step interactive experience with conditional branching | `steps`, `decisions` | ## Quick Start — Transfer Action ```typescript theme={null} import { createMetadata, type Metadata } from '@relayerfi/action-kit'; const metadata: Metadata = { url: 'https://myapp.example', icon: 'https://example.com/icon.png', title: 'Send AVAX', description: 'Transfer 0.1 AVAX instantly', actions: [ { type: 'transfer', label: 'Send 0.1 AVAX', description: 'Transfer 0.1 AVAX to recipient', to: '0x1234567890123456789012345678901234567890', amount: 0.1, chains: { source: 43114 }, // Avalanche C-Chain }, ], }; const validatedMetadata = createMetadata(metadata); ``` ## Quick Start — Blockchain Action ```typescript theme={null} import { createMetadata, type Metadata } from '@relayerfi/action-kit'; const metadata: Metadata = { url: 'https://myapp.example', icon: 'https://example.com/icon.png', title: 'Approve USDC', description: 'Approve contract to spend USDC', actions: [ { type: 'blockchain', label: 'Approve', address: '0xA0b86a33E6417C8D7648D5b1D6fF0F6dB6c15b2a', abi: [/* contract ABI */], functionName: 'approve', chains: { source: 1 }, // Ethereum Mainnet params: [ { name: 'spender', type: 'address', value: '0xSpenderAddress', fixed: true }, { name: 'amount', type: 'number', label: 'Amount', required: true }, ], }, ], }; const validatedMetadata = createMetadata(metadata); ``` ## Validation Use `validateMetadata` for non-throwing validation with detailed error reporting: ```typescript theme={null} import { validateMetadata } from '@relayerfi/action-kit'; const result = validateMetadata(metadata); if (result.isValid) { console.log('Valid:', result.type); } else { console.error('Errors:', result.errors); } ``` ## Parameter Templates `PARAM_TEMPLATES` provides predefined parameter shapes (email, token select, etc.) so you do not have to hand-craft common parameter structures: ```typescript theme={null} import { createParameter, PARAM_TEMPLATES } from '@relayerfi/action-kit'; const tokenParam = createParameter(PARAM_TEMPLATES.TOKEN_SELECT, { name: 'token', label: 'Select Token', options: [ { label: 'USDC', value: 'usdc' }, { label: 'DAI', value: 'dai' }, ], }); ``` ## Supported Chains | Chain | Chain ID | | ----------------- | ---------- | | Ethereum Mainnet | `1` | | Ethereum Sepolia | `11155111` | | Avalanche C-Chain | `43114` | | Avalanche Fuji | `43113` | | Celo Mainnet | `42220` | | Base Mainnet | `8453` | | Base Sepolia | `84532` | | Mantle Mainnet | `5000` | ## API Reference | Export | Type | Purpose | | -------------------------------------- | ---------- | ---------------------------------------------- | | `createMetadata(metadata)` | function | Validate and process metadata; throws on error | | `validateMetadata(input)` | function | Non-throwing validation with `errors` array | | `isBlockchainActionMetadata(action)` | type guard | Narrow action type to `blockchain` | | `isTransferAction(action)` | type guard | Narrow action type to `transfer` | | `isHttpAction(action)` | type guard | Narrow action type to `http` | | `isActionFlow(obj)` | type guard | Narrow to nested `flow` | | `PARAM_TEMPLATES` | constant | Library of predefined parameter shapes | | `createParameter(template, overrides)` | function | Create a parameter from a template | ## Debug Your Trigger Use the [Relayer Debugger](https://app.relayer.fi/debugger) to test and preview blockchain action metadata before embedding. Paste your metadata JSON or URL and see the rendered Trigger in real time. ## Next Steps Embed the Trigger component in your app. Register and publish blockchain actions via the Relayer API.