Invoices API · Geat.io

Invoices

Hosted-invoice endpoints. All three require the X-API-Key header - see README.md.

Base URL: https://api.geat.io (pending final deploy confirmation).

MethodPath
POST/v1/invoices
GET/v1/invoices/{invoice_uuid}
POST/v1/invoices/{invoice_uuid}/reconcile

A path parameter that is not a well-formed UUID answers 404 NOT_FOUND before the handler runs.


POST /v1/invoices

Create an invoice and get back its hosted payment-page URL.

Request body

FieldTypeRequiredRules
profile_uuidstring (UUID)yesA non-deleted payment profile owned by the key's account.
invoice_idstringyesYour own reference. Max 255 chars, pattern ^[A-Za-z0-9._-]+$. Not unique - the same value may be reused.
pricenumber or numeric stringyesGreater than 0, at most 1,000,000, finite.
success_urlstring (URL)yeshttp/https with a host. Where the payer is sent after a successful payment.
callback_urlstring (URL)yesWebhook target. Must resolve to a public address - private, loopback and link-local destinations are rejected.
failure_urlstring (URL)yeshttp/https with a host.
redirectbooleanno (default false)When true the endpoint answers 302 to the payment page instead of a JSON body.
customer_emailstringnoValidated email; used for the payer receipt.
currencystringnoFiat symbol, upper-cased. Must be an active currency. Defaults to the profile's currency.

Example request

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",
    "customer_email": "payer@example.com",
    "success_url": "https://shop.example.com/thanks",
    "failure_url": "https://shop.example.com/oops",
    "callback_url": "https://shop.example.com/hooks/geatio",
    "redirect": false
  }'

Response - 201 Created

{
  "success": true,
  "data": {
    "uuid": "0f0f6a6c-7e2e-4a4e-9f4b-9a6d0f3a1b22",
    "invoice_id": "ORDER-1042",
    "price": "49.00",
    "currency": "USD",
    "currency_name": "United States Dollar",
    "customer_email": "payer@example.com",
    "status": "created",
    "redirect": false,
    "created_at": "2026-10-14T09:12:04.118233+00:00",
    "invoice_url": "https://pay.geat.io/0f0f6a6c-7e2e-4a4e-9f4b-9a6d0f3a1b22"
  }
}

invoice_url is built from the server's configured invoice base URL plus the invoice UUID. Send the payer there; everything after that is handled by the payer endpoints in Payer endpoints.

With redirect: true the response is instead a 302 whose Location is that same URL.

Errors

StatusCodeWhen
400MISSING_FIELDSprofile_uuid, invoice_id, or any of the three URLs is absent/blank.
400INVALID_INVOICE_IDLonger than 255 chars, or contains characters outside A-Za-z0-9._-.
400INVALID_PRICENot a number, not positive, not finite, or above 1,000,000.
400INVALID_URLsuccess_url/failure_url is not a valid http(s) URL, or callback_url is not a valid public URL.
400INVALID_EMAILcustomer_email is not a valid address.
400INVALID_CURRENCYThe requested currency is not an active fiat currency.
400INVALID_PROFILE_TYPEThe profile exists but is not a payment profile.
400NO_COINSThe profile has no accepted coins configured.
403PROFILE_SUSPENDEDThe profile is suspended.
403PROFILE_INACTIVEThe profile is not active.
404NOT_FOUNDNo such profile under this account (also returned for a malformed profile_uuid).
503RATE_UNAVAILABLEThe price could not be converted to USD - exchange rates are temporarily unavailable.

GET /v1/invoices/{invoice_uuid}

Read one invoice. Scoped to the key's account: another merchant's invoice answers 404.

Response - 200 OK

Identical body to the create response (the model dict plus invoice_url), with one deliberate difference: the internal status expired_partial is reported as expired. The external contract exposes only the status set the webhooks promise; the partial-receipt distinction stays internal for the payer page.

Errors

StatusCodeWhen
404NOT_FOUNDUnknown UUID, malformed UUID, soft-deleted invoice, or an invoice belonging to another account.

Invoice status lifecycle

created ──> waiting ──> partial ──> pending ──> completed
   │           │           │
   │           │           └──> expired_partial ──(API: reported as expired)
   │           └──> expired
   └──> failed
StatusMeaning
createdInvoice exists; the payer has not picked a coin yet.
waitingA coin is locked and an address is issued; waiting for funds.
partialSome funds arrived but less than the expected amount.
pendingEnough has arrived; waiting for the coin's confirmation threshold.
completedConfirmed and credited. Terminal. Emits invoice.completed.
expiredThe payment window closed with nothing credited. Emits invoice.expired.
expired_partialInternal only: expired after a partial receipt. The v1 API reports this as expired.
failedCancelled or otherwise dead. Terminal.

The payer-facing invoice-session status (returned by the pay endpoints) is a separate, narrower set - see Payer endpoints.


POST /v1/invoices/{invoice_uuid}/reconcile

Force a re-check of a paid-but-stuck invoice. The handler pulls the confirmed transaction universe for the invoice's watched address directly from the watch service and completes the invoice if the pulled total now covers the expected amount.

Non-custodial and idempotent: it never moves funds, only mirrors on-chain truth into the invoice ledger, and a concurrent scheduler completion makes one of the two callers a no-op rather than double-crediting.

Request

No body. Path parameter only.

Response - 200 OK

{
  "success": true,
  "data": {
    "status": "completed",
    "settled": true,
    "reconciled": true,
    "confirmed_amount": "0.00412000",
    "expected_amount": "0.00412000"
  }
}
FieldTypeNotes
statusstringThe invoice status after the call. expired_partial is reported as expired, as everywhere else on v1.
settledbooleantrue once the invoice is completed.
reconciledbooleantrue only when this call performed the completion. A call that found the invoice already completed, or that lost the race to a concurrent completion, returns false.
confirmed_amountstringFixed-point total pulled from the watch service. Absent on the already-completed short-circuit, which does no pull.
expected_amountstringFixed-point amount the invoice is waiting for. Absent on the same short-circuit.

When the invoice was already complete the response also carries "message": "Invoice was already completed.".

When the confirmed total is still short, the call is not an error: it answers 200 with settled: false, reconciled: false and both amounts, so you can show the shortfall.

Errors

StatusCodeWhen
404NOT_FOUNDUnknown/malformed UUID, soft-deleted, or another account's invoice.
409NOT_RECONCILABLENo payment session, no coin selected, the invoice was cancelled, or there is no payable watched address for the selected coin.
502WATCH_UNAVAILABLEThe watch service could not be reached, or the invoice has no active watch to verify against.