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. Sendcredentials: "include"so thesama_sessioncookie travels. - Bigints travel as
{"$bigint": "<decimal>"}in both directions. - Every response carries an
x-request-idheader. Errors are{ error, requestId }. - State-changing requests from an origin outside
SAMA_ALLOWED_ORIGINSget403.
| Status | Meaning |
|---|---|
| 400 | Invalid input (for example weights that don't add up to 100%) |
| 401 | No session, or the sign-in token didn't verify |
| 404 | Not found, or not visible to you |
| 409 | A round or Circle rule blocks the step (wrong state, not a member) |
| 502 / 503 | PancakeSwap, Binance, the database or the RPC is unavailable; retry shortly |
Session
| Method | Path | Description |
|---|---|---|
| POST | /api/session | Exchange a Privy access token { token, address } for the session cookie |
| GET | /api/session | The signed-in address, or null |
| DELETE | /api/session | Sign out |
Public
| Method | Path | Description |
|---|---|---|
| GET | /api/health | Database, chain, settlement contract and configured providers |
| GET | /api/assets | The allowlist with prices, tiers and disclosures |
| GET | /api/proof | Contract deployment and every settled round |
| GET | /api/invites/:code | Which Circle an invite opens, and whether it was used |
Your account
| Method | Path | Description |
|---|---|---|
| GET | /api/me/home | Portfolio, target, drift, Circles, rounds waiting on you, recent activity |
| GET | /api/me/portfolio | Live 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/preview | Check a target against your wallet without saving |
| GET / POST | /api/me/target | Read or save your target |
| GET / POST | /api/me/settings | Notification and default preferences |
| GET | /api/me/activity | Paged history: limit (30), cursor, group, range |
| POST | /api/me/transfers/sync | Scan the chain now so a transfer you just sent shows in Activity |
Circles
| Method | Path | Description |
|---|---|---|
| GET / POST | /api/circles | Circles you can see / create one |
| GET | /api/circles/:id | One Circle with its rounds and your role |
| POST | /api/circles/:id/join | Join, with { invite } when the Circle needs one |
| POST | /api/circles/:id/invite | Create a single-use invite link (organizer) |
| POST | /api/circles/:id/round | The live round, opening one with a fresh snapshot if none is running |
Rounds
| Method | Path | Description |
|---|---|---|
| GET | /api/rounds/:id | The round from your point of view (RoundView) |
| GET / POST | /api/rounds/:id/intent | Build your intent and its EIP-712 payload / submit { intent, signature } |
| POST | /api/rounds/:id/close | Organizer closes collection early and matches |
| GET / POST | /api/rounds/:id/approval | Plan approval payload and allowances / submit { signature } |
| GET / POST | /api/rounds/:id/settle | The settle() call to send / report { txHash } |
| POST | /api/rounds/:id/residual | Decide leftovers: { choice: "CARRY_FORWARD" | "CANCEL" } |
| POST | /api/rounds/:id/residual/swap | Swap 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)),
});