API reference
Base URL in local development: http://localhost:8080
The API is read-only. It holds no keys and signs nothing. Every state change is a transaction signed in the user’s wallet and submitted straight to Soroban RPC.
Amounts
Every amount is i128 on chain and is returned as a decimal string in the smallest unit — 7 decimal places for USDC.
"30000000" -> $3.00
Do not parse these into JavaScript numbers. Above 2^53 you silently lose precision. Use BigInt.
Endpoints that return an amount also return a …Display variant already formatted.
GET /health
Liveness. Always {"status":"ok"} if the process is up.
GET /ready
Readiness, including how far the indexer trails the chain.
{
"status": "ready",
"checks": {
"database": "ok",
"rpc": "ok",
"latestLedger": 4466016,
"indexedLedger": 4466014,
"lagLedgers": 2,
"indexer": "ok",
"network": "testnet"
}
}
Returns 503 when the database is unreachable, RPC is unreachable, or the indexer is more than 120 ledgers behind (roughly 10 minutes). Use this for your health check, not /health.
GET /stats
{
"providers": 1,
"activeProviders": 1,
"vouchers": 2,
"settledVouchers": 1,
"settledValue": "30000000",
"receipts": 1
}
GET /providers
| Parameter | Type | Notes |
|---|---|---|
status | Pending | Active | Suspended | |
country | string(2) | ISO 3166-1 alpha-2 |
q | string | Name search |
limit | int | 1–100, default 50 |
offset | int | default 0 |
{
"total": 1,
"limit": 50,
"offset": 0,
"providers": [
{
"address": "GDOOCNK2HL6TB2Y7FDYNNMG4GTM2PNPY4XCTWG2INFPYYVA66FCPZKBK",
"name": "Ikeja General Clinic",
"country": "NG",
"status": "Active",
"registeredAt": "2026-09-02T12:36:37.000Z",
"services": [
{
"code": 101,
"label": "Outpatient consult",
"price": "30000000",
"priceDisplay": "3.0000000"
}
]
}
]
}
GET /providers/:address
One provider with its full catalogue, including inactive services. 404 if unknown.
GET /vouchers
Requires at least one filter. An unfiltered dump is not a useful endpoint and invites scraping.
| Parameter | Type |
|---|---|
funder | Stellar address |
provider | Stellar address |
beneficiaryRef | 64 hex characters |
status | Funded | Claimed | Attested | Settled | Disputed | Refunded |
Without one, returns 400:
{
"error": "filter_required",
"message": "Provide at least one of: funder, provider, beneficiaryRef, status"
}
Each voucher carries derived fields so clients do not re-implement the state machine:
serviceLabel— the clinic’s catalogue name forserviceCode, ornullif the service was never indexedrefundableAt— whenrefundstarts to succeed: the expiry for aFundedvoucher, expiry plus the seven-day claim grace for aClaimedone, otherwisenulldisputeReason— the funder’sreason_codefor a disputed voucher (see the lifecycle), otherwisenullisSettleable— attested and past the dispute deadlineisRefundable—refundableAthas passed
{
"id": "1",
"funder": "GDUPJTF3PNSYJ73WWLNTYLG6UXN7HHBKCGFEEMDFMWKWGR3UMQT3JG45",
"beneficiaryRef": "72676a6f4fff92b09ab1c6368672b05112062f683014d07c9518d4141d094745",
"provider": { "address": "GDOO…ZKBK", "name": "Ikeja General Clinic", "country": "NG" },
"serviceCode": 101,
"serviceLabel": "Outpatient consult",
"amount": "30000000",
"amountDisplay": "3.0000000",
"status": "Settled",
"createdAt": "2026-09-02T12:37:17.000Z",
"expiresAt": "2026-10-02T12:37:12.000Z",
"claimedAt": "2026-09-02T12:37:27.000Z",
"attestedAt": "2026-09-02T12:37:32.000Z",
"disputeDeadline": "2026-09-02T12:38:32.000Z",
"disputeReason": null,
"settledNet": "29700000",
"settledFee": "300000",
"refundableAt": null,
"isSettleable": false,
"isRefundable": false
}
GET /vouchers/:id
One voucher plus its receipt (null unless settled). 404 if unknown.
GET /receipts?beneficiaryRef=
A patient’s settled care history. beneficiaryRef is required and must be exactly 64 hex characters.
Because that value is an HMAC under a key held by the patient, this endpoint cannot be walked to enumerate people.
{
"total": 1,
"totalSpend": "30000000",
"totalSpendDisplay": "3.0000000",
"receipts": [
{
"voucherId": "1",
"providerAddress": "GDOO…ZKBK",
"serviceCode": 101,
"serviceLabel": "Outpatient consult",
"amount": "30000000",
"amountDisplay": "3.0000000",
"settledAt": "2026-09-02T12:38:42.000Z"
}
]
}
Rate limiting
120 requests per minute per IP. Exceeding it returns 429.
Errors
| Status | Meaning |
|---|---|
| 400 | Invalid or missing query parameters |
| 404 | Not found |
| 429 | Rate limited |
| 503 | Degraded — check /ready |