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_address you 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).