DocsAPI

Wallets, portfolio & PnL

List and create wallets, read balances, holdings, projects and profit and loss.

Wallets#

Method & pathScopeDoes
GET /walletsreadEvery wallet in your account (paged)
GET /wallets/balancesreadCoin balances (paged)
GET /wallets/token-balances?mint=…readOne token's balance in each wallet
POST /walletstradeCreate or import wallets
PATCH /wallets/:idtradeRename a wallet, change its role or move it into a group
DELETE /wallets/:idtradeDelete a wallet
GET /wallets/:id/profilereadOne wallet's trading history in numbers
GET /wallets/groups · POST /wallets/groupsread / tradeYour wallet groups (folders)
PATCH /wallets/groups/:id · DELETE /wallets/groups/:idtradeRename or delete a group
GET /wallets/cashback · POST /wallets/cashback/claimread / tradepump.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" }
  • count is 1–100 new wallets.
  • Send privateKey instead to import a wallet you hold, or privateKeys (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 400 naming 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 projectId to 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 a 404.

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.

No key export

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" }
  • label is up to 120 characters; send null to clear it.
  • role is dev, trading or funding (the roles the app's wallet page offers).
  • groupId moves the wallet into one of your groups; null takes 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 as GET /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 with DELETE /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/groups returns { "groups": [ … ] } with a walletCount per group.
  • PATCH /wallets/groups/:id takes { "name": "…" } (1–64 characters). A name you already use is 409 with code: "conflict", on create and on rename.
  • DELETE /wallets/groups/:id answers { "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 with GET /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_…" -i

Keep adding limit to offset until offset reaches X-Total-Count. A limit outside 1–500 is 400 with code: "validation_failed".

Portfolio & projects#

Method & pathDoes
GET /portfolioCoin totals across your wallets (balances paged)
GET /portfolio/holdingsTokens you hold, grouped by token
GET /portfolio/pnlPnL per token
GET /portfolio/dailyDay-by-day results
GET /projectsTokens you launched, newest first, with their live stage and market cap
GET /projects/:mint · GET /projects/by-id/:idOne project
POST /projects · POST /projects/ctoCreate a draft project, or take over a token that already exists on chain
PATCH /projects/:id · DELETE /projects/:idEdit or delete a project
GET /projects/:id/wallets · POST /projects/:id/wallets/assign · DELETE /projects/:id/wallets/:walletIdThe wallets in a project
POST /projects/:id/closeoutSell every project wallet's bag and sweep the coin to one wallet
POST /pnl/wallet-totalsOne 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#

CallReturns
GET /pnlEvery 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.

See also#