# O402
[![Crates.io][crates-badge]][crates-url]
[![Docs.rs][docs-badge]][docs-url]
[![CI][ci-badge]][ci-url]
[![License][license-badge]][license-url]
[![Rust][rust-badge]][rust-url]
[crates-badge]: https://img.shields.io/crates/v/o402.svg
[crates-url]: https://crates.io/crates/o402
[docs-badge]: https://img.shields.io/docsrs/o402.svg
[docs-url]: https://docs.rs/o402
[ci-badge]: https://github.com/qntx/o402/actions/workflows/ci.yml/badge.svg
[ci-url]: https://github.com/qntx/o402/actions/workflows/ci.yml
[license-badge]: https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg
[license-url]: LICENSE-MIT
[rust-badge]: https://img.shields.io/badge/rust-1.95%20%2B%20edition%202024-orange.svg
[rust-url]: https://doc.rust-lang.org/edition-guide/
**OpenAI-compatible HTTP gateway, paid with x402.**
One binary, one TOML file. Clients speak the OpenAI HTTP API (`base_url`; dummy `Authorization` is stripped). Payment is x402 v2, not an API key. TLS terminates at a reverse proxy. o402 holds **no seller private keys** — only pay-to addresses and upstream LLM keys. Run a remote [`facilitator`](https://github.com/qntx/facilitator) for verify/settle.
Requires **Rust 1.95**. Default features: `evm` + `svm`. Image stays default features (no `full`). No OpenTelemetry.
## Install
**macOS / Linux**
```sh
**Windows** (PowerShell)
```powershell
Or via Cargo:
```bash
cargo install o402
# from this tree: cargo install --path crates/o402
```
## Run
```bash
o402 init --path gateway.toml
# edit gateway.toml; for payment copy deploy/gateway.example.toml
o402 serve --config gateway.toml
```
`serve --config` is required. Bind comes from `server.bind` (default `0.0.0.0:8080` for containers). `o402 init` writes the unpaid example. There is no `sync` command.
CLI: `o402 init [--path gateway.toml]` and `o402 serve --config PATH`.
## HTTP
| `GET` | `/health` | unpaid — liveness `{ "status": "ok", "service": "o402", "version": "…" }` |
| `GET` | `/ready` | unpaid — 200 when payment is off, or when facilitator `GET /supported` succeeds; 503 otherwise |
| `GET` | `/v1/models` and `/openai/v1/models` | unpaid — proxied to Bifrost |
| `GET` | `/v1/pricing` | unpaid — o402 rates |
| `POST` | `/v1/chat/completions`, `/v1/completions`, `/v1/embeddings`, `/v1/responses` (and `/openai` prefixed) | paid **upto** (token `usage`) |
| other exported HTTP | `/v1/*`, `/openai/*`, `/anthropic/*`, `/genai/*`, `/bedrock/*`, `/cohere/*`, `/litellm/*`, `/langchain/*`, `/pydanticai/*` | paid **exact** (flat `price`) |
| `GET` | `/v1/realtime`, `Upgrade: websocket`, `/api/*`, `/` | 404 — cannot bill |
Unknown models are forwarded. Catalog `[[models]]` is optional (rate/upstream overrides only).
Point an OpenAI SDK at `server.base_url` (`/v1/...`) or `server.base_url/openai` (Bifrost drop-in). Dummy `Authorization` is stripped; the gateway injects the upstream key.
## Config
TOML with `deny_unknown_fields`. After parse, a string is replaced **only when the entire value** is `$VAR` or `${VAR}`. `"Bearer $TOKEN"` is not expanded. Auth header values are the full header (`$FACILITATOR_AUTH_HEADER` = `Bearer …`). `dotenvy` loads `.env` from the process cwd at start.
Examples: [`deploy/gateway.example.toml`](deploy/gateway.example.toml) (paid EVM+SVM), [`deploy/gateway.full.example.toml`](deploy/gateway.full.example.toml) (production extras; not used by Docker), and [`deploy/gateway.unpaid.example.toml`](deploy/gateway.unpaid.example.toml) (`payment.enabled = false`). Env names: [`deploy/o402.env.example`](deploy/o402.env.example).
`server.base_url` is required when payment is on (r402 never derives origin from `Host`). Loopback `http` is allowed; public URLs must be `https`.
### Payment
Cargo features match the facilitator production matrix. Default binary is EVM+SVM. Rebuild with extras (and a facilitator built with the same extras) to advertise those namespaces.
```bash
cargo install --path crates/o402 --locked --features full
```
| `evm` (default) | `eip155` | `exact`, `upto` | `usdc` |
| `svm` (default) | `solana` | `exact` | `usdc` |
| `near` | `near` | `exact` | `usdc` |
| `xrpl` | `xrpl` | `exact` | `xrp`, `rlusd` |
| `hedera` | `hedera` | `exact` | `usdc`, `hbar` |
| `avm` | `algorand` | `exact` | `usdc` |
| `aptos` | `aptos` | `exact` | `usdc` |
| `keeta` | `keeta` | `exact` | `usdc` |
| `tvm` | `tvm` | `exact` | `usdt` |
| `stellar` | `stellar` | `exact` | `usdc` |
| `concordium` | `ccd` | `exact` | `usdr`, `ccd` |
| `experimental-tron` | `tron` | `exact` | `usdt` |
`full` = production extras minus `experimental-tron`. No `casper`. SVM `upto` stays out (voucher signer / seller key). o402 holds no seller keys.
TOML `price` values are **asset units**, not a USD peg. `shared_decimals` is a scale check: mixing Stellar USDC (7), HBAR (8), or RLUSD (15) with USDC-6 fails startup. Same-decimal mixes (USDC-6 + XRP-6) load; treating those rates as dollars is economically inconsistent.
`[payment.facilitator] url` is the remote facilitator (`POST /verify`, `POST /settle`, `GET /supported`). Public `facilitator.qntx.org` requires path-keyed auth (entire header value):
```toml
[payment.facilitator]
url = "https://facilitator.qntx.org"
timeout_secs = 60
[payment.facilitator.auth.verify]
Authorization = "$FACILITATOR_AUTH_HEADER"
```
`[server.cors] origins` must list every SPA origin (`https://chat.qntx.org`, Vite `http://localhost:5173`). Empty origins = no CORS layer; the browser then hides `Payment-Required` / `Payment-Response`.
### Metrics
Default: off. Optional `observability.metrics_bind = "127.0.0.1:9090"` serves `GET /metrics` on **that** bind only. It is never mounted on the payment port (`:8080`). No OpenTelemetry.
## Docker Compose
Gateway HTTP `8080` is loopback/internal. Caddy publishes `80`/`443` and terminates TLS. Reverse proxy uses `flush_interval -1` so SSE is not buffered.
```bash
cp deploy/gateway.example.toml deploy/gateway.toml
cp deploy/o402.env.example deploy/o402.env
cp deploy/bifrost-config.example.json deploy/bifrost-config.json
# fill o402.env (pay-to, OPENAI_API_KEY, FACILITATOR_AUTH_HEADER); set server.base_url / Caddyfile hostname
docker compose -f deploy/docker-compose.yml up --build -d
```
Bifrost admin UI: `http://127.0.0.1:8081` (loopback). Public traffic hits Caddy → o402 → Bifrost.
Image: pin `ghcr.io/qntx/o402:0.1.4` (tag `v*` publishes GHCR; PRs only build). Default features only (`evm`+`svm`). `HEALTHCHECK` is `GET /health`. Official `caddy:2` — no rate-limit plugin. SSE requires `flush_interval -1` in the Caddyfile. linux/amd64 only.
```bash
docker build -t o402 .
docker run --rm -p 127.0.0.1:8080:8080 --env-file deploy/o402.env \
-v "$PWD/deploy/gateway.toml:/app/gateway.toml:ro" o402
```
## systemd
Host + reverse proxy: set `server.bind = "127.0.0.1:8080"` and terminate TLS at Caddy (change `deploy/Caddyfile` `o402:8080` to `127.0.0.1:8080`). The container keeps `0.0.0.0:8080` so Compose Caddy can reach it on the Docker network.
```bash
sudo useradd --system --home /etc/o402 --shell /usr/sbin/nologin o402
sudo mkdir -p /etc/o402
sudo cp gateway.toml /etc/o402/gateway.toml
sudo cp o402.env /etc/o402/o402.env
sudo chown -R o402:o402 /etc/o402
sudo chmod 640 /etc/o402/o402.env
sudo install -m 755 target/release/o402 /usr/bin/o402
sudo install -m 644 deploy/o402.service /etc/systemd/system/o402.service
sudo systemctl daemon-reload
sudo systemctl enable --now o402
```
Unit: `EnvironmentFile=-/etc/o402/o402.env`, `ExecStart=/usr/bin/o402 serve --config /etc/o402/gateway.toml`, `Restart=on-failure`. `TimeoutStopSec=65s` covers HTTP drain plus inflight settle (`2 × server.shutdown_timeout_secs` default 30, plus slack).
## Crates
See **[`crates/README.md`](crates/README.md)** for the crate table.
## Contributing
See **[`CONTRIBUTING.md`](CONTRIBUTING.md)** for development setup, PR expectations, and the release checklist.
## Security
This library has **not** been independently audited. Use at your own risk.
See **[`SECURITY.md`](SECURITY.md)** for scope and reporting.
## License
Licensed under either of:
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or <https://www.apache.org/licenses/LICENSE-2.0>)
- MIT License ([LICENSE-MIT](LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project shall be dual-licensed as above, without any additional terms or conditions.
---
<div align="center">
A **[QuantX](https://qntx.org)** open-source project.
<a href="https://qntx.org"><img alt="QuantX" width="369" src="https://raw.githubusercontent.com/qntx/.github/main/profile/qntx.svg" /></a>
Code is law. We write both.
</div>