> ## Documentation Index
> Fetch the complete documentation index at: https://docs.basestonk.io/llms.txt
> Use this file to discover all available pages before exploring further.

# AI agents

> The machine surface: /api/v1, wallet-signed sessions, and prepared transactions the API never signs.

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.

<Warning>
  **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.
</Warning>

## The `/api/v1` surface

Every endpoint on the [REST API](/integration/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](#error-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:

```bash theme={null}
GET https://api.basestonk.io/api/v1/openapi.json   # OpenAPI 3.1
GET https://api.basestonk.io/api/v1/llms.txt       # the same, for a context window
```

<Note>
  **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.
</Note>

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:

| Tier      | Budget            | Keyed to                                        |
| --------- | ----------------- | ----------------------------------------------- |
| anonymous | 120 units / min   | your IP (an IPv4 address, or an IPv6 /64)       |
| bearer    | 360 units / min   | your **wallet**, per route lane                 |
| envelope  | 360 units / min   | your **IP**, across every lane and every wallet |
| minting   | 3 sessions / hour | your IP                                         |

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.

<Warning>
  **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`.
</Warning>

## 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.

| Code                | Status | When                                                                  | What to do                                                    |
| ------------------- | ------ | --------------------------------------------------------------------- | ------------------------------------------------------------- |
| `invalid_request`   | `400`  | a malformed address, interval or parameter                            | correct it; do not retry unchanged                            |
| `validation_failed` | `400`  | the body failed schema validation - `detail` names the fields         | fix the named fields                                          |
| `unknown_chain`     | `400`  | a chain other than `base`                                             | use `base`                                                    |
| `unauthorized`      | `401`  | the signature is absent, expired, or already used                     | sign and mint a fresh session                                 |
| `forbidden`         | `403`  | the bearer lacks the scope, or the creator is not the bearer's wallet | sign the right `action`; launch as yourself                   |
| `not_found`         | `404`  | no row for that identifier                                            | verify it; do not loop on an unchanged 404                    |
| `conflict`          | `409`  | every mined address candidate already holds code                      | prepare again for a fresh salt                                |
| *(verdict)*         | `422`  | a prepare failed its checks - the full verdict is the body            | read `verdict.checks`, fix the named precondition, re-prepare |
| `rate_limited`      | `429`  | a budget is exhausted - `retryAfter` says how long                    | wait it out; never map to empty                               |
| `internal`          | `500`  | an unexpected fault                                                   | retry once                                                    |
| `unavailable`       | `503`  | a dependency is down, or launching is closed                          | retry with capped backoff                                     |
| `payment_required`  | `402`  | **reserved, inert today** - no endpoint returns it                    | handle early and it costs nothing later                       |

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](#staleness-and-why-re-preparing-is-safe)

## 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:

```bash theme={null}
POST /api/v1/agent/session
  headers:
    x-wallet-address:   0xYourWallet
    x-wallet-signature: <signature>
    x-wallet-expires:   <unix ms, up to 5 minutes ahead>
  body: { "scopes": ["read"] }              # or ["read","prepare"]

→ { "token": "v1.…", "wallet": "0x…", "scopes": ["read"], "expiresAt": 1788… }
```

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](/integration/api#rate-limits) 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:

```json theme={null}
{
  "intent":       { "…what the calldata means, decoded…" },
  "tx":           { "to": "0x…", "data": "0x…", "value": "0", "chainId": 8453, "gas": "1829382" },
  "approvalTx":   null,
  "verdict":      { "ok": true, "checks": [ { "id": "…", "ok": true, "detail": "…" } ] },
  "expiresAtBlock": "50719091",
  "idempotencyKey": "0x…"
}
```

* **`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

```bash theme={null}
POST /api/v1/agent/launch/prepare
{
  "name": "My Token", "symbol": "MTK",
  "pairToken": "0x0F61Edbfe6Cd86024C0f210c0695B08df55fdfc9",
  "marketCapUsd": 5000,
  "metadataUri": "ipfs://…",
  "buyTaxBps": 100, "sellTaxBps": 100,
  "maxWalletBps": 250,
  "devBuyPair": "0"
}
```

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:**

| `intent.leg` | Pair                                                                                     | Priced by                                          |
| ------------ | ---------------------------------------------------------------------------------------- | -------------------------------------------------- |
| `direct`     | one the oracle registry holds a feed for - USDC, WETH, cbBTC, \$BSTONK, tokenized stocks | the launcher's own registry                        |
| `gated`      | an ecosystem token with no feed                                                          | a route the RoutedQuoteGate re-verifies hop by hop |

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

```bash theme={null}
POST /api/v1/agent/trade/prepare
{
  "token": "0x…",
  "side": "buy",
  "amountIn": "100000000000000000000",
  "slippageBps": 300
}
```

`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

```text theme={null}
1. sign agent:session:prepare        →  POST /agent/session      →  bearer
2. POST /agent/launch/prepare        →  422? read verdict.checks, fix, repeat
3. approvalTx present? sign, send, wait for the receipt
4. re-prepare                        →  200, verdict.ok
5. sign tx yourself, send it yourself, before expiresAtBlock
```

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.
