Wallets, portfolio & PnL
List and create wallets, read balances, holdings, projects and profit and loss.
Wallets#
| Method & path | Scope | Does |
|---|---|---|
GET /wallets | read | Every wallet in your account (paged) |
GET /wallets/balances | read | Coin balances (paged) |
GET /wallets/token-balances?mint=… | read | One token's balance in each wallet |
POST /wallets | trade | Create or import wallets |
PATCH /wallets/:id | trade | Rename a wallet, change its role or move it into a group |
DELETE /wallets/:id | trade | Delete a wallet |
GET /wallets/:id/profile | read | One wallet's trading history in numbers |
GET /wallets/groups · POST /wallets/groups | read / trade | Your wallet groups (folders) |
PATCH /wallets/groups/:id · DELETE /wallets/groups/:id | trade | Rename or delete a group |
GET /wallets/cashback · POST /wallets/cashback/claim | read / trade | pump.fun cashback per wallet, and claim it |
GET /wallets lists your global wallets by default. ?scope=all adds the wallets that live inside a project, ?groupId= narrows to one group and ?chain= to one chain.
GET /wallets:
{
"wallets": [
{
"id": "812",
"chain": "solana",
"evmChainId": null,
"address": "7tXi4mbvppPBgYgTVj2NpYq9K1KYYQvHv6tfCpJv9kMT",
"role": "dev",
"label": "funded-test-wallet",
"groupId": null,
"projectId": null,
"createdAt": "2026-08-29T10:03:44.318Z"
}
]
}GET /wallets/balances — coin balance per wallet, in the coin's smallest unit (lamports on Solana):
{
"balances": [
{
"id": "812",
"address": "7tXi4mbv…9kMT",
"chain": "solana",
"role": "dev",
"label": "funded-test-wallet",
"lamports": "419434195",
"block": 447482145,
"seeded": true
}
]
}GET /wallets/token-balances?mint=… — one token's balance in every wallet, as a map of wallet id to base units (mint is required):
{
"mint": "7xKX…pump",
"balances": { "812": "3512000000", "813": "0" }
}Create or import wallets#
POST /wallets
{ "chain": "solana", "count": 5, "label": "bot fleet" }countis 1–100 new wallets.- Send
privateKeyinstead to import a wallet you hold, orprivateKeys(a list, up to 100) to import many in one call:
{ "chain": "solana", "privateKeys": ["5Kd3…", "3xQe…", "4hTm…"], "label": "imported fleet" }- Importing is all-or-nothing: one bad or repeated key is a
400naming its position (privateKeys[2]) and nothing is written. - A key you already hold comes back as the wallet it is, unchanged (same id, role and label), so a list can be sent again after a timeout.
- Wallets are global: every wallet lives in your account's pool and any project can use it. Add
projectIdto create or import straight into a project — the wallets show under that project and stay reusable elsewhere. A project takes up to 100 wallets; an id that is not one of your projects is a404.
The response is 201 with the new wallets:
{ "wallets": [ { "id": "9001", "chain": "solana", "address": "…", "role": "trading", "label": "bot fleet" } ] }New wallets appear on the Wallets page straight away.
The API never returns private keys. Export them from the app, with your password (Key export).
A key limited to allowed wallets can still list every wallet, but can only trade from, rename or delete the wallets on its list.
Rename a wallet or change its role#
PATCH /wallets/:id
{ "label": "sniper 1" }labelis up to 120 characters; sendnullto clear it.roleisdev,tradingorfunding(the roles the app's wallet page offers).groupIdmoves the wallet into one of your groups;nulltakes it out of its group.- Send any of the three; an empty body or an unknown field is a
400. - The answer is
{ "wallet": { … } }, the wallet after the change, in the same shape asGET /wallets.
Delete a wallet#
DELETE /wallets/:id
Deleting moves no funds and cannot lose a key you still hold: the wallet leaves your lists and stops being usable in the app and the API, and importing the same private key again brings it back as it was. The answer is { "deleted": "812" }.
Two refusals, both 409 with code: "conflict":
- A running or paused task uses the wallet. Stop the task first (the error names it).
- One of your projects still uses the wallet. The error lists them in
projects. Deleting anyway empties the wallet's seat in each project; do that withDELETE /wallets/:id?force=true.
A wallet that is not yours, or does not exist, is a 404.
Wallet groups#
Groups are the folders of the Wallets page. A group only organises: it moves no funds and a wallet can be in one group at a time.
curl -X POST https://evm.vortexdeployer.com/evm/api/v1/wallets/groups \
-H "Authorization: Bearer vd_…" -H "Content-Type: application/json" \
-d '{ "name": "snipers" }'{ "group": { "id": "728", "name": "snipers", "walletCount": "0", "createdAt": "2026-09-16T23:12:35.947Z" } }GET /wallets/groupsreturns{ "groups": [ … ] }with awalletCountper group.PATCH /wallets/groups/:idtakes{ "name": "…" }(1–64 characters). A name you already use is409withcode: "conflict", on create and on rename.DELETE /wallets/groups/:idanswers{ "deleted": "728" }. Its wallets stay in your account, ungrouped.- Put a wallet in a group with
PATCH /wallets/:id { "groupId": "728" }; list a group's wallets withGET /wallets?groupId=728.
Wallet profile#
GET /wallets/:id/profile is the wallet's history in numbers, from your ledger:
{
"profile": {
"walletId": "812", "chain": "solana", "address": "7tXi4mbv…9kMT", "role": "dev", "label": "funded-test-wallet",
"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#
pump.fun pays cashback on volume to each trading wallet. GET /wallets/cashback (paged like GET /wallets) lists what each wallet has earned, claimed and can claim now, in lamports, with needsWsolAccount when the claim must first open the wallet's wrapped-SOL account.
POST /wallets/cashback/claim claims for up to 200 wallets:
{ "walletIds": ["812", "813"], "dryRun": true }dryRun defaults to true; send false to claim. The answer is { "dryRun": …, "outcomes": [ { "walletId", "claimable", "ok", "signature", "err" } ] }. A sandbox key can only rehearse; a wallet-list key can only claim for wallets on its list.
Paging long lists#
GET /wallets, GET /wallets/balances and GET /portfolio can run to hundreds of rows. They take limit (1–500, default 200) and offset (default 0), and every answer carries the full size in X-Total-Count:
curl "https://evm.vortexdeployer.com/evm/api/v1/wallets?limit=100&offset=200" \
-H "Authorization: Bearer vd_…" -iKeep adding limit to offset until offset reaches X-Total-Count. A limit outside 1–500 is 400 with code: "validation_failed".
Portfolio & projects#
| Method & path | Does |
|---|---|
GET /portfolio | Coin totals across your wallets (balances paged) |
GET /portfolio/holdings | Tokens you hold, grouped by token |
GET /portfolio/pnl | PnL per token |
GET /portfolio/daily | Day-by-day results |
GET /projects | Tokens you launched, newest first, with their live stage and market cap |
GET /projects/:mint · GET /projects/by-id/:id | One project |
POST /projects · POST /projects/cto | Create a draft project, or take over a token that already exists on chain |
PATCH /projects/:id · DELETE /projects/:id | Edit or delete a project |
GET /projects/:id/wallets · POST /projects/:id/wallets/assign · DELETE /projects/:id/wallets/:walletId | The wallets in a project |
POST /projects/:id/closeout | Sell every project wallet's bag and sweep the coin to one wallet |
POST /pnl/wallet-totals | One token's cash-flow totals across a list of wallet addresses |
Reads need read; every write needs trade.
GET /portfolio:
{
"chain": "solana",
"sol": {
"totalLamports": "31497186480",
"walletCount": 716,
"balances": [ { "id": "812", "address": "…", "lamports": "419434195", "…": "…" } ]
}
}GET /projects returns { "projects": [ … ], "launchedTotal": 12, "nextCursor": null }. Each project has its id, chain, mint, name, symbol, launchpad, devWalletId, dryRun, launchedAt, createdAt, the metadataUri the launch pinned, and on the list market (stage and market cap). Pass nextCursor back as ?cursor= for the next page. GET /projects/:mint returns { "project": { … } }.
Create, edit and delete a project#
POST /projects creates a draft: a project without a token yet, the same as New project in the app. Set it up, put wallets in it, then launch it with POST /launch or from the app.
{ "chain": "solana", "name": "Vortex Cat", "symbol": "VCAT", "launchpad": "pump", "devWalletId": "812" }Every field is optional (chain defaults to solana). The answer is 201 { "project": { … } } in the same shape as GET /projects/by-id/:id.
POST /projects/cto takes over a token that is already on chain (a community takeover): { "chain": "solana", "mint": "…" } makes it a project of yours so tasks and the terminal can work on it. A mint nobody has indexed is a 404.
PATCH /projects/:id changes name, symbol, launchpad or devWalletId. Once a project is launched its name, symbol and launchpad are frozen: changing them is 409 with code: "conflict". DELETE /projects/:id answers { "deleted": "1021" }; the project leaves your lists and its history stays.
A project's wallets#
GET /projects/:id/wallets lists them (paged), each with an origin: project for a wallet created inside the project, assigned for one of your global wallets linked to it.
POST /projects/:id/wallets/assign links global wallets: { "walletIds": ["813", "814"] } (up to 500) answers { "assigned": [ … ], "skipped": [ … ] }, skipped being the ones already in the project. Or create wallets straight into a project with POST /wallets and projectId.
DELETE /projects/:id/wallets/:walletId takes a wallet out: { "walletId": "813", "outcome": "unassigned" } for a linked global wallet (it still exists) or "deleted" for a project-only wallet.
Close out a project#
POST /projects/:id/closeout is the app's Close out: it sells the token in every project wallet and starts a Collector task that sweeps the coin to one wallet.
{ "destinationWalletId": "812", "dryRun": true }dryRun defaults to true and rehearses the sells without sending; the sweep is recorded as a dry-run Collector task, so you can inspect it under GET /tasks. Send "dryRun": false to close out for real. The answer:
{
"projectId": "1021", "mint": "J11L…pump", "chain": "solana", "symbol": "APIQA", "dryRun": true,
"sells": [ { "walletId": "813", "address": "…", "ok": true, "signature": null, "err": null } ],
"closed": [], "sweep": { "taskId": "6868", "sources": ["813"] }, "skipped": []
}A key with limits (caps, a wallet list or sandbox) can only rehearse: dryRun: false is 403 with code: "key_restricted". A wallet-list key must have the destination on its list.
POST /pnl/wallet-totals sums one token's flows across wallet addresses (up to 1000): { "mint": "…", "wallets": ["7tXi…"], "quoteMint": "So111…112" } answers the spent, received and held totals the token page shows for that selection.
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" } ]
}
]
}GET /portfolio/daily:
{
"chain": "solana",
"days": [
{
"day": "2026-09-08",
"buyCount": 81,
"sellCount": 59,
"solSpentLamports": "1048881293",
"solReceivedLamports": "552401905",
"netLamports": "-496479388",
"feesLamports": "254446",
"exactCount": 2
}
]
}PnL#
| Call | Returns |
|---|---|
GET /pnl | Every token's position PnL, as on the Dashboard (same as GET /portfolio/pnl) |
GET /pnl?mint=… | One token's realized and unrealized PnL in USD, the figure on its share card |
GET /pnl:
{
"chain": "solana",
"tokens": [
{
"mint": "7xKX…pump",
"symbol": "VCAT",
"name": "Vortex Cat",
"buyCount": 23,
"sellCount": 0,
"solSpentLamports": "2294703578",
"solReceivedLamports": "0",
"realizedSolPnlLamports": "-2294703578",
"feesLamports": "225000",
"exactCount": 23,
"held": true,
"currentValueLamports": "2276357388",
"totalPnlLamports": "-18346190"
}
]
}GET /pnl?mint=…:
{
"mint": "7xKX…pump",
"pnl": {
"profitUsd": 42.17,
"profitQuote": 0.271,
"quoteUsd": 155.6,
"wallets": 3,
"trades": 12,
"held": 3512000000,
"spotQuote": 0.0000004
}
}When a token can't be priced, pnl is null and reason says why:
{ "mint": "7xKX…pump", "pnl": null, "reason": "Live holdings are not available yet" }Want it live? Subscribe to pnl or pnl:<mint> on the live stream.