Payments release against a condition the protocol can verify, not against the other side’s word. Assets lock on-chain until it clears. You never hold a key, and never stand as the counterparty.
Plain HTTP. Predictable JSON. One surface covering accounts and on-chain allowances, an order book whose sell side is really locked, escrowed settlement tickets, and withdrawals you sign yourself.
remedy — a value you can resubmit as-isphase is computed per observer — no client-side state machine// 1 — take a maker's offer const r = await fetch( 'http://localhost:8080/api/v1/offers/p1/take', { method: 'POST', headers: { 'X-Atara-User': 'demo' }, body: JSON.stringify({ amount: '73100', amount_kind: 'fiat' }) } ); // 2 — read the ticket const order = await r.json(); { ref: 'ATR-8F42C1', state: 'match', phase: 'lock', actor: 'auto', amount: { amount: '10000', asset: 'USDT' }, seconds_left: 20 }
http://localhost:8080/api/v1
Every path on this page appends to this base.
X-Atara-User: <handle | address>
Mock auth during internal testing; omitted falls back to demo. Real auth lands before public release.
X-Atara-Confirmation: <token>
Only on calls that move or commit funds. Minted from POST /passkey/assert. 120 s, single use, bound to that exact amount and peer.
53 endpoints across 4 domains. Every one of them settles the same way: escrow on-chain, release on evidence.
Digital assets only — GET /wallet never lists a fiat balance.
The fiat leg settles bank-to-bank between the two parties.
Conditional transfers run a separate state machine and sit outside the v1 contract.
After the fiat leg, the payer submits a bank receipt and the ticket sits in s3v until the party who received the fiat verifies it. The submitter can't verify their own receipt.
lock_tx is returnedNOT_YOUR_CALLdisputed, funds stay lockedmatch timeout is cancelled, not expired — fill rate untouched{
"ref": "ATR-8F42C1",
"state": "s3v", "terminal": "",
// computed for whoever is asking
"phase": "verify", "actor": "you",
"amount": { "amount": "10000", "asset": "USDT" },
"otc": {
"side": "sell", "unit_price": "7.31",
"fiat_code": "CNY", "fiat_amount": "73100",
"receipt_ref": "rcpt_7f2a"
},
"escrow": {
"contract": "TEscrow...", "network": "TRON",
"tx_hash": "0x...",
"confirmations": 12, "required": 12
},
"state_deadline": "2026-09-02T07:44:11Z",
"seconds_left": 6
}
Every outflow needs an explicit confirmation — no small-amount exemption. A token is a hash of this operation: change the amount or the counterparty and it no longer applies, and is voided on the spot.
signature — moves money. Sell offers, withdrawals, allowancescommit — promises only. Buy offers, taking a trade, declaring a transferSIGNATURE_REQUIRED// request — bind the token to this exact payment { "scope": "withdraw", "parts": ["payee-id", "USDT", "250.5"], "grade": "signature" } // response { "confirmation": "a1b2c3…", "expires_at": "2026-09-02T05:44:05Z", "grade": "signature", "header": "X-Atara-Confirmation" } // then pass it on the call that moves funds curl -X POST .../withdrawals \ -H "X-Atara-Confirmation: a1b2c3…"
Amounts are decimal strings in the major unit. Times are RFC 3339 UTC. Use state_deadline for logic, seconds_left only for countdowns.
One envelope for every failure. Branch on code, never on message. When the protocol can already tell a request will fail downstream, it stops it here and attaches a remedy.
{
"error": {
"code": "BELOW_MIN_LOT",
"field": "amount",
"message": "3000 CNY is below the maker's smallest lot",
"remedy": {
"action": "set_amount",
"value": "5000",
"label": "Use the smallest lot — 5000 CNY"
}
}
}
CONFIRMATION_INVALID— expired, used, or bound to a different paymentSIGNATURE_REQUIRED— needs a signature token, you sent commitNOT_YOUR_CALL— receipt submitter tried to verify their own receiptBELOW_MIN_LOT— under the maker's smallest lot · carries a remedyABOVE_AVAILABLE_QTY— over remaining, or lost a concurrent take · carries a remedyNO_MATCH_WITH_COUNTERPARTY— that maker can't fill this, and we don't fall backCAP_ABOVE_WINDOW— per-payment cap exceeds the window capKYC_NOT_APPROVED— listing config submitted before identity approvalAtara is a settlement protocol, not a venue and not a custodian. Two invariants shape everything above.
The fiat leg settles bank-to-bank between the parties. GET /wallet never lists a fiat asset, and a fiat withdrawal returns ASSET_REQUIRED.
custody is always "self". External wallets grant allowances by on-chain approve; we can't sign for them, so we watch the chain instead.
Not a matching engine. Windows are measured in minutes and hours, and the fiat leg runs at bank speed.
POST /orders and its evidence and confirm routes exist and run, but sit outside the v1 contract. Specified with v2.
POST /offers doesn't check maker approvalusd_rate is a compile-time constantATARA_DEMO_TIMING switches between two sets. Demo is the default: a 4-hour payment window runs 24 seconds, a 72-hour dispute window runs 15. Don't build countdowns against the demo numbers.