RefundHalt

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

EndpointWhat it does
GET /v1/appsList your apps
GET /v1/apps/{id}/setupSetup guide values (webhook URL, topic, …)
PUT /v1/apps/{id}/policyUpdate refund policy & rules
GET /v1/apps/{id}/forwardsList notification forwards
POST /v1/apps/{id}/forwardsAdd a forward URL
GET /v1/refund-requestsThe refund-request ledger (filterable)
GET /v1/refundsRefund outcomes
GET /v1/analytics/overviewSaved 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.

On this page