Developer API
Sell TOR Coin inside your own wallet or platform. You embed one snippet, we host the checkout, the Rust vault and the payout rails — and your payment credentials are never exposed to your users or to your front-end.
Your snippet
<script src="https://www.torcoin.fun/docs/embed.js" data-tor-key="pk_live_your_key"></script> <button data-tor-buy="2.00">BUY TOR</button>
Replace pk_live_your_key with the publishable key issued for your
integration. The secret key (sk_live_…) stays on your server and is only
used for server-to-server calls — it must never be shipped to a browser.
Your API keys
Sign in on the platform page first: a key is issued against your TOR wallet key pair, so the wallet that owns the integration is provable.
Full reference
# TOR Coin — Developer API
Sell (and buy back) TOR Coin inside your own wallet, storefront or bot. You embed
one script tag, the widget opens a **BUY TOR** popup on your page, TOR Coin hosts
the checkout, the Rust vault signs the TOR delivery and the payout rails — your
payment credentials are never exposed to your users or your front-end.
Base URL: **`https://www.torcoin.fun`** (the domain the platform is served on;
local dev only: `http://127.0.0.1:5000`). Every URL the platform hands out — the
payment return/cancel URLs, the partner snippet, the CORS allow-list — is built
from `TOR_BASE_URL`, which defaults to that domain.
The TOR itself lives on the OneNess chain behind two hosts that serve **one**
ledger:
| role | URL | used for |
|---|---|---|
| node 1 (primary) | `https://www.torblockchain.fun` | every read, every transfer, the TOR/CAD market price |
| node 2 (failover) | `https://torblockchain.onrender.com` | only when node 1 does not answer (same `chain_id` 072316, same head) |
```text
TOR_CHAIN_RPC_URL=https://www.torblockchain.fun
TOR_CHAIN_RPC_FALLBACK_URL=https://torblockchain.onrender.com
```
A failover is never silent: `/healthz`, `GET /api/vault` and the readiness gate
report which host answered (`chain_endpoint`, `chain_failover`).
---
## 1. Get a key
API keys are **bound to your TOR wallet key pair**, so the wallet that created
the key is the wallet the integration belongs to.
```bash
# 1. sign in — this returns your TOR address + public key (session cookie set)
curl -c cookies.txt -X POST http://127.0.0.1:5000/api/session \
-H 'content-type: application/json' \
-d '{"email":"dev@your-app.com"}'
# 2. issue a key pair with your own fees (basis points, 0-2000 = 0-20%)
curl -b cookies.txt -X POST http://127.0.0.1:5000/api/keys \
-H 'content-type: application/json' \
-d '{"name":"My Wallet","fee_bps_buy":150,"fee_bps_sell":150}'
```
The response carries `partner.pk_key` (public, safe in HTML) and
`partner.sk_key` (secret, server-side only, **shown once**), plus a ready-to-paste
`snippet`.
`GET /api/keys` lists the keys issued to the signed-in wallet (secret masked).
| field | meaning |
| --- | --- |
| `pk_key` | `pk_live_…` — identifies your integration in the browser |
| `sk_key` | `sk_live_…` — server-to-server only, never ship it to a browser |
| `wallet_address` | the TOR wallet the key belongs to |
| `fee_bps_buy` / `fee_bps_sell` | your transaction fee, in basis points |
---
## 2. Embed the widget (the BUY TOR button + popup)
Paste this where you want the button. `data-tor-key` is your publishable key.
```html
<script src="https://www.torcoin.fun/embed.js"
data-tor-key="pk_live_your_key"></script>
<button data-tor-buy="2.00">BUY TOR</button>
<button data-tor-sell="1.00">SELL TOR</button>
```
* `data-tor-buy="2.00"` — the buyer pays **2.00 CAD**; the popup shows the live
quote (TOR/CAD), the TOR they receive and the fee before they confirm.
* `data-tor-sell="1.00"` — the seller hands back **1.00 CAD** worth of TOR and
gets the CAD pushed to their payout destination.
* Optional: `data-tor-theme="dark"`, `data-tor-fee-bps-buy` / `data-tor-fee-bps-sell`
(informational — the server always charges the fee stored against your key), and
`data-tor-address="TOR…"` to pre-fill the receiving wallet.
Any element with `data-tor-buy` / `data-tor-sell` opens the popup, so you can style
your own buttons:
```html
<button class="buy-tor" data-tor-buy="5.00">Buy 5 CAD of TOR</button>
```
Programmatic control (no button markup):
```js
// window.TorCoin is installed by embed.js (alias of window.TorCoinWidget)
TorCoin.buy({ amountCad: 2.00 }); // the same popup, BUY pane
TorCoin.sell({ amountTor: 0.198568 }); // the same popup, SELL pane
TorCoin.open({ side: "buy", amountCad: 2.00 });
TorCoin.close(); // close the popup yourself
TorCoin.baseUrl; // "https://www.torcoin.fun"
TorCoin.publicKey; // the data-tor-key this page loaded with
```
Both `sell({ amountCad })` (what the seller gets in CAD) and
`sell({ amountTor })` (what they hand back in TOR) are accepted; the pane shows the
converted side before anything is signed.
The popup is a modal over your page: it creates the order on our side
(`POST /api/order`), opens the hosted checkout for the card / approval step, and
polls `GET /api/order/<id>` until the TOR lands in the wallet — it then shows the
receipt id, the TOR balance and the block reference.
---
## 3. Fees, collected in TOR
A fee is `basis points` of the CAD amount, set per key (0-2000 = 0-20%):
* **Buy** — the fee is taken off the top, so the buyer receives
`(amount_cad - fee_cad) / price` TOR. The TOR-equivalent `fee_tor` is booked
**in TOR** into the platform fee wallet (`TOR_FEE_WALLET`) inside the vault
ledger (`vault.collectFee`, entry kind `COLLECT_FEE`), so it is real TOR that can
be sold later — not a number in a spreadsheet.
* **Sell** — the fee is withheld from the CAD payout; the matching `fee_tor` is
recorded on the same sale (the TOR itself is already in the vault reserve).
The quote always reports both `fee_cad` and `fee_tor`, so your UI can show the fee
in whichever unit your user thinks in.
```json
{ "quote": { "side": "buy", "charge_minor": 200, "charges_cad": "2.00",
"fee_bps": 150, "fee_cad": "0.03", "fee_tor": "0.005957",
"tor_amount": "0.391180", "price_cad": "5.036040" } }
```
---
## 4. Server-to-server calls
Every endpoint accepts your publishable key in the `X-Tor-Key` header (the widget
sends it for you) or `?key=pk_live_…`. The secret key is only needed for key
management and must stay on your server.
### Quote
```bash
curl -X POST http://127.0.0.1:5000/api/quote \
-H 'content-type: application/json' -H 'X-Tor-Key: pk_live_your_key' \
-d '{"side":"buy","amount_cad":2.00}'
```
### Buy (create the order)
```bash
curl -X POST http://127.0.0.1:5000/api/order \
-H 'content-type: application/json' -H 'X-Tor-Key: pk_live_your_key' \
-H 'Idempotency-Key: order-1234' \
-d '{"amount_cad":2.00,"address":"TOR...","email":"buyer@example.com"}'
```
Response: the order (`id`, `tor_amount`, `charge_display`, `status`) and
`payment.approve_url` — send the buyer there, or let the widget do it.
Status values: `payment_pending` -> `funded` (TOR delivered, `receipt_id` + `txid`
set) -> or `payment_failed` / `expired`.
### Sell
```bash
curl -X POST http://127.0.0.1:5000/api/sell \
-H 'content-type: application/json' -H 'X-Tor-Key: pk_live_your_key' \
-d '{"amount_tor":"0.198568","payout_email":"seller@example.com","address":"TOR..."}'
```
The vault takes the TOR into escrow, then the payout provider pushes CAD to the
seller; `status` becomes `payout_settled` with a `provider_ref`.
### Read back
| method | path | purpose |
| --- | --- | --- |
| `GET` | `/api/config` | currency, fees, price, widget settings |
| `GET` | `/api/rate` | live TOR/CAD quote + freshness + source |
| `GET` | `/api/rate/history` | the chart series |
| `POST` | `/api/session` | sign in and get your TOR wallet |
| `GET` | `/api/wallet` | your wallet address + balance |
| `POST` | `/api/quote` | price a buy or a sell |
| `POST` | `/api/order` | create a buy order |
| `GET` | `/api/order/<id>` | order status + on-chain balance |
| `POST` | `/api/order/<id>/redirect` | re-open the hosted approval link |
| `POST` | `/api/sell` | sell TOR back for CAD |
| `GET`/`POST` | `/api/keys` | list / create your API keys |
| `GET` | `/api/vault` | the vault summary (reserve, supply, conservation) |
| `GET` | `/healthz` | platform + rate + vault status (no secrets) |
---
## 5. Errors
Errors are `{"ok": false, "error": {"code": "...", "message": "..."}}` with a
matching HTTP status.
| code | status | meaning |
| --- | --- | --- |
| `SIGN_IN_REQUIRED` | 401 | call `POST /api/session` before `/api/keys` |
| `BAD_FEE` | 400 | fees must be integers between 0 and 2000 bps |
| `AMOUNT_TOO_SMALL` / `AMOUNT_TOO_LARGE` | 400 | outside the order bounds |
| `MISSING_PAYOUT_DESTINATION` | 400 | a sell needs a payout email / card |
| `INSUFFICIENT_BALANCE` | 409 | the wallet holds less TOR than the sale needs |
| `PAYMENT_NOT_COMPLETED` | 402 | the card was not captured |
| `CHAIN_NOT_PRODUCTION` | 409 | the vault is not bound to a production chain — real money is refused |
| `INSUFFICIENT_RESERVE` | 400 | the vault reserve cannot cover the TOR (or the fee) |
`CHAIN_NOT_PRODUCTION` is deliberate: the platform will not take a real card
payment while the TOR it would deliver exists only in a mock ledger. The vault
reports `torchain` when it is bound to the running OneNess node, and that mode is
one of `server.config.PRODUCTION_CHAIN_MODES` — the same set the readiness gate
reads, so a genuine node is never refused and a simulator is never accepted.
Check `GET /healthz` -> `vault_up` and the readiness gate
(`python tools/live_readiness.py`).
One caveat the gate keeps flagging until the address bridge exists: the node pays
only `TOR1<base32>.onion`, so a credit to a storefront wallet
(`TOR<40 hex><checksum>`) is a **vault-ledger balance backed by the reserve**, not
an on-chain transfer. Each such order is recorded in the platform log as
`wallet.off_chain`, with that sentence attached.
---
## 6. Verify a delivery yourself
Nothing in this API asks you to trust a screen. After a buy:
```bash
curl -s http://127.0.0.1:7788/health # chain mode, height, price
curl -s -X POST http://127.0.0.1:7788/rpc \
-H 'content-type: application/json' \
-H "x-tor-vault-token: $TOR_VAULT_RPC_TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"vault.balance","params":{"address":"TOR..."}}'
```
* `vault.verifyReceipt` re-hashes the receipt.
* `vault.verifyLedger` re-walks the whole hash-chained ledger and proves
`reserve + balances + escrow == minted`.
* The order's `txid` is the node's own transaction id when the vault is bound to
the OneNess chain (`TOR_CHAIN_KIND=torchain`).
Raw file: docs/DEV_API.md in the project root.