Trading — buy & sell stablecoins
StarPay terminates the exchange as principal (ADR-11): the GHS leg settles over StarPay's own rails and the crypto leg from StarPay's own USDT/USDC inventory. You quote, then execute against that quote before it expires.
Before you start
- Get API access. Trading is gated by the service ladder — request access in the dashboard, test on sandbox, then go live. See Authentication → Service access.
- Fund your GHS wallet. A buy draws cedis from your GHS wallet balance, which is what makes the 45-second quote lock viable. Top it up (and withdraw the proceeds of a sell) as described in Wallet funding.
1. Get a quote
POST /api/v1/trades/quote prices a side off the composite benchmark plus the
configured spread (ADR-7). The benchmark is
benchmark(ASSET/GHS) = usd_price(ASSET) × fx(USD/GHS) — the USD leg follows the
live market (≤ 90s stale), the USD/GHS leg is an admin-managed rate (≤ 24h stale).
If either leg is stale, quoting halts and you get rate_expired.
Provide exactly one of fiat_amount (gross GHS notional, minor units) or
asset_amount (native asset minor units).
curl -X POST https://api.starpay.example/api/v1/trades/quote \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"side": "buy", "asset": "USDT", "network": "TRC20", "fiat_amount": 50000}'
The response carries reference, applied_rate, asset_amount,
net_fiat_amount, and expires_at (default 45s TTL). After it expires the quote
can no longer be executed — request a fresh one (rate_expired).
2. Execute
POST /api/v1/trades executes a quote. It requires the trades:write scope, an
HMAC signature (X-Starpay-Timestamp / X-Starpay-Signature, see
Authentication), and an Idempotency-Key.
- Buy — funds from your GHS wallet balance and settles immediately, delivering
the asset to the
delivery_addressyou supply (Cobo-backed, ADR-6). Too little balance →insufficient_funds. - Sell — the order is created awaiting your on-chain deposit; send the asset to the address shown, and the GHS lands in your wallet once it confirms.
curl -X POST https://api.starpay.example/api/v1/trades \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: 7f9a...d2" \
-H "X-Starpay-Timestamp: 1718563200" \
-H "X-Starpay-Signature: <hmac>" \
-H "Content-Type: application/json" \
-d '{"quote_reference": "qt_...", "delivery_address": "T..."}'
GET /api/v1/trades/{reference} returns an order; GET /api/v1/trades lists them
(cursor-paginated — follow meta.next_cursor). Both need the read scope. The
stablecoin legs are Cobo-backed — responses never include Cobo ids or raw custody
payloads.
Limits & controls
Each merchant has per-trade, daily, and monthly limits plus a treasury-exposure
cap that tightens as inventory depletes. A breach returns limit_exceeded (at
quote or execute time). Trades are also compliance-screened: a block returns
compliance_block; a trade parked for manual review returns compliance_block
with error.details.order_reference and status: "under_review" (also delivered
as a compliance.review_required webhook) so you can track it.
Errors
| Code | When |
|---|---|
rate_expired |
the quote (or a benchmark leg) expired, or the market moved beyond tolerance |
insufficient_funds |
not enough GHS wallet balance to fund a buy, or inventory to fill it |
limit_exceeded |
a per-trade / daily / monthly limit or the exposure cap would be breached |
compliance_block |
screening blocked the trade, parked it for review, or the service is not enabled |
validation_error |
bad side/amount, or a quote already consumed |
Webhooks
Quotes and orders emit lifecycle events: quote.created, quote.expired,
trade.created, trade.pending_payment, trade.pending_confirmation,
trade.completed, trade.settled, trade.failed, trade.reversed,
compliance.review_required, and (for sells) deposit.confirmed. Each carries the
order/quote snapshot. Verify them like any other StarPay webhook (see
Webhooks).