DocsAPI

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#

CallScopeWhat it does
GET /walletsreadYour wallets.
GET /wallets/balancesreadEach wallet's coin balance.
GET /wallets/token-balances?mint=…readOne token's balance in each wallet.
POST /walletstradeMake new wallets or import your own.
PATCH /wallets/:idtradeRename a wallet, change its role or move it to a group.
DELETE /wallets/:idtradeDelete one wallet.
POST /wallets/deletetradeDelete many wallets.
POST /wallets/movetradeMove many wallets to a group.
GET /wallets/:id/profilereadOne wallet's trading history in numbers.
GET /wallets/groups, POST /wallets/groupsread / tradeList or make groups.
PATCH /wallets/groups/:id, DELETE /wallets/groups/:idtradeRename or delete a group.
GET /wallets/cashback, POST /wallets/cashback/claimread / tradeRead and claim pump.fun cashback.
The API never shows private keys

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=all adds 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 the X-Total-Count header.

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 — default solana.
  • 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 gets 404.

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 400 naming its place, like privateKeys[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. null clears it.
  • role — the wallet's role, like dev or trading. Lowercase letters, digits and _, up to 32 characters.
  • groupId — move the wallet into a group. null takes 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, send DELETE /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": true to 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/groups with { "name": "snipers" } (1–64 characters) answers 201 { "group": { "id": "728", "name": "snipers", "walletCount": "0", "createdAt": "…" } }. A name you already use gets 409.
  • GET /wallets/groups answers { "groups": [ … ] }.
  • PATCH /wallets/groups/:id renames it. Same body.
  • DELETE /wallets/groups/:id answers { "deleted": "728" }. Its wallets stay, without a group.
  • POST /wallets/move with { "walletIds": ["812", "813"], "groupId": "728" } moves many wallets. "groupId": null takes them out. It answers { "moved": [ … ], "missing": [ … ] }. A group that is not yours gets 403.

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 }
  • dryRun defaults to true. Send false to 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#

CallWhat it gives
GET /portfolioCoin totals and per-wallet balances.
GET /portfolio/holdingsThe tokens your wallets hold, summed per token.
GET /portfolio/pnlPnL per token. The same as GET /pnl.
GET /portfolio/dailyTrading 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. null when no pool can price them.
  • totalPnlLamports — realized plus current value. null when 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.

CallScopeWhat it does
GET /projectsreadYour projects, newest first.
GET /projects/:mint, GET /projects/by-id/:idreadOne project.
POST /projectstradeMake a draft: a project with no token yet.
POST /projects/ctotradeTake over a token that already trades.
PATCH /projects/:idtradeChange its name, symbol, launchpad or dev wallet.
DELETE /projects/:idtradeDelete it.
GET /projects/:id/walletsreadIts wallets.
POST /projects/:id/wallets/assigntradeAdd wallets from your pool.
DELETE /projects/:id/wallets/:walletId, POST /projects/:id/wallets/removetradeTake wallets out.
POST /projects/:id/closeouttradeSell 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/wallets lists them. origin is project for a wallet made inside the project, or assigned for a wallet from your pool.
  • POST /projects/:id/wallets/assign with { "walletIds": ["813", "814"] } adds wallets. It answers { "assigned": [ … ], "skipped": [ … ] }.
  • DELETE /projects/:id/wallets/:walletId answers { "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/remove with { "walletIds": [ … ] } answers { "deleted": [], "unassigned": [ … ], "missing": [ … ] }. deleted is 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).

See also#