# Signo > Signo gives you AI agents that watch your DeFi positions around the clock. Each agent follows a position or a market condition you choose, alerts you on Telegram when it matters, and prepares the next step for you to review and sign in your own wallet. Signo never holds your funds. In early access, agents can also act on their own within limits you sign onchain and can revoke at any time. - App: https://app.signo.fi - Docs: https://docs.signo.fi - Blog: https://signo.fi/blog - Pricing: https://signo.fi/pricing - X: https://x.com/signo_fi - LinkedIn: https://www.linkedin.com/company/136014545/ - Company: Flip AI, Inc. ## Key pages - Get started: https://docs.signo.fi/get-started - Agents (Notify, Prepare): https://docs.signo.fi/for-users/agents - Execute what you authorize: https://docs.signo.fi/for-users/execute-what-you-authorize - Telegram alerts: https://docs.signo.fi/for-users/telegram - Protocol coverage and risk tiers: https://docs.signo.fi/protocol-coverage - Security: https://docs.signo.fi/security - For AI assistants (MCP): https://docs.signo.fi/for-assistants/ - For integrators (API, SDK): https://docs.signo.fi/for-integrators/ - AI agents for DeFi: what Notify and Prepare agents do: https://signo.fi/blog/ai-agents-notify-prepare - Idle stablecoins: find money in your wallet that earns no yield: https://signo.fi/blog/idle-stablecoins ## Signo API > Turn a plain-English outcome into a ready-to-sign, multi-step, cross-chain transaction > plan. The planning API returns UNSIGNED transactions (EVM calldata + Solana base64 > messages); your own wallet or MPC signer signs and broadcasts. Separately, an > owner-authorized Execute permission can run within its signed on-chain limits. Base URL: https://app.signo.fi/api/v1beta Auth: `Authorization: Bearer sg_...` for partner operations (key kept server-side). Public agent drafting needs no API key; it grants no account or spending authority. Format: JSON in, JSON out, all public fields snake_case. Every response carries a `Signo-Schema-Version` header; every error uses one envelope: `{ error, friendly_message, issues[] }`. Metering: 5 credits per delivered call, debited from a per-tenant balance. ## Ways to use Signo from an AI WITHOUT A KEY (any assistant): add https://app.signo.fi/api/mcp with no Authorization header. Three tools: wallet_insights_report (read public wallet data), signo_draft_agent (draft Notify/Prepare; wallet optional) and signo_draft_permission (draft an Execute permission on X Layer or Arbitrum; wallet required). Drafts return a private review_url for owner authorization; no tool claims a draft, signs a permission, manages a private account or spends funds. Docs: https://docs.signo.fi/for-assistants WITH YOUR SIGNO ACCOUNT: add https://app.signo.fi/api/mcp/account and complete OAuth with your wallet sign-in. It exposes the three public tools plus six account tools: signo_account_agents, signo_account_agent, signo_account_agent_history, signo_account_agent_status, signo_account_agent_update and signo_account_agent_remove. Access covers the account's wallets. A connected app can pause and resume Execute; resume re-arms the existing signed permission and can allow a firing at the next check without another signature. Execute edits require owner review, and removal is refused while its permission is live. Only the wallet owner can revoke it on chain in Signo. Disconnect under Settings > Connected apps. Disconnecting removes the app's access; it does not pause agents or revoke on-chain permissions. Signing out of the browser is not the same as disconnecting an app. See https://docs.signo.fi/for-assistants. WITH A PARTNER KEY (protocols and apps): everything below. Docs: https://docs.signo.fi/for-integrators ## User-facing Advanced reasoning and ordinary token approvals Any plan may use Settings > AI Models > Advanced reasoning with a supported model subscription; on Signo Free those turns count toward the 15 requests a day. Only the providers listed there are available; the hosted connection supports ChatGPT today, and Claude is not offered until its runtime is enabled. Enable selects the provider that answers Chat; Disable switches Chat back to Signo's model but keeps the connection. When the connected model can answer, the first loading line names Advanced Reasoning, the provider and the selected model; later progress can replace that line. If the turn starts with plain Thinking, Signo's standard model handles it. Settings puts a green Enabled badge on the usable provider answering Chat and a gray Disabled badge on another usable connected provider. Pending, disconnected and failed rows have no status badge; their sentence and button show the available next step. The provider login is held in an isolated hosted-runtime snapshot, while Signo's database keeps only the provider, status, model choice and an opaque pointer. The snapshot expires after 30 days unless use refreshes it. Disconnect asks the runtime to delete the login and account-tagged copies immediately; any copy that cannot be deleted expires within 30 days. A connected turn sends the current prompt, relevant Chat history and answer instructions to the provider under the user's own account, terms, privacy settings and usage limits. Removing a wallet stops Portfolio tracking and sign-in for that address; it does not delete the account or revoke an on-chain permission. Notify and Prepare Agents stay account-owned. Their delivered history, Chat history, saved flows and saved Insights move to the first remaining same-chain wallet. With no same-chain wallet, those records are archived under the released account key and are no longer shown. They do not follow the removed wallet into another account. The removed wallet's note, per-wallet profile and prompt history are deleted, and its wallet-specific Telegram link is disabled. Execute Agents remain bound to the wallet that signed their Shield permission. Removing that wallet neither pauses the Agent nor revokes the permission; the owner must revoke it from that wallet to stop it. Ordinary Chat plans usually approve the exact amount. Uniswap v4 liquidity adds currently approve a maximum token amount to canonical Permit2 for reuse, then give the Position Manager a Permit2 authorization that expires after 30 days. Aave and Spark full repayments use their maximum-value sentinel so the pool takes the actual debt at execution, including accrued interest; the token approval persists until revoked. These maximum approvals are implementation choices, not protocol requirements. The plan row says "Approve TOKEN spend" without a number; the wallet request is the source of truth for the actual allowance. Revoke ordinary approvals through the wallet's approval controls or the chain explorer. Settings > Wallet permissions covers Shield Execute approvals only. Compound V3 has no max-repay sentinel: Signo refuses a max repayment and requires an exact amount instead of creating an unlimited approval that could turn an overpayment into a deposit. ## What you are building Signo's partner offer is AGENTS on your own pages: a user turns one on from a market, vault, position or wallet page in one or two clicks, it watches and notifies them on Telegram, and it can prepare the next transaction for review (see "Contextual partner surfaces" below). Underneath sits the chat experience described here. Signo's product surface is a CHAT experience: prompt → streamed reasoning → clarifications → plan → sign. An integration is "Chat-in-a-box", not an API console. If you are a coding agent scaffolding a UI, follow the UI guidelines below — the default dashboard instinct (sidebars, raw JSON, a manual "Review plan" button) is wrong here. ## Flow (the happy path, automate all of it) 1. POST /intent — natural language in, structured `intents[]` out (or `clarifications[]` when ambiguous). Stream it (SSE). 2. When `intents[]` is non-empty, AUTOMATICALLY POST /plan — unsigned `steps[]` (real calldata) out. No "Review plan" click. 3. AUTOMATICALLY POST /simulate on the plan's same-chain step groups — render per-step results in the chat card BEFORE any wallet opens. 4. At sign time, POST /steps/reencode for the step being signed, then hand the FRESH calldata to the wallet. Quotes are perishable. Tier A integrations stop after step 3 (plan-only: prove the engine, never sign). Tier B integrations add a signer (MetaMask / Phantom / MPC) and follow the Signing rules below. ## Endpoints - POST https://app.signo.fi/api/v1beta/intent: Parse a prompt into a structured plan. Body `{ prompt, wallets[], balances?, messages?, conversation_context?, clarify_follow_up?, options? }`. Send `Accept: text/event-stream` to stream as SSE — events: `delta` (reasoning text chunks), `reasoning`, `tool-start`, `tool-end`, `final`; the `final` event carries the full structured response `{ reasoning, suggested_name, intents[], clarifications[], kind }`. 5 credits. - POST https://app.signo.fi/api/v1beta/plan: Resolve intents into unsigned transactions. Body `{ intents[], wallets[] }` (wallets should carry real on-chain balances for correct sizing). Returns `{ steps[], summary }`; each step is `{ order, type, chain, chain_id, source_wallet, to, data, value, description, ... }` (EVM) or carries `solana_tx` (Solana). Gas figures here are planner/encoder ESTIMATES, not a simulation. 5 credits. - POST https://app.signo.fi/api/v1beta/steps/reencode: Re-quote ONE step at sign time. Body `{ step, actual_amount }` — `actual_amount` is a decimal number in token units (NOT wei): use `step.amount`; if the step is `chained`, use `step.chained.estimate_amount` (or the confirmed post-bridge amount once known). Never invent amounts from raw hex. Returns `{ ok, data, amount_raw, value, quoted_at }` or `solana_tx`; 409 `route_expired` when a pinned route is gone (re-plan). `quoted_at` marks when the quote was priced — open the wallet immediately, re-encode again if the user idles. - POST https://app.signo.fi/api/v1beta/simulate: Dry-run steps against a fork before signing. Body `{ chain, sender, steps[] }` — one call per chain; `sender` is the user's signing wallet. Returns `{ success, gas_used, gas_price, gas_price_source, total_cost, error, steps[] }` with human-readable revert reasons in `error` / `steps[].error` (e.g. "ERC20: transfer amount exceeds balance") — show them directly in the chat card. Read-only. 5 credits. - GET https://app.signo.fi/api/v1beta/usage: The tenant's credit balance and recent ledger. Returns `{ balance, cost_per_call, recent[] }`. Free. Partner/integrator metering — check the tenant balance and debug 402s server-side or in your partner ops dashboard. Not an end-user UI element: do not render a credits balance or refill CTA in Chat. - GET https://app.signo.fi/api/v1beta/meta: Capability discovery — `{ schema_version, cost_per_call, actions[], chains[], rate_tiers }`. Feature-detect from this; validate the actions/chains you render against it. Free. - POST https://app.signo.fi/api/v1beta/agents: Create an agent — plain-English `{ request, wallets[] }` (compiled, may return `needs_clarification` or `422 unwatchable`) or typed `{ kind: price|yield|health|portfolio|schedule, wallets[], ... }` (no compile step). `wallets[]` are bare address strings, not `{address,chain}` objects. Not credit-metered; gated by a per-tenant active-agent quota (429 `agent_quota_exceeded` — beta 50 / standard 500 / high 5000). Defaults to Notify; `autonomy: "prepare"` plus an `action` (typed) or an action inside `request` (plain-English) stores a Prepare agent whose fire hands back an UNSIGNED plan via `preparation.review_url` on the event (outcome ready|clarification|informational|failed|cancelled; 5 credits per `ready`, once). Explicit Prepare without an action → 422 action_required; `autonomy: execute` → 422 execute_requires_shield. Optional tenant-scoped `idempotency_key` (409 on a changed body, 410 after delete). 201 (or 200 on `needs_clarification`). - GET https://app.signo.fi/api/v1beta/agents: List the tenant's agents. Query `status` (active|paused), `kind` (price|yield|health|portfolio|schedule|condition), `wallet`, `cursor`, `limit`. Free. - GET/PATCH/DELETE https://app.signo.fi/api/v1beta/agents/{id}: Get a tenant-owned agent; `PATCH { status: paused|active }` or `PATCH { request }` (re-compile a supported `condition` agent's text only); `DELETE` is idempotent. Owner-only Execute rows are excluded from key-based control: pause and edits return not found; delete returns 204 without removing the agent. A 204 is not proof of deletion or on-chain revocation. - POST https://app.signo.fi/api/v1beta/agents/{id}/test: Manually trigger an agent once for integration testing — queues a `agent.test` event through the same delivery path a real trigger uses. 202. - GET https://app.signo.fi/api/v1beta/agent-events: Cursor-paginated pull of the tenant's trigger history — the fallback/reconciliation rail when you haven't registered a webhook endpoint, or to replay a delivery gap. Delivery is at-least-once; dedup on `id`. Free. - POST/GET https://app.signo.fi/api/v1beta/webhook-endpoints: Register (`{ url }`, https only, no private/internal hosts) or list webhook endpoints that agent triggers push to. `POST` returns `signing_secret` exactly once. Max 5 per tenant (429 `endpoint_quota_exceeded`). - DELETE https://app.signo.fi/api/v1beta/webhook-endpoints/{id}: Remove a webhook endpoint. Idempotent (204). - GET https://app.signo.fi/api/v1beta/telegram/link-status?address=&chain= — `{ address, chain, linked }` whether that wallet has an active Telegram link. Auth: Bearer sg_, scope agents. Free. Use it to pick Turn on vs Turn on with Telegram. Never call this from the browser with an sg_ key. - GET https://app.signo.fi/api/v1beta/positions?wallet=&protocol=&kind=&chain=: a wallet's ADDRESSABLE positions, each row carrying the canonical composite `id` already built: `{ wallet, positions[], degraded, unavailable[] }`, each position `{ id, owner, protocol, reference, kind, chain, asset, value_usd, apy_pct?, days_to_expiry?, claimable?, started?, accepted_hours_ago?, fill_pct? }`. Send a row's `id` straight to /surfaces/resolve and /surfaces/activate as the `position` subject. Never assemble the string. Pendle (pt, yt, lp) and Saffron (fixed, variable) today; other protocols hold real positions but cannot be a position subject, so they are not listed. `degraded: true` with a non-empty `unavailable` means a source could not be read, so do NOT treat `positions: []` as "holds nothing"; every source failing is 503 `positions_unavailable`. Auth: Bearer sg_, scope agents. Free. One wallet per call. - POST https://app.signo.fi/api/v1beta/agent-drafts: KEYLESS (no Authorization). `{ request, wallet?, autonomy?: notify|prepare }` (wallet optional: omit it and the first wallet to sign on the review page owns the agent) → 201 `{ proposal_id, review_url, expires_at, status: "draft", wallet, summary, autonomy, action, activation }`. Stores an INACTIVE draft only: the owner must open `review_url`, sign a draft-bound challenge, link Telegram and explicitly press Set up agent. No agent, subscription or spending authority is created; a claimed agent is user-owned and not in the tenant's `GET /agents`. Rate limited per client and globally (429 + Retry-After); drafts expire after 72 h; 422 on ambiguous/unwatchable; 503 if the draft service is not configured. Execute is not available here. - POST https://app.signo.fi/api/v1beta/shield/proposals: Draft a Signo Shield permission, an Execute agent (partner key with agents scope, or NO key under the draft rate limits; free) and return `{ proposal_id, review_url, summary, limits, fee_bps, expires_at }`. NEVER authorizes spending — the owner signs the permission on the review page. New Execute setup is in early access: a wallet without access gets 403 `execute_team_only`, no proposal is stored, and the owner can contact support@signo.fi. One shape: `{ schema_version: 2, wallet, chain_id: 196|42161, trigger (health_factor | token_balance | price | condition), action (transfer|swap|vault|supply|repay_from_wallet|repay_from_collateral), budget, cap_per, expiry_days? }`. The old protection short-form (no schema_version) is retired and answers 400 with the mapping. X Layer (chain_id 196) and Arbitrum One (42161) today. 422 with the compiler reason when it cannot be drafted. - GET https://app.signo.fi/api/v1beta/shield/proposals/{id} | /permission | /runs?limit=: Read a proposal you created, the permission once authorized (null before), and its bounded execution runs. Tenant-scoped; not yours → 404. ## Public Wallet Insights API Separate from `/v1beta`, `POST https://app.signo.fi/api/insights` accepts any public EVM or Solana address and returns `signoScore` (0-100, the Signo Score: how well the wallet's capital is managed, from idle cash, yield against the cash benchmark, unclaimed rewards, the leverage guard and dust; not a safety or liquidation-risk score), `riskTolerance`, `walletAge`, `holdings` (every chain's tokens, DeFi positions and loans with USD values, largest first, up to 30 tokens per chain with the remainder counted, and a `complete` flag), `categories` (each with state, headline, detail, a next step and, where an agent fits, `agentSuggestion`), `suggestedAgents` plus `draftWith` (the exact `request` and `autonomy` arguments for `signo_draft_agent`, and the endpoint to call), and an `appUrl` for the next step. Body: `{ address, chain?, narrative? }`. It is read-only, keyless, free of `/v1beta` credits, IP-rate-limited, and cached per address for 24 hours. It cannot read the owner's private Agents, so watch coverage is `not_applicable` with "Agent coverage isn't shown here" and the public score omits that category. A signed-in owner's app scan adds watch coverage, so its score can differ from the public report. The same report is the `wallet_insights_report(address, narrative?)` tool on BOTH keyless Streamable HTTP MCP servers: `https://app.signo.fi/api/mcp` (next to the drafting tools, so one connection reads the report and drafts the agents it suggests) and `https://app.signo.fi/api/insights/mcp` (the OKX.AI listing endpoint). ## UI guidelines (make it feel like Signo) Layout - Single chat column, composer pinned at the bottom. Not a multi-panel admin layout. - Wallet connect stays compact (top-bar pills), never a full sidebar. - Reasoning, clarification chips, and the unsigned plan render INLINE in the thread, under the Signo message that produced them. - Never dump `intents[]` or step JSON in the primary UI. Structured payloads belong in logs/devtools. Calldata goes behind a disclosure, not a wall of hex. Flow - Stream /intent (SSE) into the Signo bubble as it arrives; show tool status lines while it thinks. - Clarifications render as option chips in the chat, then re-send with `clarify_follow_up` (template below) — never a modal form. - Auto-/plan when intents arrive; render each step as a readable card (title, chain, protocol, human description). Tone - Composer placeholder and empty state sound like chat ("Message Signo…"), with 2-3 example prompts as chips. Not an API console. Anti-patterns (do not ship these) - Two-column "Wallets | Chat" dashboards. - A required manual "Review plan" click after every intent. - Primary UI that is mostly OpenAPI-shaped JSON. - Hiding the plan until the user clicks something else. - A partner credits balance or "refill credits" control in the end-user chat UI — `/usage` is integrator metering, not end-user chrome. ## Signing (Tier B) - Execute `steps[]` in ascending `order`. Typical pattern: approve → wait for confirmation → swap. Soft-disable later Sign buttons until prerequisites confirm (allowance set, bridged funds arrived, `chained` amount known). Respect `wait_for_bridge` before simulating or signing post-bridge steps. This is UX guidance, not server-enforced — but out-of-order signing produces wallet failures and support tickets. - ALWAYS re-encode before signing: POST /steps/reencode, hand the returned calldata to the wallet, sign promptly. Re-encode again if the user idles past ~30s (`quoted_at` tells you the age). - HARD-FAIL on re-encode failure: if /steps/reencode returns an HTTP error or `ok: false`, do NOT fall back to plan-time calldata. Show `unsupported_reason` in the chat and stop. Plan-time calldata embeds quote-time amounts and deadlines; signing it stale can pass a simulation and still revert on-chain. - Branch on `unsupported_code`, not the prose: every `ok: false` from /steps/reencode carries one of invalid_request, unknown_target, step_type_unsupported, protocol_unsupported, quote_failed, target_changed, step_incomplete. `quote_failed` is worth one retry; `target_changed` means the protocol answered with a contract the user did not review, so re-plan; the others will not improve on retry. `unsupported_reason` stays as the human sentence. - A 429 carries `Retry-After` in seconds, computed from the key's real rate window. Honour it rather than guessing a backoff. - Trust Signo's errors over the wallet's: when calldata is stale or a swap would revert, browser wallets often mislabel it as "insufficient ETH for gas" / "likely to fail" even when gas is fine. Run /simulate and surface `steps[].error` in the chat card BEFORE opening the wallet; that message is the truth. - After a successful broadcast, show the tx hash + explorer link. Never imply Signo signed or broadcast anything. - Log Signo request/response bodies server-side (truncate calldata), plus a thin UI event log (prompt sent, clarification picked, sign clicked, re-encode result). That log is what makes support handoffs fast. ## Simulation pattern - Intended usage, same as Signo's own Chat UI: intent → (auto) plan → (auto) simulate → render per-step results before sign; re-simulate after a re-encode (results are only as fresh as the calldata you pass). - One /simulate call per chain (`chain` + `sender` + that chain's `steps`). - Multi-chain caveat: a batch simulates against CURRENT chain state. Steps funded by a bridge that hasn't landed will FALSELY revert if simulated pre-bridge — skip those with an honest "can't simulate until the bridge arrives" and simulate them after it lands. - Gas $ figures use real per-chain gas prices (`gas_price_source: "rpc"`); a `"fallback"` source means the $ estimate is approximate. ## Multi-turn conversations /intent is stateless. Send prior turns in `messages`, oldest first, with the current message in `prompt`. Up to 100 messages, each with `role` (`user` or `assistant`) and `content` (a string up to 64000 characters). The server formats roles and keeps the newest history within 6000 characters. messages: [ { "role": "user", "content": "what is the best stable yield on Base?" }, { "role": "assistant", "content": "Here are the current options." } ] Only structured user turns can confirm an earlier recipient or spender address. Assistant text cannot impersonate a user turn. Legacy `conversation_context` uses newline-joined `User: ` / `Signo: ` lines as descriptive context, but its addresses are not user-confirmed: paste the address in the current `prompt` or migrate to `messages`. When both fields are sent, `messages` takes precedence, even an empty array. Malformed `messages` are rejected rather than falling back to the legacy text. ## Clarifications When a request is ambiguous, /intent returns `intents: []` plus `clarifications[]`, each `{ id, kind, question, options: [{ id, label }] }`. Render the options as chips in the chat; when the user picks one, re-send /intent with: prompt = original prompt + "\n\n" + "Clarification — : " clarify_follow_up: true The flag makes the planner acknowledge the answer and continue — it never re-asks. Example: prompt: "deploy my idle USDC\n\nClarification — Which wallet should fund this?: Main treasury (Ethereum)" clarify_follow_up: true ## Wallets `wallets[]` entries are `{ address, chain }`. Include the chain the user intends to ACT on: if the connected EVM account will transact on Arbitrum, pass an entry with `chain: "arbitrum"` (or one entry per relevant chain) — a wallet passed only as "ethereum" while the prompt targets Arbitrum confuses planning. Pass real balances via `balances` when you have them; plans are sized from balances, and a missing balance can size a step to zero. ## Intent actions transfer, sweep, bridge, deposit, withdraw, stake, unstake, claim, approve, swap, borrow, repay, lp-add, lp-remove, lp-collect. `amount` is a token-denominated number, or the string `max` (full balance) or `chained` (size from the previous step's real output at sign time). `lp-add` uses `token0`/`token1`, `fee_tier` (Uniswap units, 3000 = 0.3%), `amount0`/`amount1`, optional `slippage_bps` — full-range positions only. ## Chains ethereum, base, arbitrum, optimism, polygon, xlayer, robinhood, solana. EVM addresses are 0x-hex; Solana addresses are base58 (never lower-cased). ## Pendle by chain Filled from the capability matrix in code (`lib/pendle/capabilities.ts`), one row per CHAINS entry: - Ethereum: market discovery, PT/YT/LP reads, positions, convert trading, claims, watchers, PT-loop handoff, boxed-loop reads. - Base, Arbitrum, Optimism, Robinhood Chain (4663): discovery, positions, convert trading, claims, watchers. No boxed-loop reads, no in-product PT loop. - Polygon, Solana, Hyperliquid: not a Pendle chain. `getPendleMarkets` / a Pendle plan on those names the matrix, it does not return an empty list. Convert on Robinhood Chain is live for SY-list tokens (RouterV4). Aggregator zaps of arbitrary tokens can still 400. ## Request ids Every response carries a `Signo-Request-Id` header, streaming responses included, and every error body repeats it as `request_id`. Send the header yourself and we keep your value, so one id joins your logs, your user's report and ours; omit it and we generate one. Log it on every call. Accepted shape is 8-128 characters of `A-Za-z0-9._:-`; anything else is replaced with a generated id. ## Errors Single envelope `{ error, friendly_message, issues[] }`. Common codes: - 400 invalid_request — malformed body or unsupported chain/asset (`issues[]` lists offending fields). - 400 invalid_url — a webhook endpoint URL failed the safety check (not https, or a private/internal host). - 401 invalid_key — missing, malformed, or revoked key. - 402 insufficient_credits — tenant balance below the call cost. - 403 scope_forbidden — key not authorized for the endpoint. - 409 route_expired — a pinned route is gone; re-plan the step. - 422 unwatchable — an agent `request` isn't a measurable trigger; rephrase it. - 429 rate_limited — per-key tier exceeded; back off and retry. - 429 agent_quota_exceeded / endpoint_quota_exceeded — per-tenant agent or webhook-endpoint cap hit; pause/delete one first. ## Agents & webhook delivery Programmatic alerts (price / yield / health-factor / portfolio-move / schedule / a plain-English condition), scoped to your tenant, not credit-metered. Recommended setup order for an integration: register a webhook endpoint (`POST /webhook-endpoints`) → create an agent (`POST /agents`) → receive signed `POST` events at that endpoint. Registering an endpoint is optional, not required — an agent created with none registered still arms, evaluates, and triggers normally; it just has nothing to push to, so poll `GET /agent-events` instead (the same triggers, cursor-paginated). Every push is signed: `X-Signo-Signature: t=,v1=hex_hmac_sha256(signing_secret, ".")`, plus `X-Signo-Event-Id` (dedup key, at-least-once delivery, retried with backoff 1m/5m/30m/2h/12h before dead-lettering). Full walkthrough, typed-kind field reference, and a copy-paste signature-verification snippet: https://docs.signo.fi/for-integrators/agents Agent ids: create, list, get, and the `agent.id` inside a trigger event ALL return the same public id, shaped `w__` (e.g. `w_alert_nn730k1p...`). Store it and pass it back verbatim on `/agents/{id}`, `/agents/{id}/test`, PATCH and DELETE. The prefix is the storage TYPE (alert | condition | reminder), NOT the public `kind` — a `kind:"price"` agent is `w_condition_...` — so you cannot derive the id from `kind`; use the id you were given. A bare, unprefixed id returns 404 agent_not_found. Wallets: Notify and Prepare agents are READ-ONLY at fire time (a Prepare plan is unsigned until the owner signs), so any address works — no signature, no connected session, no ownership check. `wallets[0]` is the owner for bookkeeping; array case is preserved (Solana is case-sensitive). Display: every agent carries a `rendered` one-liner for UI ("BTC price is above $65,350"); plain-English ones also carry `intent`. Prefer `rendered` over building your own label per kind. Clarification round-trip: a too-broad plain-English `request` returns 200 (NOT 201) with {status:"needs_clarification", question, options:[{id,label}]}. Both are 2xx — branch on the BODY (`status === "needs_clarification"`), never on res.ok, or you'll treat a clarification as a created agent. Send the option's LABEL back, not its id (ids are positional per response, nothing server-side remembers them); `options` may be empty (cap 4), in which case show a free-text input. Re-POST the ORIGINAL request with the answer folded in — `\n\nClarification — :