API
Authentication, usage ingestion, and where to find the full endpoint reference.
Everything the dashboard does is available over a clean REST API at
https://app.refundhalt.com. The complete, always-current endpoint
reference lives at
app.refundhalt.com/docs (OpenAPI).
Authentication
Create an API key in Dashboard → API keys and send it as a Bearer token.
API keys work on every /v1 endpoint, exactly like dashboard sessions:
curl https://app.refundhalt.com/v1/apps \
-H "Authorization: Bearer rh_live_..."Stream usage
Refund responses carry much stronger evidence when they include the buyer's real usage. Stream events as they happen — one POST, batched:
curl -X POST https://app.refundhalt.com/v1/apps/$APP_ID/ingest/usage \
-H "Authorization: Bearer rh_live_..." \
-H "Content-Type: application/json" \
-d '{
"events": [{
"external_user_id": "user_8231",
"event_type": "scan",
"quantity": 3,
"ip_address": "203.0.113.9",
"country": "US"
}]
}'Register purchases
Link store transactions to your users so refund requests match the right
buyer (iOS: send appAccountToken at purchase or register the transaction;
Android: set obfuscatedAccountId in your billing flow):
curl -X POST https://app.refundhalt.com/v1/apps/$APP_ID/ingest/purchases \
-H "Authorization: Bearer rh_live_..." \
-H "Content-Type: application/json" \
-d '{
"external_user_id": "user_8231",
"original_transaction_id": "2000000123456789"
}'iOS sends original_transaction_id (optionally app_account_token);
Android sends purchase_token instead. Safe to call repeatedly —
fire-and-forget it right after the purchase completes.
Common endpoints
| Endpoint | What it does |
|---|---|
GET /v1/apps | List your apps |
GET /v1/apps/{id}/setup | Setup guide values (webhook URL, topic, …) |
PUT /v1/apps/{id}/policy | Update refund policy & rules |
GET /v1/apps/{id}/forwards | List notification forwards |
POST /v1/apps/{id}/forwards | Add a forward URL |
GET /v1/refund-requests | The refund-request ledger (filterable) |
GET /v1/refunds | Refund outcomes |
GET /v1/analytics/overview | Saved revenue, save rate, totals |
Errors always come back as
{"error": {"code": "...", "message": "...", "details": …}}, and response
shapes never change based on state — empty lists are empty lists, never a
different object.