# Introduction

cryptoinfra is **custodial wallet infrastructure on TRON**. You bring your users; the
platform gives each of them a [Static Wallet Address](deposit-addresses), watches it, credits confirmed
deposits to a double-entry ledger, and executes payouts on their behalf.

You never handle a private key, and you never write chain-specific code. You call one
REST API — `https://api.cryptoinfra.build` — with your merchant key, and the platform speaks the chain
underneath. `GET /v1/assets` is the live list of what can be received and sent — today TRX,
USDT (TRC20) and USDC (TRC20).

> **Start here** In a hurry? The [Quickstart](quickstart) is six copy-paste calls from a
> new user to a paid-out balance, with the five mistakes everyone makes. The rest of this
> page is the model behind them.

## The happy path

```http
POST /v1/users            → a static wallet address
GET  /v1/users/{id}/balances
POST /v1/withdrawals      → payout, with an idempotency key
```

1. **Create the user.** `POST /v1/users` with your own `external_user_id` — a number of
   5 to 10 digits. You get one **[Static Wallet Address](deposit-addresses)** back — a permanent
   TRON address that never expires and never rotates, so you can store and display it once. The call is idempotent on `external_user_id`:
   replaying it returns the same user and the same address, still `201`.

2. **Show the static address; wait for the deposit.** When your user sends funds, the platform
   detects the transaction, waits for the chain's confirmation threshold, credits the
   balance in full (deposits are free), and fires a
   [`deposit.credited`](webhook-events#deposit-credited) webhook.

3. **Read the balance.** `GET /v1/users/{externalId}/balances` returns per-asset
   spendable balances. Assets with no activity are simply absent from the response.

4. **Pay out.** `POST /v1/withdrawals` holds `amount + fee + platform_fee` from the user's
   balance and queues the payout. `fee` is the live network cost of the send, gas included
   (quote it first with `GET /v1/withdrawal-fee-quote`, which also says whether the figure
   is live); `platform_fee` is the flat platform fee. `amount` is what the *recipient* receives. You get
   `202 Accepted` back; the outcome arrives as
   [`withdrawal.confirmed`](webhook-events#withdrawal-confirmed) or
   [`withdrawal.failed`](webhook-events#withdrawal-failed) — and the failure path refunds
   the hold automatically.

Preview the exact cost of step 4 before you commit to it with
`GET /v1/withdrawal-fee-quote`.

## What "custodial" means here, precisely

Three facts follow from the custodial model, and every integration decision comes back to
one of them.

**Keys are ours, balances are yours.** The platform derives and holds the keys for every
address it hands out. Your API key authenticates *you*, the merchant — never your end
user. A user identifier is a number you choose (`external_user_id`, 5 to 10 digits); it
carries no authority of its own. Never ship the API key to a browser or a mobile app.

**Balances are ledger rows, not chain state.** A user's balance is a double-entry ledger
position, not a `getBalance` call. It is credited when a deposit clears confirmations and
debited the moment a payout is *requested*, not when it confirms. That is deliberate: the
balance you read is always **spendable** and always net of holds, so it can read lower
than the chain while an operation is in flight, and never higher.

**Every movement of money is on-chain.** A payout — to one of your own users or to any
address outside the platform — is a `POST /v1/withdrawals`, priced with our fee and the
network's gas. There is no off-chain book transfer between accounts.

## Your account is live the moment you sign up

You create your account yourself at the dashboard — company, email, password and a
Telegram username. There is no approval step: the account is active from the first
minute. The one thing it waits on is your email. Until the address is verified you cannot
sign in, and you cannot generate or see an API key (`403 email_unverified`); the link is
in your inbox, and the sign-in page can send a fresh one. Verify it, sign in, and the
dashboard is yours: generate your key, register webhooks, invite teammates, use the
sandbox — and the API and every money feature are open.

Signup is open, not unguarded. Use a mailbox you will keep — disposable addresses are
refused (`400 email_disposable`) — and, when the page shows one, complete the bot check
before you submit (`400 bot_check_failed` otherwise). We keep where a signup and each
sign-in came from, and we look at accounts that share a network, a Telegram username, a
company e-mail domain, a payout address or their customers with another merchant; such a
link, or a day of earnings far out of line with your own history, can hold a profit
withdrawal for a look — the request says so — but never closes an account on its own.

## Webhooks are hints; the poll is truth

This is the single most important operational rule in the API, and it is worth internalising
before you write any code.

> **Important** Webhook delivery is at-least-once and best-effort. The read endpoints are
> authoritative. Before you credit a customer, issue a refund, or tell anyone that money
> has moved, reconcile against the resource itself — `GET /v1/withdrawals/{id}`,
> `GET /v1/swap/orders/{id}`,
> `GET /v1/cards/orders/{order_id}`, `GET /v1/users/{externalId}/balances`.

And some flows have no webhook at all. **Swaps and exchanges emit nothing** — there is
no `swap.completed` and no `exchange.completed`. Code that waits
for one waits forever. See [Webhooks](webhooks) for the complete list and what to poll
instead.

## What else is in the API

Beyond the wallet core, the same key and the same conventions reach every flow. Each has a
short guide that walks it once, in order:

- **[Payouts](payouts)** — quote, send, and learn the outcome.
- **[Swaps and exchange](swaps-and-exchange)** — trade between assets a user holds, or sell
  one and pay any of thousands of other coins out to an external address.
- **[Cards, end to end](cards-guide)** — issue crypto debit cards to your users, funded from
  their own balance. Cards settle in USDT; a card is issued **funded**.
- **[Developer](developer)** — register webhook endpoints and read your own request log.

## Conventions at a glance

| Convention | What to expect |
| --- | --- |
| Base URL | `https://api.cryptoinfra.build` |
| Auth | `Authorization: Bearer ck_…` — see [Authentication](authentication) |
| Casing | `snake_case` everywhere, in both directions |
| Money | decimal string + exact `*_base` integer string — see [Money and amounts](money-and-amounts) |
| Timestamps | RFC 3339 with an offset; date filters accept a bare IST calendar day |
| Errors | `{ "error": "code", "message": "…" }` — see [Errors](errors) |
| Mutations | carry an `idempotency_key` — see [Idempotency](idempotency) |
| Contract | OpenAPI 1.0.0, 68 operations, 56 schemas, 9 webhook events |

Every page of this reference is generated from that OpenAPI contract, so it cannot drift
from the running service. Every page is also available as raw Markdown at `/md/{slug}`,
and the whole site as one file at [`/llms-full.txt`](/llms-full.txt) — see
[AI integration](ai-integration).
