Read pod’s markets, sign in with a Solana wallet and build trades your wallet signs. This guide covers every public route.
Overview
pod’s API is the same one the pod app uses. It serves market data for pairs, baskets and pools, signs you in with a Solana wallet, and builds unsigned transactions that your wallet signs and sends.
- Base URL:
https://api.usepod.fun. Every route is versioned under/v1. - Network: Solana devnet. Transactions the API builds are for devnet, and devnet assets have no value.
- Format: JSON in and out, with
camelCasefield names. Sendcontent-type: application/jsonon requests with a body. - No API key. Reads are open and rate limited by IP. Anything personal, and every transaction builder, needs a wallet session (see Authentication).
- Non-custodial. The API never holds your keys and never signs for an external wallet. It returns a transaction; you decide whether to sign it.
The API is in preview and can change without notice while pod is on devnet. Using it is covered by the Terms of Service.
Quick start
No setup needed for reads. Platform totals:
curl https://api.usepod.fun/v1/stats{"pools":10,"tvlUsd":0.71,"volume24hUsd":0.0,"fees24hUsd":0.0,"lps":10,"pairs":22,"graduated":1}The newest pairs, then one pair’s hourly candles:
curl "https://api.usepod.fun/v1/pairs?sort=new&limit=5"
curl "https://api.usepod.fun/v1/pairs/<pool>/candles?tf=1h&limit=48"A pair is addressed by its pool address (the pool field), which is also its URL on usepod.fun: usepod.fun/pair/<pool>. Transaction builders take the token’s mint instead.
Conventions
Numbers
Display figures (prices, USD volumes, market caps, percentages) are JSON numbers. On-chain amounts are strings of base units: an integer in the token’s smallest unit, so "1500000" is 1.5 of a 6-decimal token. Each pair’s decimals are on GET /v1/curve/pairs/{mint}. Never convert base-unit strings to floating point before you send them back.
Time
Timestamps are RFC 3339 strings (createdAt) or Unix milliseconds (ts, candle t), as named in each response.
Pagination
List routes take limit, clamped to a per-route maximum. GET /v1/pairs also takes cursor, an offset: pass cursor=40 to skip the first 40. Paged responses are { "items": [...], "nextCursor": null }; today nextCursor is always null, so page by offset until you get fewer than limit items.
Caching
Every response carries Cache-Control for its route class. Registry data (assets, baskets) is cacheable for 60 seconds, feeds for 5, live resources for 2 and prices for 1. Anything under /v1/me, and every error, is private, no-store. Polling faster than a route’s max-age returns the same data.
Browsers and CORS
Cross-origin browser requests are only accepted from pod’s own sites. Call the API from your server, a script or a bot. A browser app on another domain can’t use it directly.
Timeouts
A request that takes longer than 15 seconds on the server returns 504. Transaction builders read chain state, so retry a 504 or 502 with a short backoff.
Errors
Every error has the same JSON shape, with a stable machine-readable code and a human-readable message:
{"error":{"code":"unknown_timeframe","message":"2h is not one of 1s, 5s, 15s, 1m, 5m, 15m, 1h, 4h, 1D"}}| Status | Common codes |
|---|---|
| 400 | invalid_request (body or query couldn’t be read), plus route-specific codes such as unknown_timeframe, nonce_invalid or invalid_address. |
| 401 | unauthorized: no valid session. Sign in again. |
| 403 | Route-specific: the session is valid but can’t do this. |
| 404 | not_found or a more specific code. |
| 409 | Conflicts, for example a resource that already exists. |
| 429 | rate_limited or too_many_live_connections. Wait for Retry-After seconds. |
| 502 / 503 | An upstream (Solana RPC, price source) failed, or a feature is off on this deployment, for example curve_unavailable. |
| 500 | internal: our fault. The message is deliberately generic. |
Branch on code, not on message: messages are written for people and may change.
Rate limits
Limits are per client IP address:
- Most routes: a burst of 120 requests, refilling at 2 per second (120 a minute sustained).
- Sign-in routes (
/v1/auth/*): a burst of 10, refilling at 6 per second. - Live WebSocket: at most 5 open connections per IP.
Responses carry x-ratelimit-limit and x-ratelimit-remaining. Over the limit you get 429 with a Retry-After header. Prefer the live WebSocket over tight polling for anything that changes every few seconds.
Authentication
pod uses Sign In With Solana. You ask for a one-time message, sign it with the wallet’s key (no transaction, no fee), and get back a session cookie. The session is tied to that wallet: every builder you call afterwards builds for it.
- Get a challenge.
POST /v1/auth/noncewith the wallet address. The nonce is single-use, valid for 5 minutes, and replaces any earlier unused nonce for that wallet. - Sign the exact
messageas UTF-8 bytes with the wallet’s Ed25519 key. Don’t reformat it: the server rebuilds the same text and checks the signature against it. - Verify.
POST /v1/auth/verifywith the address, nonce and base64 signature. The response sets apod_sessioncookie (HttpOnly, 30 days) and returns the account. - Send the cookie on every authenticated request:
Cookie: pod_session=<id>. There is no bearer token.
{"addr":"6EL3azXJs4As1vjjVukNXsQere58GsmRJJLuVm4jQPkQ"}{
"nonce": "a1fec7c25c4d4ab19fe6dd7fec26392f",
"message": "www.usepod.fun wants you to sign in with your Solana account:\n6EL3az…jQPkQ\n\nSign in to pod. This request will not trigger a blockchain transaction or cost any fees.\n\nURI: https://www.usepod.fun\nVersion: 1\nChain ID: devnet\nNonce: a1fec7c25c4d4ab19fe6dd7fec26392f\nIssued At: 2026-10-04T01:06:24.829531Z\nExpiration Time: 2026-10-04T01:11:24.829531Z",
"expiresAt": "2026-10-04T01:11:24.829531Z"
}import { Keypair } from "@solana/web3.js";
import nacl from "tweetnacl";
const API = "https://api.usepod.fun";
const wallet = Keypair.fromSecretKey(/* your devnet key */);
const addr = wallet.publicKey.toBase58();
const { nonce, message } = await fetch(`${API}/v1/auth/nonce`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ addr }),
}).then((r) => r.json());
const signature = Buffer.from(
nacl.sign.detached(new TextEncoder().encode(message), wallet.secretKey),
).toString("base64");
const res = await fetch(`${API}/v1/auth/verify`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ addr, nonce, signature }),
});
if (!res.ok) throw new Error((await res.json()).error.message);
// "pod_session=<uuid>; Path=/; HttpOnly; ..." → keep the first part.
const cookie = res.headers.get("set-cookie")!.split(";")[0];
const me = await fetch(`${API}/v1/me`, { headers: { cookie } }).then((r) => r.json());Signing in with a wallet that has no pod account creates a basic one. POST /v1/auth/logout ends the session. A nonce_invalid error means the nonce expired, was already used, or was replaced by a newer one; ask for a new one.
Keep keys used by scripts in a dedicated devnet wallet. Never paste a seed phrase or private key into a website, including anything that says it is pod.
Market data
Public, no session needed.
Pairs
| Endpoint | What it returns |
|---|---|
| GET/v1/pairs | Paged list of pairs. Query: sort = volume (default, 24h) · progress · new · holders; stage = Just Launched · Mid Curve · Graduating Soon · Graduated; quote (an asset id, or basket:<id>); category; limit (default 40, max 100); cursor. |
| GET/v1/pairs/new | The most recent launches. |
| GET/v1/pairs/pending | Launches whose terms are published but whose first buy hasn’t happened yet. |
| GET/v1/pairs/{pool} | One pair: price, market cap, progress to graduation, fees, tax, creator and socials. |
| GET/v1/pairs/{pool}/trades | Paged recent fills, newest first. Query: limit (default 50, max 200), trader (a wallet, to see only its fills). |
| GET/v1/pairs/{pool}/candles | OHLCV in USD. Query: tf = 1s · 5s · 15s · 1m · 5m · 15m · 1h · 4h · 1D; limit (default 500, max 1500). |
| GET/v1/pairs/{pool}/holders | Top holders with their share of supply in percent. |
| GET/v1/pairs/{pool}/endorsements | Endorsements posted by holders. |
| GET/v1/pairs/{pool}/distributions | Holder-reward distribution rounds for the pair. |
| GET/v1/curve/pairs/{mint} | The pair’s on-chain curve terms: program, pair and quote addresses, status, decimals, tax, supply and reserves as base-unit strings. Read this before building a trade. |
| GET/v1/trending | The trending ranking, the same rows the trending live topic sends. |
{
"items": [{
"id": "G36Ke631LjmKoUoXPmCH7aq3iip5vZSSrJfnrah7Np19",
"pool": "BobD2AUkQJvfLszFxZN4rC2vdjRMPjhJHEoiKu9utoLX",
"mint": "G36Ke631LjmKoUoXPmCH7aq3iip5vZSSrJfnrah7Np19",
"symbol": "FIXGRAD",
"quote": "basket:devnet-vault-smoke-v1",
"quoteSymbol": "DVSMOKE",
"progress": 100.0,
"priceUsd": 0.000190610007340192,
"marketCap": 190610.01,
"volume24h": 20050.75,
"change24h": 5431.28,
"holders": 2,
"feeBps": 69.6,
"taxBps": 0,
"stage": "Graduated",
"createdAt": "2026-09-26T21:15:27Z"
}],
"nextCursor": null
}[{"t":1790456400000,"o":3.35e-6,"h":0.0001906,"l":3.35e-6,"c":0.0001906,"v":20050.0,"buyV":20050.0,"n":3}]In a candle, t is the bucket start in milliseconds, prices are USD, v is USD volume, buyV the buy share of it and n the number of trades.
Assets and prices
| Endpoint | What it returns |
|---|---|
| GET/v1/assets | Quote assets pod lists (tokenized stocks, crypto, currencies and more). Query: category, issuer, q (matches symbol or name). |
| GET/v1/assets/{id} | One asset, with mint, logo, price, liquidity and whether its price is stale. |
| GET/v1/prices | Current USD price of every listed asset, with source and update time. Cached for 1 second. |
Baskets
| Endpoint | What it returns |
|---|---|
| GET/v1/baskets | Discoverable baskets. Query: origin = curated · community; category. |
| GET/v1/baskets/{id} | One basket: legs and target weights, NAV, share mint, creator. |
| GET/v1/baskets/{id}/vault | The vault’s on-chain state: custody balances and share supply. |
| GET/v1/baskets/{id}/reserves | Live reserves per leg, valued in USD. |
| GET/v1/baskets/{id}/history | The vault’s deposit and redemption history. |
| GET/v1/baskets/resolve | Find a basket from ref: its share mint, ticker or id. |
| GET/v1/baskets/drift | Baskets whose holdings have drifted from their target weights. all=true includes those on target. |
Pools
| Endpoint | What it returns |
|---|---|
| GET/v1/pools | Paged liquidity pools. Query: kind = migrated · standard · strata · stable · custom; sort = tvl · volume · apy · new; limit (default 50, max 200). |
| GET/v1/pools/{id} | One pool: sides, TVL, volume, fee settings and lock status. |
| GET/v1/manual-pools/{pool} | On-chain state of a hand-opened Deep pool. Also /trades, /candles, /positions, /fees and /lp under the same path. |
| GET/v1/stats | Platform totals: pools, TVL, 24h volume and fees, LPs, pairs, graduated pairs. |
Other
| Endpoint | What it returns |
|---|---|
| GET/v1/referrals/{code} | Who a referral code belongs to (username and avatar), for an invite preview. |
| GET/v1/healthz | Liveness, with the running build’s commit. |
Live WebSocket
For anything that changes every few seconds, subscribe instead of polling:
wss://api.usepod.fun/v1/live?topics=trending,prices,pair:<pool>On connect you get a snapshot frame with the current trending ranking, then frames for the topics you subscribed to. Each frame is a JSON object with a kind.
| Topic | Frames |
|---|---|
trending | {"kind":"trending","pairs":[...]} when the ranking changes. Rows have pool, rank, prevRank, rankDelta, score and driver. |
prices | {"kind":"prices","prices":[...],"ts":…} as asset prices update. |
pair:<pool> | {"kind":"trade","trade":{…}} for every finalized fill on that pair, with side, amount, usd, priceUsd, trader, signature and ts. |
pair:* | Trades on every pair. |
Change topics without reconnecting by sending:
{"op":"sub","topics":["pair:BobD2AUkQJvfLszFxZN4rC2vdjRMPjhJHEoiKu9utoLX"]}
{"op":"unsub","topics":["prices"]}
{"op":"ping"}- Up to 64 topics per connection, and up to 5 connections per IP.
- The server pings every 30 seconds and closes a connection that misses two pongs. Standard WebSocket clients answer automatically.
- A client that falls behind gets a fresh
snapshotrather than being disconnected. Treat a snapshot as “replace what you have”. - Trade frames are sent once the transaction is finalized and indexed, a few seconds after it lands.
Your account
These need a wallet session and are never cached.
| Endpoint | What it returns |
|---|---|
| GET/v1/me | The signed-in account: address, username, avatar, bio, wallet type, referral code, linked X profile.Session |
| PATCH/v1/me/profile | Update avatarUrl and/or bio (up to 160 characters). An omitted field is left alone; null clears it.Session |
| GET/v1/me/portfolio | Holdings across tokens, basket shares and LP, valued in USD.Session |
| GET/v1/me/positions | Open token positions with cost basis and P&L.Session |
| GET/v1/me/pnl | Realized and unrealized P&L over time.Session |
| GET/v1/me/baskets | Basket share holdings.Session |
| GET/v1/me/lp | Liquidity positions.Session |
| GET/v1/me/manual-lp | Positions in hand-opened Deep pools.Session |
| GET/v1/me/creator | For creators: your launches and their fee streams.Session |
| GET/v1/me/baskets/launched | Baskets you created.Session |
| POST/v1/me/claims/preview | What “claim all” would pay out right now, line by line.Session |
| GET/v1/me/distributions | Holder-reward distributions you can claim.Session |
| GET/v1/me/referrals | Your referral link, the sign-ups it brought, and rewards earned and claimable.Session |
| GET/v1/me/trade-settings | Your saved trade settings: slippage, MEV protection, priority fee and Jito tip. PUT the same path to change them.Session |
| POST/v1/auth/logout | Ends the session.Session |
Building a trade
Every write follows the same pattern: the API builds an unsigned transaction for your session’s wallet, you sign it, and you send it to Solana yourself. Here is a curve buy from start to finish.
1. Read the pair’s terms
GET /v1/curve/pairs/{mint} gives the decimals and status: pending (the first buy creates the market), trading (on the curve), or graduated / deep. Graduated pairs use the same buy and sell endpoints, which build the trade against the pair’s Deep pool instead.
2. Ask for the transaction
{"amount":"5000000","minOut":"24000000000"}- Buy:
amountis the most quote to spend andminOutthe fewest tokens you’ll accept, both in base units. - Sell (
/sell):amountis the tokens to sell andminOutthe least net quote you’ll accept. minOutis your slippage protection, enforced on-chain."0"means no protection: don’t use it outside tests.
{
"transaction": "AQAAAA…", // base64 unsigned VersionedTransaction
"recentBlockhash": "9xQe…",
"lastValidBlockHeight": 412345678,
"kind": "buy",
"pair": "BobD2AUk…",
"mint": "G36Ke631…",
"quoteAmount": "5000000", // quote charged
"tokenAmount": "24917355371900", // tokens received
"fee": "50000",
"tax": "0",
"attestationExpiresAt": "2026-10-04T01:07:10Z",
…
}The amounts are the quote at build time, so show them to your user. The transaction includes a short-lived price attestation, so it fails on-chain after attestationExpiresAt (30 seconds after it was built) or once the blockhash expires. Sign and send promptly; if it expires, ask for a new one.
3. Sign and send
import { Connection, VersionedTransaction } from "@solana/web3.js";
const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const built = await fetch(`${API}/v1/tx/pairs/${mint}/buy`, {
method: "POST",
headers: { "content-type": "application/json", cookie },
body: JSON.stringify({ amount: "5000000", minOut: "24000000000" }),
}).then((r) => r.json());
const tx = VersionedTransaction.deserialize(Buffer.from(built.transaction, "base64"));
tx.sign([wallet]); // the session's wallet is the fee payer and signer
const signature = await connection.sendRawTransaction(tx.serialize());
await connection.confirmTransaction(
{ signature, blockhash: built.recentBlockhash, lastValidBlockHeight: built.lastValidBlockHeight },
"confirmed",
);Check the decoded transaction before signing if you didn’t build the request yourself: its fee payer must be your wallet. Once finalized, the fill appears in /v1/pairs/{pool}/trades and on the pair:<pool> live topic.
POST /v1/tx/submit with {"transaction": "<signed base64>"} relays a signed transaction through Jito where the deployment supports it, and answers 503 jito_unavailable where it doesn’t. Sending through your own RPC always works.
All transaction builders
All need a session, take JSON, and return an unsigned base64 transaction plus a preview of what it will do. Amounts are base-unit strings.
Curve pairs
| Endpoint | What it returns |
|---|---|
| POST/v1/tx/pairs/{mint}/buy | Buy on the curve. {amount, minOut}.Session |
| POST/v1/tx/pairs/{mint}/sell | Sell on the curve. {amount, minOut}.Session |
| POST/v1/tx/pairs/create | Launch a token from a prepared draft, with the first buy in the same transaction. The mint keypair must also sign.Session |
| POST/v1/tx/pairs/atomic-basket | Launch a token on a new community basket in one signature.Session |
| POST/v1/tx/pairs/{mint}/claim | Creator: claim accrued creator fees.Session |
| POST/v1/tx/pairs/{mint}/route-fees | Creator: send accrued fees down the pair’s chosen route (buyback, holder distribution and so on).Session |
Baskets
| Endpoint | What it returns |
|---|---|
| POST/v1/tx/baskets/{id}/deposit | Deposit every leg in proportion and mint basket shares.Session |
| POST/v1/tx/baskets/{id}/redeem | Burn shares for a proportional cut of every leg.Session |
| GET/v1/tx/baskets/{id}/state | Your balances for each leg and the share, as needed for the two calls above.Session |
| POST/v1/tx/baskets/community/register | Propose a community basket. Then /v1/tx/baskets/{id}/initialize, /confirm and /publish take it live.Session |
Deep pools
| Endpoint | What it returns |
|---|---|
| POST/v1/tx/pool/create | Open a pool.Session |
| POST/v1/tx/pool/{pool}/deposit | Add liquidity. /withdraw removes it.Session |
| POST/v1/tx/pool/{pool}/swap | Swap in a pool.Session |
| POST/v1/tx/pool/{pool}/claim | Claim your share of pool fees.Session |
| POST/v1/tx/pool/{pool}/lp-lock | Lock LP tokens. /lp-burn burns them and /lp-release releases a lock that has ended.Session |
Rewards
| Endpoint | What it returns |
|---|---|
| POST/v1/tx/distributions/{distribution}/claim | Claim a holder-reward payout. {index} from /v1/me/distributions.Session |
| POST/v1/tx/referrals/{distribution}/claim | Claim referral rewards, the same way.Session |
Endorsements
| Endpoint | What it returns |
|---|---|
| POST/v1/endorsements | Post an endorsement on a pair you hold.Session |
| PUT/v1/endorsements/{id}/boost | Boost an endorsement. DELETE the same path to remove your boost.Session |
Support and changes
The API follows pod’s preview: routes can change while pod is on devnet, and anything mainnet will be announced first. Breaking changes will be noted here.
For questions, or to report a bug or a security issue, contact @LaunchOnPod on X. Please report vulnerabilities privately rather than in public replies.
