# Endpoint Reference — Request/Response Shapes

All shapes target the public PumpDev API at `https://pumpdev.io`.
Backward compatibility is enforced — fields documented here will not be
renamed or removed.

---

## Auth

Lightning wallet/trade/create endpoints accept the API key either way:

- Query: `?api-key=YOUR_KEY`
- Header: `X-Api-Key: YOUR_KEY`

Local unsigned-tx endpoints don't take an API key — they take a `publicKey`
string instead. This includes `/api/trade-local`, `/api/create`, `/api/bundle`,
claim/cashback/distribute, and transfer endpoints.

---

## Wallets

### POST /api/wallet/create

```json
// Request
{ "label": "my-bot" }              // label optional

// Response
{
  "apiKey":     "pdk_...",         // shown ONCE
  "publicKey":  "Sol...",
  "privateKey": "base58 secret"    // shown ONCE
}
```

### POST /api/wallet/import

```json
// Request
{ "privateKey": "base58 secret", "label": "imported" }

// Response
{ "apiKey": "pdk_...", "publicKey": "Sol..." }
```

### GET /api/wallet/info?api-key=...

```json
// Response
{ "publicKey": "Sol...", "createdAt": "...", "lastUsedAt": "...", "label": "..." }
```

---

## Lightning trade — POST /api/trade-lightning?api-key=...

```json
// Request
{
  "action":            "buy",          // "buy" | "sell"
  "mint":              "Mint...",
  "amount":            0.1,            // SOL if denominatedInSol, else tokens
  "denominatedInSol":  "true",         // string or boolean
  "creator":           null             // optional override
}

// Response
{ "signature": "...", "mint": "Mint...", "action": "buy", "publicKey": "Sol..." }
```

Behaviour:
- `signature` is returned BEFORE broadcast resolves on chain. Poll with
  `getSignatureStatuses` or watch `/ws` for finality.
- Quote mint and token-program are resolved on-chain inside the builder.
- Errors return `{ error, ...details }` where applicable.

---

## Lightning create — POST /api/create-lightning?api-key=...

```json
// Request
{
  "name":           "My Token",    // <= 32 chars
  "symbol":         "MTK",         // <= 10 chars
  "image":          "https://.../logo.png",   // required if no `uri`
  "uri":            "https://...",            // OR provide pre-uploaded metadata
  "description":    "optional",               // default branding used if omitted
  "twitter":        "https://x.com/...",
  "telegram":       "https://t.me/...",
  "website":        "https://...",
  "buyAmountSol":   0.5,            // optional dev buy in SOL
  "buyAmountQuote": null,           // optional dev buy in quote token units
  "quoteMint":      null,           // optional non-SOL quote
  "mintKeypair":    null,           // base58 secret for vanity mint (e.g. "*pump")
  "cashbackEnabled": false,
  "mayhemMode":      false
}

// Response
{
  "signature":   "...",
  "mint":        "Mint...",
  "metadataUri": "https://pumpdev.io/metadata/<uuid>.json",
  "publicKey":   "Sol..."
}
```

Lightning create is **one client call**. Create-only always returns one signed
transaction; create+dev-buy uses one tx when it fits and otherwise PumpDev
sends sequential signed create/dev-buy txs. For atomic multi-buyer launches,
use `/api/bundle-lightning`.

---

## Lightning bundle — POST /api/bundle-lightning

Atomic Jito bundle, up to **4** account entries. The Jito tip is appended
to the LAST tx in the bundle.

```json
// Request
{
  "jitoTip":     0.01,
  "mayhemMode":  false,
  "mint":        null,             // required when no `create` entry present
  "quoteMint":   null,
  "accounts": [
    { "apiKey": "K1", "type": "create",
      "name": "X", "symbol": "X", "image": "https://.../x.png",
      "mintKeypair": null, "cashbackEnabled": false },
    { "apiKey": "K2", "type": "buy",
      "amount": 0.5, "denominatedInSol": true },
    { "apiKey": "K3", "type": "buy",
      "amount": 0.3, "denominatedInSol": true,
      "creator": null },
    { "apiKey": "K4", "type": "sell",
      "amount": 1000000, "denominatedInSol": false }
  ]
}

// Response
{
  "results": [
    { "type": "create", "signature": "...", "publicKey": "...",
      "mint": "...", "metadataUri": "...", "error": null },
    { "type": "buy",    "signature": "...", "publicKey": "...", "error": null }
  ],
  "mint": "Mint..."
}
```

Validation rules:
- Each entry's `type` ∈ `"buy" | "sell" | "create"`.
- At most one `create` entry per bundle.
- `sell` cannot coexist with `create` (token doesn't exist yet).
- `name` ≤ 32, `symbol` ≤ 10, `image` or `uri` required for create.
- `mint` is required when no create entry is present.

---

## Local (client-signed) variants

`/api/trade-local`, `/api/create`, `/api/bundle` accept the same body
shape but use `publicKey` (or `accounts[i].publicKey`) instead of `apiKey`.
They return either:

- An unsigned `VersionedTransaction` byte stream (trade/create), OR
- A `{ transactions: [{ transaction, signers, type, publicKey, ... }], mint, mintSecretKey? }`
  object (bundle).

Client signs with `@solana/web3.js` and broadcasts. `mintSecretKey` is
returned only when a create entry generated a fresh mint keypair server-side
— store it if you want to claim that mint later.

---

## Claim & transfer

| Endpoint | Body highlights |
|----------|-----------------|
| `GET /api/claim-account` | Query `{ publicKey, mint?, quoteMint? }`; read-only balance/preview |
| `POST /api/claim-account` | Body `{ publicKey, mint?, quoteMint?, priorityFee? }`; returns binary unsigned tx |
| `POST /api/claim` | Body `{ publicKey, mint?, quoteMint?, priorityFee? }`; returns binary unsigned tx |
| `POST /api/claim-all` | Body `{ publicKey, mints: ["Mint1", ...], priorityFee? }`; returns `{ transactions: [{ mint, transaction, ... }], count, errors }` |
| `POST /api/claim-distribute` | Body `{ publicKey, mint, priorityFee? }`; returns binary unsigned tx |
| `GET /api/claim-cashback` | Query `{ publicKey, program?, mint?, quoteMint? }`; read-only balance/preview |
| `POST /api/claim-cashback` | Body `{ publicKey, program?, mint?, quoteMint?, priorityFee? }`; returns binary unsigned tx |
| `POST /api/transfer` | Body `{ publicKey, recipient, amount, priorityFee? }`; returns binary unsigned tx |
| `POST /api/transfer-all` | Body `{ publicKey, recipient, priorityFee? }`; returns `{ transaction, balance, estimatedAmount, estimatedFees }` |

These are unsigned local-sign flows. Deserialize/sign/send the returned
transaction with the payer keypair or wallet. Native-SOL quote claim/cashback
payouts can include PumpDev commission; SPL quote payouts skip SOL commission.
SOL transfers have no PumpDev commission.

Claim gotchas: `publicKey` is the fee payer and the only required signer — derive
it from the signing keypair or the RPC rejects the tx. The wallet needs ~0.01 SOL
even when claiming USDC-quoted fees. A `500 Could not resolve quote mint for
<mint>` means no bonding curve or pool resolved; retry, verify the mint, then
check `GET /api/claim-account`. Omitting `mint` sweeps only the wrapped-SOL
creator vault and silently skips graduated PumpSwap, non-SOL quote, and
fee-sharing balances.

---

## Reclaim SOL (Rent)

| Endpoint | Body highlights |
|----------|-----------------|
| `GET /api/reclaim/scan?publicKey=...` | Query `{ publicKey }`; read-only scan for empty/dust accounts, 10 requests/min |
| `GET /api/reclaim/balance?publicKey=...` | Query `{ publicKey }`; `{ publicKey, lamports }`, uncached — check the owner can pay the network fee before building, 30 requests/min |
| `POST /api/reclaim` | Body `{ publicKey, accounts: [...] }`; build close/burn transactions the owner signs and pays for, 20 requests/min |
| `POST /api/reclaim/confirm` | Body `{ publicKey, signatures: [1-4] }`; confirm + record transactions the wallet sent itself (signAndSend), 60 requests/min |
| `POST /api/reclaim/claims` | Body `{ publicKey }`; creator-fee + cashback claim transactions at 2% (no minimum), registered for `/api/reclaim/send`, 20 requests/min |
| `POST /api/reclaim/send` | Body `{ transactions: [...] }`; broadcast and confirm signed reclaim transactions, 60 requests/min |
| `GET /api/reclaim/stats` | Public counter: total SOL reclaimed, accounts closed, distinct wallets; unmetered |
| `GET /api/reclaim/activity` | Public feed, newest first, 20 rows per page — `{ items: [...], hasMore }`; next page via `?before=<last createdAt>&beforeSignature=<last signature, if any>`: landed reclaims (`status: "landed"`, signature + exact lamports) and prepared creator-fee/cashback claims (`status: "prepared"`, `estimatedSol`, no signature); unmetered |

Reclaim closes empty and dust token accounts to recover their rent deposits (roughly 0.002 SOL each). Scan identifies what can be closed, build prices each batch with a 2% commission (the owner pays the network fee — about 0.000015 SOL per transaction — since Solana charges it before the transaction runs), and send broadcasts your signed transactions. The server validates every account against live chain state before building — stale or wrong addresses land in `skipped`, not fatal. Transactions expire after ~3 minutes; rebuild rather than retry. `dust` means any non-zero balance, not only crumbs: each dust entry carries `uiAmount`, `symbol`, `usdPrice` and `usdValue` (Jupiter, `null` when unknown) — check `usdValue` before burning.

---

## PONS (Robinhood Chain) — https://rhc.pumpdev.io

A separate EVM host. Chain id `4663`. No API key on the trade endpoints; the
commission (0.5%, taken on the quote asset) is what pays for them.

### POST /api/trade-local

```json
// Request
{
  "publicKey": "0xYourWallet",     // required — used as `from`, and to read nonce/balance/allowance
  "action": "buy",                 // required — "buy" | "sell"
  "token": "0xTokenAddress",       // required — `mint` accepted as an alias
  "amount": 0.05,                  // required — quote amount, "50%", "100%", or exact tokens
  "denominatedInQuote": "true",    // "true" = amount is in the pair's quote asset
                                   //   (`denominatedInSol` accepted as an alias)
  "slippage": 15,                  // percent, default 90
  "priorityFee": 0.05,             // GWEI PER GAS, default 0.05, refused above 100
  "gasLimit": "312500",            // optional — skips estimation
  "nonce": 41,                     // optional — skips the lookup; pass it when firing in a row
  "deadline": 60,                  // optional — seconds until the swap expires on chain
  "amountOutMin": "900000...",     // optional — your own floor, overrides slippage
  "maxSnipeTaxBps": 700            // optional — highest PONS launch tax (bps) to pay on a buy;
                                   //   default 0 = any tax → HTTP 425 with retryAfterMs
}

// Response
{
  "transaction": {                 // pass straight to wallet.sendTransaction() (ethers/viem)
    "chainId": 4663,
    "type": 2,
    "from": "0xYourWallet",
    "to": "0x...",
    "data": "0x4d819a2a...",
    "value": "50000000000000000",
    "gasLimit": "312500",
    "maxFeePerGas": "2500000",
    "maxPriorityFeePerGas": "1000000000000000",
    "nonce": 41
  },
  "approvalTransaction": null,     // non-null on a sell (or ERC-20-quoted buy) with a short allowance
                                   // when set: { chainId, type, to: <token>, data: approve(spender, max),
                                   //   value: "0" } — same response, no separate endpoint; the wallet
                                   //   fills from/nonce/gas itself
  "gasEstimated": true,            // false = allowance missing, gasLimit is a safe fallback
  "quote": {
    "quoteAsset": { "address": "0x0000...", "symbol": "ETH", "decimals": 18 },
    "amountIn": "50000000000000000",
    "expectedOut": "1000000000000000000000",
    "amountOutMin": "900000000000000000000",
    "priceSource": "curve",        // "curve" (on the bonding curve, exact) | "pool" (graduated, v4 pool, exact) | "feed" | "logs" | "caller"
    "priceAgeMs": 0,
    "snipeTaxBps": 0,              // curve only — launch tax on THIS wallet's buy right now
    "snipeTaxEndsAt": null,        // unix seconds the tax reaches 0, while it is non-zero
    "routeSource": "registry"
  },
  "route": [
    { "kind": 27, "tokenIn": "0x0000...", "tokenOut": "0xToken", "pool": "0xPool", "fee": 0 }
  ],
  "fee": { "bps": 50, "amount": "250000000000000", "asset": "ETH" }
}
```

Field notes:
- **Every amount is a decimal string in base units.** A `uint256` does not
  survive a JSON number, so none of these are numbers.
- `amountIn` / `expectedOut` are **wallet-facing** — what leaves your wallet
  and what lands in it after commission. `amountOutMin` is a floor on the same
  wallet-facing figure in both directions.
- A quote never comes back with `amountOutMin: "0"`. If recent trades cannot
  price the amount safely the request is refused instead — pass
  `amountOutMin: "0"` yourself only if you really mean it.
- `priceAgeMs`, not `priceSource`, is what tells you how much to trust the
  price. Blocks are ~100ms, so seconds of age is stale.
- `approvalTransaction` → send it, wait for it, then **rebuild** so
  `gasEstimated` comes back `true`.
- There is no simulation. `swap()` returns no data, so `eth_call` is empty.
- **Snipe tax.** A PONS curve taxes buys 9900 → 618 → 19 → 0 bps at +0/+1/+2/+3
  whole seconds of `block.timestamp` after the launch block (creator wallets
  exempt). A buy whose tax exceeds `maxSnipeTaxBps` is refused with HTTP 425
  and `{ snipeTaxBps, launchedAt, snipeTaxSeconds, snipeTaxEndsAt, retryAfterMs }`
  — sleep `retryAfterMs`, rebuild. Within the ceiling it is priced in and
  built. Read in the same Multicall as the reserves: no extra request.

### GET /api/quote

Same route lookup and same maths, no gas estimation — works for a wallet that
holds nothing yet.

```
GET /api/quote?publicKey=0x...&action=buy&token=0x...&amount=0.05&denominatedInQuote=true
```

### Errors

`No known route for this token` (never traded), `No token balance found`,
`Invalid percentage value — use 1% to 100%`, `Calculated sell amount is zero`,
`Price unavailable — pass amountOutMin explicitly`, `Refusing to build a trade
with no floor…`, `Snipe tax of N bps applies to buys this soon after launch…`
(HTTP 425, see above), `Gas estimation failed: …` (usually slippage, allowance
or a stale route).

### POST /api/create-local

Launch a PONS token on its ETH bonding curve. Unsigned transaction back; the
commission (0.5%) is on the dev buy only, taken inside the launch by
PumpDev's launch router. No dev buy → no commission. ETH pair only for now.

```json
// Request
{
  "publicKey": "0xYourWallet",     // required — creator, dev-buy recipient, `from`
  "name": "My Token",              // required, ≤ 32 chars
  "symbol": "MINE",                // required, ≤ 10 chars
  "image": "https://…/logo.png",   // required — URL, stored in the launch itself (no metadata JSON)
  "description": "…",              // default: "Launched on pumpdev.io — the fastest PONS API"
  "twitter": "…", "telegram": "…", "website": "…",
  "buyAmount": 0.01,               // dev buy in ETH, default 0 (buyAmountSol/buyAmountQuote = aliases)
  "creatorTaxBps": 100,            // your tax on every curve buy, 0–1000, default 0
  "slippage": 90,                  // on the dev buy; the curve is fresh so the quote is exact
  "snipeTaxExempt": ["0x…"],       // ≤ 30 wallets that buy tax-free in the first 3 s (bundle team)
  "priorityFee": 0.05,             // gwei per gas
  "gasLimit": "4500000",           // optional — a launch is ~3.7–4.2M gas
  "nonce": 41                      // optional
}

// Response
{
  "transaction": { "chainId": 4663, "type": 2, "from": "0x…", "to": "0xLaunchRouter",
                   "data": "0x9fbbeb66…", "value": "10500000000000000",   // launchFee + buyAmount
                   "gasLimit": "…", "maxFeePerGas": "…", "maxPriorityFeePerGas": "…", "nonce": 41 },
  "token": "0xNewToken",           // predicted before sending (CREATE2); null only if the call failed
  "curve": "0xBondingCurve",
  "salt": "0x…",
  "publicKey": "0xYourWallet",
  "launchFee": "500000000000000",  // PONS's own fee, read live (0.0005 ETH)
  "fee": { "bps": 50, "amount": "50000000000000", "asset": "ETH" },
  "quote": { "amountIn": "9950000000000000", "expectedOut": "…", "amountOutMin": "…",
             "quoteAsset": { "symbol": "ETH", "decimals": 18 } }   // null without a dev buy
}
```

The creator and every `snipeTaxExempt` wallet read 0 bps snipe tax from the
launch block; everyone else pays 9900 → 618 → 19 → 0. Errors: `Only ETH-paired
launches are supported…`, `snipeTaxExempt may list at most 30 addresses`,
`Gas estimation failed: …` (usually an unfunded wallet: launch fee + buy + gas).

### POST /api/create-lightning?api-key=KEY

Same body without `publicKey`, `gasLimit`, `nonce`. Server signs and sends;
1% of the dev buy. Response: `{ hash, token, curve, salt, publicKey,
launchFee, fee, quote }` — `hash` before the send, `402 { error, need,
balance }` when the wallet cannot cover launch fee + buy + gas.

### WebSocket — wss://rhc.pumpdev.io/ws

Same method names and frame shape as the Solana socket:
`subscribeNewToken`, `subscribeTokenTrade`, `subscribeAccountTrade`.

- Launches, graduations and `tokenMeta` are **free and unmetered** — no key.
- Trades need `?key=<API_KEY>`, the same key and plan as Solana; both chains
  share one monthly trade allowance.
- Every event carries `status: "pending"` (decoded from the sequencer, before
  execution, ~66ms median head start) or `"confirmed"` (from the executed
  block). **Deduplicate by `txHash`** and settle on `confirmed`.
- `pairToken` gives the address of the quote asset. PONS pairs against ETH,
  WETH, USDG (6 decimals) and tokenized equities (18) — always read `quote`
  rather than assuming decimals.

---

## WebSocket — wss://pumpdev.io/ws

Scope: Pump.fun bonding-curve events and canonical PumpSwap pool events. Non-canonical PumpSwap pools and other platforms are ignored by default so market data aligns with PumpDev trade execution.

Methods:

```json
{ "method": "subscribeNewToken" }
{ "method": "subscribeTokenTrade",  "keys": ["Mint1", "Mint2"] }
{ "method": "subscribeAccountTrade", "keys": ["Wallet1"] }
{ "method": "unsubscribeNewToken" }
{ "method": "unsubscribeTokenTrade", "keys": ["Mint1"] }
```

Live `create` event sample (captured from the feed):

```json
{
  "txType": "create",
  "signature": "...",
  "mint": "...pump",
  "traderPublicKey": "...",
  "name": "...",
  "symbol": "...",
  "uri": "https://ipfs.io/...",
  "initialBuy": 67062500,
  "solAmount": 8e-09,
  "bondingCurveKey": "...",
  "vTokensInBondingCurve": 1073000000000000,
  "vSolInBondingCurve": 30000000000,
  "marketCapSol": 27.96
}
```

Field notes for `txType: "create"`:
- `initialBuy` is the dev allocation in display-token units (decimals already applied).
- `solAmount` is **not reliable** on create events. Do not use it for dev-buy size.
- `vTokensInBondingCurve` is raw base units with 6 decimals. `1073000000000000`
  means `1,073,000,000` tokens.
- `vSolInBondingCurve` is in lamports. `30000000000` means `30` SOL.
- The bonding curve values in a create event are the **pristine pre-dev-buy**
  state, even though the dev buy happens in the same transaction. A naive
  `vSolInBondingCurve - 30` delta will therefore return `0`.

Compute dev buy SOL from a create event:

```python
INITIAL_V_SOL, INITIAL_V_TOKENS = 30.0, 1_073_000_000.0
K = INITIAL_V_SOL * INITIAL_V_TOKENS

def dev_buy_sol(initial_buy_tokens):
    return K / (INITIAL_V_TOKENS - initial_buy_tokens) - INITIAL_V_SOL
```

Reconnect with exponential backoff (start 1s, cap 30s) and re-subscribe
on `open`.
