Docs

API reference

Every route the Sama app uses. JSON in and out, cookie sessions, bigints as tagged strings.

Conventions

  • Base URL is NEXT_PUBLIC_SAMA_API_URL. Send credentials: "include" so the sama_session cookie travels.
  • Bigints travel as {"$bigint": "<decimal>"} in both directions.
  • Every response carries an x-request-id header. Errors are { error, requestId }.
  • State-changing requests from an origin outside SAMA_ALLOWED_ORIGINS get 403.
StatusMeaning
400Invalid input (for example weights that don't add up to 100%)
401No session, or the sign-in token didn't verify
404Not found, or not visible to you
409A round or Circle rule blocks the step (wrong state, not a member)
502 / 503PancakeSwap, Binance, the database or the RPC is unavailable; retry shortly

Session

MethodPathDescription
POST/api/sessionExchange a Privy access token { token, address } for the session cookie
GET/api/sessionThe signed-in address, or null
DELETE/api/sessionSign out

Public

MethodPathDescription
GET/api/healthDatabase, chain, settlement contract and configured providers
GET/api/assetsThe allowlist with prices, tiers and disclosures
GET/api/proofContract deployment and every settled round
GET/api/invites/:codeWhich Circle an invite opens, and whether it was used

Your account

MethodPathDescription
GET/api/me/homePortfolio, target, drift, Circles, rounds waiting on you, recent activity
GET/api/me/portfolioLive balances and values, plus your saved target
GET/api/me/portfolio/history?range=Value over time: 1H, 1D, 1W, 1M, 1Y, ALL
POST/api/me/target/previewCheck a target against your wallet without saving
GET / POST/api/me/targetRead or save your target
GET / POST/api/me/settingsNotification and default preferences
GET/api/me/activityPaged history: limit (30), cursor, group, range
POST/api/me/transfers/syncScan the chain now so a transfer you just sent shows in Activity

Circles

MethodPathDescription
GET / POST/api/circlesCircles you can see / create one
GET/api/circles/:idOne Circle with its rounds and your role
POST/api/circles/:id/joinJoin, with { invite } when the Circle needs one
POST/api/circles/:id/inviteCreate a single-use invite link (organizer)
POST/api/circles/:id/roundThe live round, opening one with a fresh snapshot if none is running

Rounds

MethodPathDescription
GET/api/rounds/:idThe round from your point of view (RoundView)
GET / POST/api/rounds/:id/intentBuild your intent and its EIP-712 payload / submit { intent, signature }
POST/api/rounds/:id/closeOrganizer closes collection early and matches
GET / POST/api/rounds/:id/approvalPlan approval payload and allowances / submit { signature }
GET / POST/api/rounds/:id/settleThe settle() call to send / report { txHash }
POST/api/rounds/:id/residualDecide leftovers: { choice: "CARRY_FORWARD" | "CANCEL" }
POST/api/rounds/:id/residual/swapSwap leftovers in three steps: prepare, build, record

Example: join a round

join-round.ts
import { createWalletClient, custom } from "viem";
import { bsc } from "viem/chains";

const api = (path: string, init?: RequestInit) =>
  fetch(process.env.NEXT_PUBLIC_SAMA_API_URL + path, { credentials: "include", ...init }).then((r) => r.json());

// 1. Build the intent from your saved target, live balances and the round snapshot.
const { intent, typedData } = await api(`/api/rounds/${roundId}/intent`);

// 2. Sign it. Free: an EIP-712 signature moves nothing.
const wallet = createWalletClient({ chain: bsc, transport: custom(window.ethereum) });
const [account] = await wallet.getAddresses();
const signature = await wallet.signTypedData({ account, ...typedData });

// 3. Submit. Bigints in `intent` are sent back as {"$bigint": "..."}.
await api(`/api/rounds/${roundId}/intent`, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ intent, signature }, (_k, v) => (typeof v === "bigint" ? { $bigint: v.toString() } : v)),
});