Wallets, portfolio & PnL
Create, list and organise wallets, read balances and holdings, manage projects and read profit and loss from code.
These calls manage the wallets in your account and read what they hold and how they did. Reads need the read scope. Every change needs trade.
Wallet calls#
| Call | Scope | What it does |
|---|---|---|
GET /wallets | read | Your wallets. |
GET /wallets/balances | read | Each wallet's coin balance. |
GET /wallets/token-balances?mint=… | read | One token's balance in each wallet. |
POST /wallets | trade | Make new wallets or import your own. |
PATCH /wallets/:id | trade | Rename a wallet, change its role or move it to a group. |
DELETE /wallets/:id | trade | Delete one wallet. |
POST /wallets/delete | trade | Delete many wallets. |
POST /wallets/move | trade | Move many wallets to a group. |
GET /wallets/:id/profile | read | One wallet's trading history in numbers. |
GET /wallets/groups, POST /wallets/groups | read / trade | List or make groups. |
PATCH /wallets/groups/:id, DELETE /wallets/groups/:id | trade | Rename or delete a group. |
GET /wallets/cashback, POST /wallets/cashback/claim | read / trade | Read and claim pump.fun cashback. |
No call returns a wallet's private key. To export keys, use the app with your password. See Export a private key.
List wallets#
GET /wallets answers { "wallets": [ … ] }:
{
"wallets": [
{
"id": "812",
"chain": "solana",
"address": "7tXi4mbvppPBgYgTVj2NpYq9K1KYYQvHv6tfCpJv9kMT",
"role": "dev",
"label": "main dev",
"groupId": null,
"projectId": null,
"createdAt": "2026-08-29T10:03:44.318Z"
}
]
}- By default it lists the wallets in your shared pool.
?scope=alladds wallets made inside a project. ?groupId=lists one group.?chain=lists one chain.- Page with
?limit=(1–500, default 200) and?offset=. The full count is in theX-Total-Countheader.
A key limited to a wallet list still sees every wallet. It can only use, change or delete the ones on its list.
Read balances#
GET /wallets/balances gives each wallet's coin balance in base units. On Solana that is lamports: 1 SOL is 1,000,000,000 lamports.
{
"balances": [
{ "id": "812", "address": "7tXi…9kMT", "chain": "solana", "role": "dev", "label": "main dev",
"lamports": "419434195", "block": 447482145, "seeded": true }
],
"totalLamports": "31497186480",
"walletCount": 716
}seeded: false means the balance has not been read yet. It shows "0" until then. Add ?chain= for one chain. The list pages like GET /wallets.
GET /wallets/token-balances?mint=… gives one token's balance in each wallet, as a map from wallet id to base units. mint is required.
{ "mint": "7xKX…pump", "balances": { "812": "3512000000", "813": "0" } }Make or import wallets#
POST /wallets makes new wallets:
{ "chain": "solana", "count": 5, "label": "bot fleet" }chain— defaultsolana.count— how many, 1–1000. Default 1.label— up to 120 characters. With more than one wallet, each label gets a number:bot fleet 1,bot fleet 2. Put{n}in the label to place the number yourself.labelStart— the first number. Use it to go past 1000 over several calls. The second call of 2,500 wallets sends"count": 1000, "labelStart": 1001.projectId— put the wallets in one of your projects. An id that is not yours gets404.
To import wallets you already have, send privateKey (one) or privateKeys (up to 1000) instead of count:
{ "chain": "solana", "privateKeys": ["5Kd3…", "3xQe…"], "label": "imported" }- An import is all or nothing. One bad or repeated key gets
400naming its place, likeprivateKeys[2], and nothing is saved. - A wallet you already have comes back as it is. Its role, label and group do not change. So you can safely send the same list again after a timeout.
- A key that cannot be used on your account gets
409. - A key limited to a wallet list cannot import (
403 private_key_not_allowed).
The answer is 201 with { "wallets": [ … ] }. New wallets have the role trading.
Change a wallet#
PATCH /wallets/:id takes any of:
label— up to 120 characters.nullclears it.role— the wallet's role, likedevortrading. Lowercase letters, digits and_, up to 32 characters.groupId— move the wallet into a group.nulltakes it out.
The answer is { "wallet": { … } }. An empty body or an unknown field gets 400.
Delete wallets#
DELETE /wallets/:id removes a wallet from your lists. It moves no funds. If you import the same private key later, the wallet comes back. The answer is { "deleted": "812" }.
It gets 409 conflict when:
- a running or paused task uses the wallet. Stop the task first.
- a project uses the wallet. The error lists them in
projects. To delete it anyway, sendDELETE /wallets/:id?force=true. That also takes it out of those projects.
A wallet that is not yours gets 404.
POST /wallets/delete deletes many in one call:
{ "walletIds": ["812", "813", "814"], "force": false }Each refusal comes back per wallet, and the rest are deleted:
{
"deleted": ["812"],
"active": [ { "id": "813", "taskId": "4410" } ],
"assigned": [ { "id": "814", "projects": [ { "id": "1021", "name": "Moon", "symbol": "MOON" } ] } ],
"missing": []
}active— a task uses it. Stop the task first.assigned— a project uses it. Send these again with"force": trueto delete them anyway.missing— not yours, or already deleted.
Groups#
A group is a folder on the Wallets page. It only sorts wallets. It moves no funds. A wallet is in at most one group.
POST /wallets/groupswith{ "name": "snipers" }(1–64 characters) answers201 { "group": { "id": "728", "name": "snipers", "walletCount": "0", "createdAt": "…" } }. A name you already use gets409.GET /wallets/groupsanswers{ "groups": [ … ] }.PATCH /wallets/groups/:idrenames it. Same body.DELETE /wallets/groups/:idanswers{ "deleted": "728" }. Its wallets stay, without a group.POST /wallets/movewith{ "walletIds": ["812", "813"], "groupId": "728" }moves many wallets."groupId": nulltakes them out. It answers{ "moved": [ … ], "missing": [ … ] }. A group that is not yours gets403.
Wallet profile#
GET /wallets/:id/profile sums up one wallet's real trades:
{
"profile": {
"walletId": "812", "chain": "solana", "address": "7tXi…9kMT", "role": "dev", "label": "main dev",
"createdAt": "2026-08-29T10:03:44.318Z", "ageDays": 18, "lastActivityAt": "2026-09-15T20:11:02.000Z",
"tradeCount": 141, "buyCount": 80, "sellCount": 61, "distinctMints": 9, "holderPositions": 3,
"solSpentLamports": "1048881293", "solReceivedLamports": "552401905"
}
}Cashback#
Cashback is what pump.fun pays a wallet back for trading certain coins. It waits in a vault until the wallet claims it.
GET /wallets/cashback lists, per Solana wallet, the earned, claimed and claimable amounts in lamports. needsWsolAccount: true means the first claim also opens the wallet's wrapped SOL account. That costs about 0.002 SOL of rent, which stays in the wallet.
POST /wallets/cashback/claim claims:
{ "walletIds": ["812", "813"], "dryRun": true }dryRundefaults totrue. Sendfalseto claim for real.- The claimed SOL stays wrapped in the wallet's wrapped SOL account.
- The answer is
{ "dryRun": true, "outcomes": [ { "walletId", "address", "label", "claimable", "ok", "signature", "err" } ] }. - A sandbox key can only dry-run. A wallet-list key can only claim for its own wallets.
Portfolio#
| Call | What it gives |
|---|---|
GET /portfolio | Coin totals and per-wallet balances. |
GET /portfolio/holdings | The tokens your wallets hold, summed per token. |
GET /portfolio/pnl | PnL per token. The same as GET /pnl. |
GET /portfolio/daily | Trading results per UTC day. |
All take ?chain= (default solana).
GET /portfolio answers chain, sol (totalLamports, walletCount and the paged balances), trading, and totalValueLamports: coins plus tokens, valued in lamports. unpricedAssets counts what could not be valued.
GET /portfolio/holdings:
{
"chain": "solana",
"holdings": [
{ "mint": "7xKX…pump", "symbol": "VCAT", "name": "Vortex Cat", "decimals": 6,
"rawAmount": "51920524244101", "uiAmount": 51920524.244101, "walletCount": 23,
"wallets": [ { "id": "812", "address": "…", "label": "bot 1" } ] }
],
"totalValueLamports": "2276357388",
"valuation": "live"
}GET /portfolio/daily?days=30 gives one row per UTC day with trades: buyCount, sellCount, solSpentLamports, solReceivedLamports, netLamports and feesLamports. Days default to 90, up to 366.
PnL#
PnL is profit and loss: what you got back minus what you spent.
GET /pnl lists every token you traded on one chain, biggest movers first, up to 100:
{
"chain": "solana",
"tokens": [
{ "mint": "7xKX…pump", "symbol": "VCAT", "buyCount": 23, "sellCount": 0,
"solSpentLamports": "2294703578", "solReceivedLamports": "0",
"realizedSolPnlLamports": "-2294703578", "feesLamports": "225000",
"held": true, "currentValueLamports": "2276357388", "totalPnlLamports": "-18346190" }
]
}realizedSolPnlLamports— received minus spent, from trades that landed.currentValueLamports— what the tokens you still hold would sell for now.nullwhen no pool can price them.totalPnlLamports— realized plus current value.nullwhen either part is unknown.
GET /pnl?mint=… gives one token's PnL in USD, the figure on its share card:
{ "mint": "7xKX…pump", "pnl": { "profitUsd": 42.17, "profitQuote": 0.271, "quoteUsd": 155.6, "wallets": 3, "trades": 12, "held": 3512000000, "spotQuote": 0.0000004 } }When the token cannot be priced, pnl is null and reason says why.
POST /pnl/wallet-totals adds up one token's trades for a set of your Solana wallet addresses:
{ "mint": "7xKX…pump", "wallets": ["7tXi…9kMT"], "quoteMint": "So11111111111111111111111111111111111111112" }It answers boughtRaw, soldRaw, cashChangeRaw, buys and sells per wallet and in total. An address that is not yours gets 403.
For live PnL, subscribe to pnl on the live stream.
Projects#
A project holds one token: its details, its wallets and its tasks.
| Call | Scope | What it does |
|---|---|---|
GET /projects | read | Your projects, newest first. |
GET /projects/:mint, GET /projects/by-id/:id | read | One project. |
POST /projects | trade | Make a draft: a project with no token yet. |
POST /projects/cto | trade | Take over a token that already trades. |
PATCH /projects/:id | trade | Change its name, symbol, launchpad or dev wallet. |
DELETE /projects/:id | trade | Delete it. |
GET /projects/:id/wallets | read | Its wallets. |
POST /projects/:id/wallets/assign | trade | Add wallets from your pool. |
DELETE /projects/:id/wallets/:walletId, POST /projects/:id/wallets/remove | trade | Take wallets out. |
POST /projects/:id/closeout | trade | Sell the token from every project wallet and collect the coin. |
GET /projects answers { "projects": [ … ], "launchedTotal": 12, "nextCursor": null }. Each project has id, chain, mint, name, symbol, launchpad, devWalletId, dryRun, launchedAt, createdAt, metadataUri and market. Take up to 200 with ?limit= (default 100). For the next page, send nextCursor back as ?cursor=.
POST /projects makes a draft. Every field is optional:
{ "chain": "solana", "name": "Vortex Cat", "symbol": "VCAT", "launchpad": "pump", "devWalletId": "812" }Launch it later with POST /launch and its projectId. See Launch a token.
POST /projects/cto with { "chain": "solana", "mint": "…" } makes a project from a token that already trades, so tasks can work on it. See CTO an existing token. An unknown token gets 404.
PATCH /projects/:id takes name (up to 64), symbol (up to 16), launchpad or devWalletId. After launch, name, symbol and launchpad are fixed, and changing them gets 409. DELETE /projects/:id answers { "deleted": "1021" }. The project's wallets are not deleted: they move to your pool.
A project's wallets#
GET /projects/:id/walletslists them.originisprojectfor a wallet made inside the project, orassignedfor a wallet from your pool.POST /projects/:id/wallets/assignwith{ "walletIds": ["813", "814"] }adds wallets. It answers{ "assigned": [ … ], "skipped": [ … ] }.DELETE /projects/:id/wallets/:walletIdanswers{ "walletId": "813", "outcome": "unassigned" }. The wallet leaves the project and stays in your pool. A wallet made inside the project moves to your pool. No wallet is deleted.POST /projects/:id/wallets/removewith{ "walletIds": [ … ] }answers{ "deleted": [], "unassigned": [ … ], "missing": [ … ] }.deletedis always empty.
Close out a project#
POST /projects/:id/closeout sells the token from every project wallet. On Solana it also closes the emptied token accounts. Then it starts a task that sweeps the coin to one wallet.
{ "destinationWalletId": "812", "dryRun": true }dryRun defaults to true: nothing is sent, and the sweep task is made as a dry run. Send false to close out for real.
{
"projectId": "1021", "mint": "J11L…pump", "chain": "solana", "symbol": "VCAT", "dryRun": true,
"sells": [ { "walletId": "813", "address": "…", "amountRaw": "3512000000", "ok": true, "signature": null, "err": null } ],
"closed": [], "sweep": { "taskId": "6868", "sources": 1 }, "skipped": []
}skipped says, in words, what could not run. A restricted key can only dry-run (403 key_restricted).