What This API Manages
SEND generates and custodies Solana keypairs for you. There are exactly two kinds of wallet. A wallet set is a named group, and it is the unit every multi-wallet strategy operates on -- trading, aging, volume. A dev wallet stands alone, outside any set, and acts as the deployer wallet for launch and snipe projects.
Every route lives under /api/v1 and carries an Authorization: Bearer header. Successful responses are wrapped as { success, data, message, timestamp }. Failures return { success: false, errorCode, message, timestamp } plus an optional structured data object. All JSON keys are camelCase, and every decimal value -- balances, USD amounts, token quantities -- is sent as a JSON string, never as a number.
Wallet Set Endpoints
A set is created empty or with an opening batch of wallets, then grown, renamed, and eventually retired.
- GET /api/v1/wallet-sets -- lists your sets. Passing ?cursor= or ?limit= returns a cursor page (default limit 20); passing ?page=, ?pageSize= or ?search= returns an offset page (default page 1, page size 20); passing no parameters returns the legacy unpaginated shape.
- POST /api/v1/wallet-sets -- creates a set from { name, chain } plus optional description, walletCount and labelPrefix. Responds 201 with only { id, name }, so re-read the set to see addresses.
- GET /api/v1/wallet-sets/{id} -- set detail, including its member wallets.
- PATCH /api/v1/wallet-sets/{id} -- updates name and/or description.
- DELETE /api/v1/wallet-sets/{id} -- responds 204.
- POST /api/v1/wallet-sets/bulk-delete -- body { walletSetIds }, responds { deleted }.
- GET /api/v1/wallet-sets/stats?search= -- { count, totalWallets, totalNativeBalance, totalUsdValue } across the filtered sets.
- GET /api/v1/wallet-sets/{id}/balance -- { totalNativeBalance, totalUsdValue, walletCount }.
- GET /api/v1/wallet-sets/{id}/wallets -- the members. The response carries a pagination envelope, but the service returns every member in a single page.
Adding Wallets to a Set
POST /api/v1/wallet-sets/{id}/wallets/batch mints new keypairs inside an existing set. The body is { count } plus optional labelPrefix and isPrimaryFirst, and the response is 201 with { wallets }.
- count must be between 1 and 200. Anything outside that range is refused 422 with the message "count must be between 1 and 200".
- There is no ceiling on how many wallets a set may hold in total -- only on how many one request may mint. Build a larger set by calling the endpoint repeatedly.
- labelPrefix is optional. Omit it and wallets are labelled with the set name plus an index, so a set named "Runners" produces "Runners 1", "Runners 2", and so on.
- This route sits on the heavy-write rate-limit tier: 5 requests per minute. POST /api/v1/wallets/dev and POST /api/v1/projects/{id}/launch share that tier, and a script that paces itself badly will spend most of its time being throttled.
Individual Wallets and Dev Wallets
Wallet-level routes work across every set you own, and dev wallets have their own pair of create and delete routes because they belong to no set.
- GET /api/v1/wallets?chain=&walletSetId=&isActive= -- a flat list of your wallets with optional filters.
- GET /api/v1/wallets/{id} -- a single wallet.
- PATCH /api/v1/wallets/{id} -- updates label and/or isActive.
- GET /api/v1/wallets/{id}/balance -- the cached balance snapshot. GET /api/v1/wallets/{id}/balance/live performs a live read instead.
- GET /api/v1/wallets/dev?chain=&limit=&cursor=&search= -- dev wallets. With ?cursor= you get a cursor page (default limit 50); otherwise a flat list.
- POST /api/v1/wallets/dev -- creates a standalone dev wallet from { label, chain }, where chain defaults to solana. Responds 201, heavy-write tier.
- DELETE /api/v1/wallets/dev/{id} -- responds 204.
Reading Balances
A wallet object always carries id, chain, address, nativeBalance, totalUsdValue, isActive, isDevWallet, createdAt and updatedAt. It carries label, walletSetId, derivationIndex, transactionCount and antiDetectionScore when those values are known.
GET /api/v1/wallets/{id}/balance returns { nativeBalance, totalUsdValue, tokens }, where each token entry is { mintAddress, amount, decimals } plus symbol, name and usdValue when they resolve.
- Optional fields are omitted from the JSON entirely rather than sent as null. An absent field means unknown, which is not the same as zero -- rendering 0 for an unpriced holding reports a balance you never measured.
- POST /api/v1/wallets/prime-tracking asks the platform to enroll all of your active wallets into balance tracking. It responds 202 with { requested }, which counts the addresses published to the enrollment queue.
- That 202 is not a confirmation. It does not mean any balance was refreshed, and the number is a request size, not a result -- enrollment is performed later by a background worker.
Deletes, Ownership, and Errors
Every delete on this API is a soft delete. The route answers 204, the record is flagged inactive, and it disappears from default listings while remaining intact behind any historical record that references it.
- Bulk operations validate the entire list before touching anything. One unknown or foreign id rejects the whole request -- there is no partial delete.
- A wallet you do not own is reported as 404 NOT_FOUND rather than 403, so the API never confirms that another account's wallet id is real.
- Validation failures are 422 VALIDATION_ERROR, not 400. A wallet set name is required, trimmed, and limited to 32 characters.
- The chain field accepts solana and ethereum case-insensitively; anything else is 422. Only Solana is supported end to end -- trading, launches and exchange funding are all Solana-only -- so an Ethereum set is a container and nothing more.
- Reads are limited to 120 requests per minute per user and writes to 60, with the heavy-write routes above at 5. Exceeding a quota returns 429 with errorCode RATE_LIMITED.