Skip to main content
Everything on this page is built for a client that branches on fields rather than reading prose: versioned paths, coded errors, a published OpenAPI document, and two endpoints that assemble a transaction for your key to sign.
BaseStonk never signs, never relays and never broadcasts. The /prepare endpoints return an unsigned transaction - to, data, value, chainId - and the sending is yours. A client waiting for a transaction hash from this API is waiting forever.

The /api/v1 surface

Every endpoint on the REST API is also served under /api/v1/ with the same data reshaped into a machine contract:
  • Coded errors. {"error":{"code":"rate_limited","message":"rate limited","retryAfter":21}} instead of prose - the code reference.
  • One list envelope. Every paged list carries items and nextOffset - pass it back as offset, stop at null.
  • Idempotency echo. An Idempotency-Key header is echoed back.
  • Every response carries x-bstonk-api-version: 1.
The machine descriptions of the whole surface:
Those two are canonical; this page is orientation. Both are generated from one registry in the API’s own source, so they cannot drift from what the server does. Treat any body shown here as an example and the spec as the authority.
Unversioned paths keep their exact shapes forever; /api/v1 is where the contract is promised. Responses evolve additively on both - ignore unknown fields.

Budgets and refusals

A bearer buys, it never gates. Every read works without one. An identity changes which bucket you spend from: With a bearer you spend from the wallet bucket and the envelope at once; the tighter of the two is what x-ratelimit-remaining reports. The envelope exists because wallets are free to create - without it, one address could mint sessions in a loop and print budget. x-bstonk-identity tells you which budget answered - wallet when the bearer was honoured, ip when it was absent, expired or invalid. Reads still succeed in the ip case, so this header is the only way to notice a bearer silently stopped working.
A 429 means refused, never empty. A client that maps a failed fetch to an empty array will report zeros for data that exists, and at machine speed it will do so confidently. Honour retry-after.

Error code reference

error.code is the stable value for program logic; message is human-readable text that may improve over time. Branch on status and code, never on message. When 402 arrives it will carry an accepts array naming an x402 scheme.

Retry policy

  • retry 429 after retryAfter
  • retry 500 once; retry 503 with capped exponential backoff and jitter
  • do not blindly retry 400, 401, 403 or 404
  • a 422 is not a retry - it is a precondition to fix, named in verdict.checks
  • re-prepare after expiresAtBlock; never sign a transaction past its expiry
  • a launch re-prepare is free of duplicates by construction - the idempotency key is the CREATE2 salt

Sessions: a wallet signature, briefly a bearer

A session keys your rate budget to your wallet instead of your IP - which matters the moment your agent shares datacenter egress with somebody else’s. Sign one message, exchange it once:
The signature is EIP-712 typed data - domain { name: "BaseStonk", version: "1", chainId: 8453 }, type Authorization { action: string, address: address, expiresAt: uint256, detail: string } - with detail empty and action = agent:session for the read tier or agent:session:prepare for read + /prepare. The scope is part of what the wallet signs, on purpose: a signature collected for reads cannot be redeemed for the prepare tier. Rules that will save you a debugging session:
  • A signature mints exactly one session - a replay answers 401 signature already used.
  • Bearers last one hour. GET /api/v1/agent/whoami says which identity and scopes a bearer carries without spending a real read.
  • Minting is rationed at 3 per hour per IP.
  • The same cost weighting applies; a /prepare call costs 10 units on top of the shared lane’s 1.

Prepared transactions

Two endpoints assemble, check and simulate a transaction, then hand it to you unsigned. Both demand the prepare scope. The shared response shape:
  • verdict.checks is what was actually asked of the chain. A failed check answers HTTP 422 with the full verdict in the body - never a 200 with a quiet flag. Branch on status, then read checks.
  • gas is the estimate plus 25% headroom, or null when the simulation failed.

approvalTx comes first

When a request moves tokens the contract must pull, the answer may carry an approvalTx. Send it, wait for the receipt, then send tx. It is a separate field rather than an array so the main transaction cannot race its own precondition, and it approves exactly the amount needed, never unbounded. It is null once the allowance suffices - re-preparing after the approval lands is how you confirm you are ready.

Staleness, and why re-preparing is safe

expiresAtBlock is roughly five minutes out. Past it, re-prepare rather than signing - the simulation was true at one block only. Re-preparing a launch costs nothing: the idempotency key is the CREATE2 salt - keccak256(sender, salt) - the same value the chain itself collision-checks. If the launch has not landed you get the same transaction back; if it has, the verdict says so and steps to the next free address. A trade’s idempotencyKey is null for the opposite reason: two identical swaps are two legitimate trades, and retry safety on a trade is your nonce, not our field.

Launch

The verdict runs the checks a launch actually fails on: the current launcher generation, the pair being priceable right now (a paused feed or a too-thin route surfaces here, named, instead of as a bare revert in your wallet), the salt sorting the token below its pair, the CREATE2 address being free, and finally eth_call plus a gas estimate against the live launcher.
  • The creator can only be your bearer’s wallet.
  • devBuyPair is raw units of the pair token; the intent reports it as pair amount, USD, and percentOfSupplyAtOpen. A non-zero dev buy gets funding and allowance checks and an approvalTx when short.
The pair decides which leg you get, and intent.leg names it: You do not choose. A gated answer additionally carries route, issuerFactory, startSqrtPriceX96 and impliedCapUsd in the intent; gate_admits is the check that fails when the cap is outside the gated band. A pair that is neither registered nor routable comes back as a 422 with tx.data empty - there is genuinely nothing to sign.

Trade

amountIn is raw units of what you are spending: the pair token on a buy, the launched token on a sell. The answer is a swap through the venue router with minOut set from the quote and your slippage.
  • Quoted at the token’s own tax, never a default. Taxes here range to 10%, and a quote at an assumed rate reverts as TooLittleReceived - a failure that reads as a broken pool. A token with no rate on record refuses to quote rather than guessing.
  • The chain outranks the quote. The eth_call fill is compared against minOut - a stale quote fails the verdict instead of failing your transaction.
  • A fresh launch nobody has bought yet quotes buys from its entry boundary; a sell into it is refused - the empty side has nothing to sell into.

The loop, end to end

Step 5 is the whole design. Your key never leaves your process, and this API never learns it exists.

A caveat worth knowing before you debug it

BaseStonk sits behind a CDN that bot-scores traffic, and requests from datacenter address space can be challenged at the edge - before the API sees them at all. The tell is a 403 with no JSON error envelope: every refusal this API itself issues is JSON with an error.code. If you hit one, it is the edge, not your bearer. A carve-out for the /api/v1 paths is pending.