# AI integration

This site is built to be read by machines as well as people. Every page exists in a
portable Markdown form, and the whole corpus is available as a single file you can paste
into a chat.

## The machine-readable endpoints

| Path | What it is | Use it for |
| --- | --- | --- |
| [`/llms.txt`](/llms.txt) | An [llmstxt.org](https://llmstxt.org) index: one line per page, absolute links | Letting an agent discover what exists and fetch only what it needs |
| [`/llms-full.txt`](/llms-full.txt) | The **entire** documentation as one Markdown file | Pasting into a chat, or seeding a context window in one shot |
| `/md/{slug}` | Any single page as raw Markdown, no site chrome | Fetching one page cheaply |
| [`/corpus.json`](/corpus.json) | Every page as structured JSON, split into sections | Building an index, a RAG store, or your own search |
| [`/search-index.json`](/search-index.json) | The index this site's own search box uses | Reusing the same section-level search |

Every HTML page also declares its Markdown twin in the head, so a crawler can find it
without guessing:

```html
<link rel="alternate" type="text/markdown" href="/md/authentication">
```

### Fetching the whole thing

```bash
# one file, everything
curl -s https://docs.cryptoinfra.net/llms-full.txt -o cryptoinfra-docs.md

# or just the index, then the pages you need
curl -s https://docs.cryptoinfra.net/llms.txt
curl -s https://docs.cryptoinfra.net/md/idempotency
```

### `corpus.json`

```json
{
  "service": "cryptoinfra",
  "version": "1.0.0",
  "generatedAt": "2026-08-31T00:00:00.000Z",
  "pages": [
    {
      "slug": "authentication",
      "title": "Authentication",
      "summary": "…",
      "kind": "guide",
      "markdown": "# Authentication\n…",
      "sections": [
        { "anchor": "the-key", "heading": "The key", "text": "…" }
      ]
    }
  ]
}
```

`kind` is `guide` for hand-written prose and `reference` for pages generated from the
OpenAPI contract. `sections` is the page split at its headings, so a search can return an
excerpt rather than a whole page.

## The OpenAPI contract itself

Everything on the reference pages is generated from a single OpenAPI 1.0.0
document — 68 operations, 56 schemas and
9 webhook events. If you are generating a client, generate it from that
document rather than from this prose. Ask your operator contact for the current
`openapi.yaml`; this site is rebuilt from it, so the two cannot disagree.

## Facts an agent should not have to infer

Models writing an integration against this API tend to make the same handful of wrong
assumptions. Each of these is stated here so it can be retrieved directly.

**There is no `swap.completed` or `exchange.completed` webhook.** Swaps and exchanges
emit **nothing**. Generated code that awaits one will hang. Poll
`GET /v1/swap/orders/{id}` (swaps *and* exchanges) while `status` is non-terminal.

**The complete event list is 9 long:** `deposit.credited`, `withdrawal.confirmed`, `withdrawal.failed`, `card.kyc`, `card.status`, `card.topup`, `card.withdraw`, `card.transaction`, `card.wallet_otp`. Nothing
else is emitted, ever.

**The API key authenticates the merchant, not the end user.** An `external_user_id` is a
selector with no authority. Never put the key in client-side code.

**Amounts come in pairs.** A decimal string for display, and an exact integer `*_base`
string for arithmetic. Use `BigInt` (or the equivalent) on `*_base`. The one systematic
exception is an *exchange* quote's buy side, which has no `_base` twin because that coin's
chain is not modelled by the platform — swap quotes, where both sides are ours, do carry it.

**Excess decimal places are refused, not rounded.** `"15.1234567"` on a six-decimal asset is
`400 invalid_amount` on a withdrawal or swap; a card
amount past six decimal places is `400 invalid_request`. The amount you send is always
the amount that moves.

**`202` on a card money endpoint means pending, not failed.** Refund only on a definitive
failure: `409 provider_rejected`, or a webhook whose order `status` is `failed`.

**A newly issued card comes back `status_normalized: issuing`, not `active`.** It turns
active once the opening balance lands at the issuer. Do not read `issuing` on a `201` as an
error.

**Cards are issued funded.** `initial_amount` is required on `POST /v1/cards` and must meet
that product's `min_opening_balance`, read from `GET /v1/cards/products` — never
hard-coded. The cardholder is debited `issue_total` from `GET /v1/cards/products` — our
issue fee, the opening balance priced as a load (with its load fee and gas) and your own
fee — not just `issue_fee + initial_amount`.

**A `404` may mean "belongs to another merchant".** Unknown and not-yours are
indistinguishable by design.

**Retry with the same `idempotency_key`.** A fresh key on a retry is a double payment.

**Webhooks are hints; the poll is truth.** Reconcile against the resource read before
telling a customer anything final.

## A prompt that works

If you are pointing an assistant at this API, this framing avoids most of the failure modes
above:

```text
You are integrating against the cryptoinfra Merchant API.
Read https://docs.cryptoinfra.net/llms-full.txt first and treat it as the
contract. Rules that override your priors:
  - Only these webhooks exist: `deposit.credited`, `withdrawal.confirmed`, `withdrawal.failed`, `card.kyc`, `card.status`, `card.topup`, `card.withdraw`, `card.transaction`, `card.wallet_otp`.
    Swaps and exchanges emit NO webhook — poll their order reads.
  - Compute with the *_base integer strings, never the decimal strings.
  - Every money mutation carries an idempotency_key; retries reuse it.
  - Treat 202 as pending. Refund only on a definitive failure.
If the docs do not state something, say so rather than inventing it.
```

## Documentation MCP server

An agent that speaks the [Model Context Protocol](https://modelcontextprotocol.io) can read
these docs itself, instead of you pasting them in:

```text
https://docs.cryptoinfra.net/mcp
```

Public, read-only, no API key, and it speaks Streamable HTTP — so anything that can reach a
remote MCP server can use it.

**It serves documentation and nothing else.** It cannot read a balance, create a payment or
move a coin, and it holds no credential — the process drops credential-shaped environment
variables before it loads any request-handling code, and reads exactly one file. Giving it to
an assistant costs you nothing.

### Connect a client

The URL is the whole configuration. Where each client wants to be told about it:

<!-- cards -->

#### Claude & Claude Code

Answers from these pages inside a conversation, and inside `claude` in your terminal.
In Claude itself, add it as a custom connector by URL.

```bash
claude mcp add --transport http cryptoinfra-docs https://docs.cryptoinfra.net/mcp
```

#### ChatGPT & Codex

ChatGPT connects to it as a custom connector by URL. Codex reads it from
`~/.codex/config.toml`, and will then cite these pages while it writes your client.

```toml
[mcp_servers.cryptoinfra_docs]
url = "https://docs.cryptoinfra.net/mcp"
```

#### Cursor

Puts the reference in the composer's reach, so a generated integration is written against
the real contract instead of a remembered one. Project-scoped, in `.cursor/mcp.json`.

```json
{
  "mcpServers": {
    "cryptoinfra-docs": { "url": "https://docs.cryptoinfra.net/mcp" }
  }
}
```

#### GitHub Copilot

Agent mode in VS Code picks the server up from `.vscode/mcp.json`, so it is shared with
everyone who checks the repository out.

```json
{
  "servers": {
    "cryptoinfra-docs": { "type": "http", "url": "https://docs.cryptoinfra.net/mcp" }
  }
}
```

<!-- /cards -->

Anything else that speaks Streamable HTTP takes the same one line:

```json
{
  "mcpServers": {
    "cryptoinfra-docs": { "url": "https://docs.cryptoinfra.net/mcp" }
  }
}
```

### Tools

| Tool | When an agent should reach for it |
|---|---|
| `list_docs` | Discover every topic and its resource URI. |
| `search_docs` | Find a section-level answer with ranked excerpts and links, instead of loading the whole corpus. |
| `get_doc` | Read one page in full, as portable Markdown. |
| `get_integration_guide` | Get an ordered reading plan for one pattern: `hosted-checkout`, `deposits-and-balances`, `payouts`, `swap-and-exchange`, `cards`, `webhooks`. |

Every result carries structured JSON alongside the human-readable text, and search returns
resource links so an agent fetches only the pages it needs.

### Resources

| Resource | What it is |
|---|---|
| `docs://cryptoinfra/index` | Machine-readable service and page index. |
| `docs://cryptoinfra/full` | The complete reference as one document. |
| `docs://cryptoinfra/{slug}` | A single page. Supports slug completion, so a client can discover valid URIs rather than guess. |

### Prompts

- **`integrate-cryptoinfra`** — reads the right guide for your pattern and produces
  implementation code, tests, configuration and a go-live checklist.
- **`review-cryptoinfra-integration`** — reviews code you already have for the things that
  actually go wrong here: auth, idempotency on every mutation, `*_base` versus decimal
  handling, webhook signature verification and replay, polling for the operations that emit
  no webhook, and reconciliation gaps.

If a `get_integration_guide` pattern is missing a page, it says so rather than inventing the
step — a gap in our docs should read as a gap, not as confident filler.

## Licence and freshness

The reference pages are generated on every build from the live contract, so a page that
disagrees with the running service is a bug — report it rather than working around it. This
build documents OpenAPI version **1.0.0**.
