Geat.io v1 API
Reference for the public /v1 HTTP surface. Every field, header, status
code and error code on these pages is copied from the code; each section
carries an HTML comment naming the source file and lines.
Non-custodial: the API never moves funds. Payment addresses are derived from the merchant's own xpub, payments land directly in the merchant's wallet, and the API only detects and bookkeeps.
Pages
| Page | Contents |
|---|---|
| Invoices | Hosted-invoice endpoints (/v1/invoices) |
| Addresses | Bare address requests + rates (/v1/addresses) |
| Payer endpoints | Payer-page endpoints (/v1/pay) - no API key |
| Webhooks | The four merchant webhook events, signing, retries |
| Error codes | Every error code the v1 surface emits |
Quickstart
The whole hosted-invoice integration is five steps.
1. Create the invoice with your API key. The merchant profile_uuid
and the three URLs come from your own system.
curl -X POST https://api.geat.io/v1/invoices \
-H "X-API-Key: $GEATIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profile_uuid": "2c9a1f44-5d3b-4f0e-9a21-7c6b8e2d4f10",
"invoice_id": "ORDER-1042",
"price": 49.00,
"currency": "USD",
"success_url": "https://shop.example.com/thanks",
"failure_url": "https://shop.example.com/oops",
"callback_url": "https://shop.example.com/hooks/geatio"
}'
2. Send the payer to data.invoice_url from that response. The
hosted page handles coin choice, the address, the QR code and the
countdown. Nothing else on your side is needed until the payment
resolves. (Pass "redirect": true and the create call answers 302 to
that URL directly, if you would rather redirect than read the body.)
3. Receive the webhook at your callback_url: invoice.completed
when the payment confirms, invoice.expired when the window closes.
4. Verify the signature before trusting the body - HMAC-SHA256 over
{X-Timestamp}.{raw body}, compared in constant time against
X-Signature. See webhooks.md.
5. Confirm against the API before releasing goods:
GET /v1/invoices/{invoice_uuid}. The webhook is a nudge; this endpoint
is the authority.
Bare receive addresses, with no hosted page, follow the same shape using
POST /v1/addresses and the address.paid / address.payment webhooks
- ·see Addresses.
Two things integrators assume that are not true here
- ·There are no list endpoints. v1 has no "list my invoices" or "list
my addresses" collection route - every read is by UUID. Store the
uuidwe return against your own order record at creation time; it is the only handle you get. Browsing and exporting live in the dashboard. - ·Only
POST /v1/addressesis idempotent. It honours an optionalIdempotency-Keyheader.POST /v1/invoicesdoes not: there is no idempotency header andinvoice_idis not unique, so retrying a create that already succeeded makes a second invoice. Treat a timed-out create as unknown and reconcile against your own records rather than blindly retrying.
Base URL
https://api.geat.io
This is the live production endpoint. Development installs serve the same paths from their own origin.
Authentication
Merchant endpoints authenticate with an API key sent in the X-API-Key
request header:
X-API-Key: <your api key>
Keys are issued per merchant account in the dashboard. They are stored
hashed, matched against non-revoked rows only, and their last_used_at
is refreshed on every accepted call.
The authenticator rejects, in this order:
| Condition | Status | Code |
|---|---|---|
| Missing, unknown, or revoked key | 401 | UNAUTHORIZED |
| Owner account not active | 403 | ACCOUNT_INACTIVE |
| Owner email not verified | 403 | EMAIL_NOT_VERIFIED |
| Owner account suspended | 403 | ACCOUNT_SUSPENDED |
The payer endpoints in Payer endpoints take no API key - they are the public invoice page's own backend and are documented here only for completeness.
Response envelope
Success:
{
"success": true,
"data": { },
"message": "..."
}
data is omitted when the endpoint returns nothing, and message is
omitted when there is no human-readable note. Both can be present.
Error:
{
"success": false,
"error": "Human-readable message.",
"code": "MACHINE_CODE"
}
code is omitted only if the handler raised the error without one; every
v1 error documented in Error codes carries one. Branch on
code, never on error - the prose may be reworded.
Rate limits
Merchant /v1 endpoints (invoices + addresses) use a per-request dynamic
limit:
| Caller | Limit |
|---|---|
| Authenticated (valid API key) | 10 per second; 100 per minute; 10000 per day |
| Unauthenticated / invalid key | 5 per minute |
The bucket key is the authenticated key's UUID (apikey:<uuid>), or the
caller's IP (ip:<address>) when no valid key was presented. Callers
whose upstream address cannot be parsed as an IP share one ip:unknown
bucket.
Other surfaces, for context:
- ·Payer
/v1/payendpoints: blanket 5 per second, applied both to the blueprint and per route.GET /v1/pay/{invoice_uuid}/reviewcarries its own 10 per second. - ·Dashboard (cookie-authenticated, non-
/v1) blueprints: blanket 10 per second floor, with tighter per-route limits where decorated. - ·Deliberately unlimited: the watch-service webhook receiver
(HMAC-signed machine traffic that must never be throttled - a 429 there
drops a payment credit) and the bare
GET /healthliveness probe (a 429 on a probe reads as "down").GET /health/detailis not exempt and keeps its own 30 per minute.
A throttled request answers 429. No custom 429 handler is registered, so that response is the rate limiter's default and does not use the success/error envelope above; treat the status code as the contract.
Money and amounts
- ·Fiat prices are strings with 2 decimal places.
- ·Crypto amounts are fixed-point decimal strings, never scientific notation. Invoice-page amounts are 8 decimal places truncated (never rounded up); ledger and webhook amounts are 18 decimal places.
- ·
fee_amountis always0on both the API and webhook surfaces. The product charges prepaid credits (1 credit per payment attempt), not a percentage of the payment, so no fee is ever deducted from a merchant's funds. The field exists for integrators who already parse it.
Detection depth
Every derived address is registered with the watch service, which keeps it in a hot tier (live detection) for a default of 24 hours, then demotes it to a cold tail that is still reconciled for a default of 120 days. A payment that lands late is therefore still detected and credited inside the cold window; after it, nothing is lost on-chain (the funds are already in the merchant's own wallet) but the bookkeeping entry has to be made by hand.