REST API

Base URL: https://api.deploytalon.com/api/v1 (local: http://localhost:4000/api/v1). OpenAPI JSON at /api/v1/openapi.json, Swagger UI at /api/v1/docs.

Authentication

TALON accounts are wallet only. A user signs in by connecting an EVM wallet and signing a Sign-In With Ethereum (EIP-4361) message. There is no email signup, magic link, password, or social login. The connected wallet is the owner of that user's agents; each agent still has its own server wallet.

Credentials, checked in order:

  1. Authorization: Bearer tlk_live_... or tlk_test_... API key (created on /developers or by the CLI device flow). Keys are SHA-256 hashed at rest.
  2. A better-auth session from wallet sign-in: the better-auth.session_token cookie (the dashboard), or the session token as Authorization: Bearer <token>.
  3. X-Talon-Internal-Token + X-Talon-User-Id (service-to-service, used by the Telegram bot).

Every request is scoped to the caller's user. Agents belong to users; a request for an agent the user does not own returns 404.

Cookie sessions and CSRF: a POST, PUT, PATCH, or DELETE authenticated by the session cookie (no Authorization header) must send Origin: <WEB_URL> (or <API_BASE_URL>) and a JSON or multipart body. Anything else, such as a cross-site form or a text/plain body, is 403 FORBIDDEN. API keys and bearer session tokens are not affected. The session cookie is SameSite=Lax when SESSION_COOKIE_SAMESITE=lax (recommended: serve the API on the same site as the dashboard) and SameSite=None; Secure otherwise in production.

Wallet sign-in

better-auth is mounted at /api/auth/* on the API service (https://api.deploytalon.com/api/auth, local http://localhost:4000/api/auth). Both routes are POST (a GET returns 404), send Origin: <WEB_URL>, and are limited to 20 requests per minute per IP. The client IP comes from the trusted proxy (TRUSTED_PROXY_HOPS entries from the right of X-Forwarded-For, or CLIENT_IP_HEADER), never from the client-written left of the header. Both responses carry X-Server-Time (ms since epoch); build Issued At and Expiration Time from it so a device with a wrong clock can still sign in.

StepMethodPathBodyReturns
1POST/api/auth/siwe/nonce{} (or no body){ nonce }: 24 alphanumeric characters, single use, valid 15 minutes
2walletpersonal_signthe EIP-4361 message below0x... signature
3POST/api/auth/siwe/verify{ message, signature } (no other fields){ token, success: true, user: { id, walletAddress, chainId } } and a Set-Cookie session

The message (build it with viem createSiweMessage) must contain:

FieldRequirement
domainSIWE_DOMAIN, default the host of WEB_URL (for example www.deploytalon.com, or localhost:3000 in dev)
addressthe signing wallet
URIany URL on the WEB_URL origin
Version1
Chain IDthe wallet's chain; smart contract wallets must use Robinhood Chain (4663 in production; 4663 or 46630 in development)
Noncethe value from step 1
Issued Atnow, by the server clock (X-Server-Time); rejected if more than 60 s in the future or older than 10 minutes at verify time
Expiration Timerequired, in the future (5 minutes is typical)
Not Beforeoptional, honored
www.deploytalon.com wants you to sign in with your Ethereum account:
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266

Sign in to TALON.

URI: https://www.deploytalon.com/login
Version: 1
Chain ID: 4663
Nonce: Q7mZk2PxT9wLrV4aH3nB8cDe
Issued At: 2026-09-23T12:00:00.000Z
Expiration Time: 2026-09-23T12:05:00.000Z

Any failure is 401 with a code: UNAUTHORIZED_INVALID_OR_EXPIRED_NONCE (unknown or reused nonce), UNAUTHORIZED_SIWE_MESSAGE_EXPIRED, UNAUTHORIZED_SIWE_MESSAGE_MISMATCH (domain, address, or chain ID the plugin could not accept), SIWE_CLOCK_SKEW (Issued At outside the window), SIWE_DOMAIN_MISMATCH (domain or URI origin), SIWE_SMART_WALLET_CHAIN (not an EOA signature and not on an accepted smart wallet chain, or a smart wallet registered on another chain), SIWE_BAD_SIGNATURE, or SIWE_INVALID_MESSAGE. A nonce is consumed on the first verify attempt, even a failed one; request a new one to retry. EOA signatures are verified locally. On Robinhood Chain, ERC-1271 and ERC-6492 smart wallet signatures are verified through the chain RPC (in production only on the production chain); on other chains only EOA signatures are accepted.

The first sign-in creates the user and records the wallet as the primary (owner) wallet. Signing in later with the same EOA address, on any chain, reaches the same user. A smart wallet is bound to the chain it registered on: the same contract address on another chain may have a different controller, so that sign-in is refused. Leftover better-auth routes (email, password, social, update-user, and similar) return 404. GET /api/auth/get-session returns the current session and POST /api/auth/sign-out ends it. The web client uses better-auth's siweClient() plugin, which calls these same routes.

API keys and the CLI

API keys work exactly as before and are unaffected by wallet sign-in. The CLI gets one through a device-code flow: the CLI starts it, the user approves the code on WEB_URL/device while signed in with their wallet, and the CLI receives a normal tlk_live_ key. Endpoints are listed under Auth: device flow.

Conventions

  • JSON in, JSON out. Errors: { "error": { "code": "POLICY_DENIED", "message": "...", "details": {} } }.
  • Amounts are strings in human units unless the field ends in Wei or Raw.
  • Write endpoints accept Idempotency-Key. A replay returns the original response.
  • Rate limit: 120 req/min per key (X-RateLimit-* headers). Wallet sign-in (/api/auth/siwe/*): 20 req/min per IP.
  • Sensitive actions may return 202 { "approval": { id, status: "pending" } } instead of a result. Poll GET /approvals/:id or resolve it from the dashboard or Telegram.

Tools endpoint (canonical)

POST /tools/:name with { "agentId": "...", "input": { ... } } runs any tool from packages/tools (see docs/tools.md). Response { "ok": true, "result": ... } or 202 with an approval. GET /tools lists tools with their JSON schemas. Everything below is an alias that maps 1:1 onto a tool.

Agents

MethodPathToolNotes
GET/agentslist the user's agents
POST/agents{ name, avatarUrl?, modelProvider, model, systemPrompt?, chainId? } creates the agent and its own server wallet. The default policy whitelists the owner wallet for transfers (withdrawals), which still require approval
GET/agents/:idprofile, wallet address, full policy and policySummary (served from the DB, same data as agent_get)
PATCH/agents/:idname, avatar, model, systemPrompt, status
DELETE/agents/:idarchives (wallet is retained)
GET/agents/:id/policyfull policy JSON
PUT/agents/:id/policyreplace policy (validated with the zod schema)
GET/agents/:id/balanceswallet_balances
GET/agents/:id/portfoliowallet_portfolio
GET/agents/:id/historywallet_history?limit=
POST/agents/:id/transferwallet_transfersensitive
POST/agents/:id/chatstreaming chat with the agent runtime (SSE, Vercel AI SDK data stream)
GET/agents/:id/messageschat history

Market

MethodPathTool
GET/tokens/:addressOrSymboltoken_lookup
GET/tokens/:address/pricetoken_price
GET/tokens/:address/chart?interval=5m&limit=200token_chart
GET/tokens/:address/trades?limit=token_trades when registered, else { ok: true, result: { trades: [] } }
GET/market/trending?window=1hmarket_trending
GET/market/new?limit=50&quote=ETHmarket_new_launches
GET/market/stocksstock_tokens_list

Market endpoints are public (no auth) and cached for 5 to 15 seconds.

Trading

MethodPathTool
POST/agents/:id/swap/quoteswap_quote
POST/agents/:id/swapswap_execute
POST/agents/:id/curve/buycurve_buy ({ quoteOnly: true } runs curve_quote with side: "buy")
POST/agents/:id/curve/sellcurve_sell ({ quoteOnly: true } runs curve_quote with side: "sell")
POST/agents/:id/approveapprove_token

Launch and earn

MethodPathTool
POST/launch/quotelaunch_quote
POST/agents/:id/launchlaunch_token (sensitive)
GET/agents/:id/earningsearnings_get
POST/agents/:id/earnings/claimearnings_claim
POST/uploads/imagemultipart image upload to IPFS, returns { cid, url }

Automations

MethodPathTool
GET/agents/:id/automationsautomation_list
POST/agents/:id/automationsautomation_create
DELETE/agents/:id/automations/:automationIdautomation_cancel

Approvals (user side)

MethodPathTool
GET/approvals?status=pendingapproval_list
GET/approvals/:id
POST/approvals/:id/resolve { decision: "approve" | "deny" }approval_resolve (responds 200 { approval, result }; if execution fails the mapped error is returned with the approval marked)

Developer

MethodPath
GET/keys
POST/keys { name, env } returns the plaintext key once
DELETE/keys/:id
GET/me returns { id, walletAddress, chainId, name, image, createdAt, telegramLinked, user, agentCount, telegram }. walletAddress is the owner wallet (EIP-55 checksummed, null only for accounts created before wallet sign-in)
POST/telegram/link returns { code, deepLink }; the code is valid for 10 minutes

Internal (Telegram bot, X-Talon-Internal-Token only):

MethodPath
POST/internal/telegram/claim { code, chatId } links the chat to the code's user; returns { ok, userId, chatId, user: { id, name, walletAddress, chainId } }
GET/internal/telegram/users/:chatId returns { userId, user: { id, name, walletAddress, chainId }, agents }
GET/internal/telegram/chat-for-user/:userId returns { chatId: string | null }

Any other route can be called on behalf of a user with X-Talon-Internal-Token plus X-Talon-User-Id.

Auth: device flow

Wallet sign-in lives at /api/auth/siwe/* (see Authentication). The CLI uses a device-code flow implemented by the API (Redis backed, 10 minute codes) rather than the better-auth plugin, because it must end in an API key:

MethodPathBodyReturns
POST/api/v1/auth/device/code{ clientId? }{ deviceCode, userCode, verificationUri, verificationUriComplete, expiresIn, interval }
POST/api/v1/auth/device/approve{ userCode, decision? } (wallet session, from WEB_URL/device){ ok: true, status: "approved" }
POST/api/v1/auth/device/token{ deviceCode } (poll every interval s){ apiKey, keyId, prefix, userId } or 400 { error: { code: "authorization_pending" | "expired_token" | "access_denied" } }

The same routes are aliased under /api/auth/device/*.

Webhooks and events

GET /agents/:id/events?since= returns the audit log (approval created, tx confirmed, launch, graduation of a token the agent created).