podDocs
Developers

API guide

Base URL https://api.usepod.fun/v1 · Solana devnet

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 camelCase field names. Send content-type: application/json on 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
curl https://api.usepod.fun/v1/stats
response
{"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
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:

response · 400
{"error":{"code":"unknown_timeframe","message":"2h is not one of 1s, 5s, 15s, 1m, 5m, 15m, 1h, 4h, 1D"}}
StatusCommon codes
400invalid_request (body or query couldn’t be read), plus route-specific codes such as unknown_timeframe, nonce_invalid or invalid_address.
401unauthorized: no valid session. Sign in again.
403Route-specific: the session is valid but can’t do this.
404not_found or a more specific code.
409Conflicts, for example a resource that already exists.
429rate_limited or too_many_live_connections. Wait for Retry-After seconds.
502 / 503An upstream (Solana RPC, price source) failed, or a feature is off on this deployment, for example curve_unavailable.
500internal: 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.

  1. Get a challenge. POST /v1/auth/nonce with the wallet address. The nonce is single-use, valid for 5 minutes, and replaces any earlier unused nonce for that wallet.
  2. Sign the exact message as 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.
  3. Verify. POST /v1/auth/verify with the address, nonce and base64 signature. The response sets a pod_session cookie (HttpOnly, 30 days) and returns the account.
  4. Send the cookie on every authenticated request: Cookie: pod_session=<id>. There is no bearer token.
POST /v1/auth/nonce
{"addr":"6EL3azXJs4As1vjjVukNXsQere58GsmRJJLuVm4jQPkQ"}
response
{
  "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"
}
TypeScript · sign in from a script
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

EndpointWhat it returns
GET/v1/pairsPaged 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/newThe most recent launches.
GET/v1/pairs/pendingLaunches 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}/tradesPaged recent fills, newest first. Query: limit (default 50, max 200), trader (a wallet, to see only its fills).
GET/v1/pairs/{pool}/candlesOHLCV in USD. Query: tf = 1s · 5s · 15s · 1m · 5m · 15m · 1h · 4h · 1D; limit (default 500, max 1500).
GET/v1/pairs/{pool}/holdersTop holders with their share of supply in percent.
GET/v1/pairs/{pool}/endorsementsEndorsements posted by holders.
GET/v1/pairs/{pool}/distributionsHolder-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/trendingThe trending ranking, the same rows the trending live topic sends.
GET /v1/pairs?limit=1 (trimmed)
{
  "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
}
GET /v1/pairs/{pool}/candles?tf=1h
[{"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

EndpointWhat it returns
GET/v1/assetsQuote 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/pricesCurrent USD price of every listed asset, with source and update time. Cached for 1 second.

Baskets

EndpointWhat it returns
GET/v1/basketsDiscoverable baskets. Query: origin = curated · community; category.
GET/v1/baskets/{id}One basket: legs and target weights, NAV, share mint, creator.
GET/v1/baskets/{id}/vaultThe vault’s on-chain state: custody balances and share supply.
GET/v1/baskets/{id}/reservesLive reserves per leg, valued in USD.
GET/v1/baskets/{id}/historyThe vault’s deposit and redemption history.
GET/v1/baskets/resolveFind a basket from ref: its share mint, ticker or id.
GET/v1/baskets/driftBaskets whose holdings have drifted from their target weights. all=true includes those on target.

Pools

EndpointWhat it returns
GET/v1/poolsPaged 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/statsPlatform totals: pools, TVL, 24h volume and fees, LPs, pairs, graduated pairs.

Other

EndpointWhat it returns
GET/v1/referrals/{code}Who a referral code belongs to (username and avatar), for an invite preview.
GET/v1/healthzLiveness, with the running build’s commit.

Live WebSocket

For anything that changes every few seconds, subscribe instead of polling:

connect
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.

TopicFrames
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:

client → server
{"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 snapshot rather 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.

EndpointWhat it returns
GET/v1/meThe signed-in account: address, username, avatar, bio, wallet type, referral code, linked X profile.Session
PATCH/v1/me/profileUpdate avatarUrl and/or bio (up to 160 characters). An omitted field is left alone; null clears it.Session
GET/v1/me/portfolioHoldings across tokens, basket shares and LP, valued in USD.Session
GET/v1/me/positionsOpen token positions with cost basis and P&L.Session
GET/v1/me/pnlRealized and unrealized P&L over time.Session
GET/v1/me/basketsBasket share holdings.Session
GET/v1/me/lpLiquidity positions.Session
GET/v1/me/manual-lpPositions in hand-opened Deep pools.Session
GET/v1/me/creatorFor creators: your launches and their fee streams.Session
GET/v1/me/baskets/launchedBaskets you created.Session
POST/v1/me/claims/previewWhat “claim all” would pay out right now, line by line.Session
GET/v1/me/distributionsHolder-reward distributions you can claim.Session
GET/v1/me/referralsYour referral link, the sign-ups it brought, and rewards earned and claimable.Session
GET/v1/me/trade-settingsYour saved trade settings: slippage, MEV protection, priority fee and Jito tip. PUT the same path to change them.Session
POST/v1/auth/logoutEnds 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

POST /v1/tx/pairs/{mint}/buy · Cookie: pod_session=…
{"amount":"5000000","minOut":"24000000000"}
  • Buy: amount is the most quote to spend and minOut the fewest tokens you’ll accept, both in base units.
  • Sell (/sell): amount is the tokens to sell and minOut the least net quote you’ll accept.
  • minOut is your slippage protection, enforced on-chain. "0" means no protection: don’t use it outside tests.
response (illustrative values, trimmed)
{
  "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

TypeScript
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

EndpointWhat it returns
POST/v1/tx/pairs/{mint}/buyBuy on the curve. {amount, minOut}.Session
POST/v1/tx/pairs/{mint}/sellSell on the curve. {amount, minOut}.Session
POST/v1/tx/pairs/createLaunch 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-basketLaunch a token on a new community basket in one signature.Session
POST/v1/tx/pairs/{mint}/claimCreator: claim accrued creator fees.Session
POST/v1/tx/pairs/{mint}/route-feesCreator: send accrued fees down the pair’s chosen route (buyback, holder distribution and so on).Session

Baskets

EndpointWhat it returns
POST/v1/tx/baskets/{id}/depositDeposit every leg in proportion and mint basket shares.Session
POST/v1/tx/baskets/{id}/redeemBurn shares for a proportional cut of every leg.Session
GET/v1/tx/baskets/{id}/stateYour balances for each leg and the share, as needed for the two calls above.Session
POST/v1/tx/baskets/community/registerPropose a community basket. Then /v1/tx/baskets/{id}/initialize, /confirm and /publish take it live.Session

Deep pools

EndpointWhat it returns
POST/v1/tx/pool/createOpen a pool.Session
POST/v1/tx/pool/{pool}/depositAdd liquidity. /withdraw removes it.Session
POST/v1/tx/pool/{pool}/swapSwap in a pool.Session
POST/v1/tx/pool/{pool}/claimClaim your share of pool fees.Session
POST/v1/tx/pool/{pool}/lp-lockLock LP tokens. /lp-burn burns them and /lp-release releases a lock that has ended.Session

Rewards

EndpointWhat it returns
POST/v1/tx/distributions/{distribution}/claimClaim a holder-reward payout. {index} from /v1/me/distributions.Session
POST/v1/tx/referrals/{distribution}/claimClaim referral rewards, the same way.Session

Endorsements

EndpointWhat it returns
POST/v1/endorsementsPost an endorsement on a pair you hold.Session
PUT/v1/endorsements/{id}/boostBoost 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.