Skip to content

Documentation

Get started with the Ureh API

From a new account to your first successful purchase: what to set up, how authentication works, what every request field means, and a complete worked request. New to Ureh? Start at Build with Ureh for an overview first.

Before you start

Four steps, once, before your first live call. Each links to where you do it.

StepWhy it's needed
1 Create an account Issues your client_id, the identifier every request carries.
2 Generate your API keys Gives you a Secret Key for purchases and a Public Key for read-only lookups.
3 Fund your wallet Every purchase debits your wallet at request time. An unfunded wallet returns 402 INSUFFICIENT_BALANCE.
4 Subscribe to webhooks Optional but recommended: how you learn a transaction settled without polling.

Your account must be verified and in good standing to transact. If it isn't, purchase calls return 403 ACCOUNT_RESTRICTED while read-only calls keep working.

Authentication

Every request carries your API key as a bearer token. There are no sessions or cookies, and each key's permissions are enforced server side.

Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
Key Prefix Can call
Secret Key sk_ Every endpoint, including purchases and wallet debits. Server side only, never in client-side code.
Public Key pk_ Read-only lookups only: product catalogues and validate calls. Rejected on a purchase or webhook-subscribe endpoint.

A Public Key on a purchase endpoint is rejected with 403 GATEWAY_ERROR, never silently downgraded.

API Keys

Generate your key pair from the API Keys page in the Developer Console. If a key is compromised, you have two options:

  • Rotate. Issue a new Secret Key from the console. Your client_id and Public Key are unaffected; only the old secret stops working.
  • Revoke. Disable a key immediately without issuing a replacement, when you need to stop traffic before reintegrating.

Rate Limits

Every request is checked against four independent dimensions before it is processed. If any dimension is over its limit, the request is rejected with a Retry-After telling you how long to wait.

Dimension Identified by Default limit
ipRequest IP address60 requests / 60s
api_keyYour bearer token100 requests / 60s
clientYour client_id100 requests / 60s
endpointThe specific route, shared across every callerDisabled by default

Purchase endpoints apply a tighter limit of 20 requests per minute on the client dimension. A rate-limited request returns:

Retry-After: 37

{
  "status": "error",
  "code": "RATE_LIMITED",
  "message": "Too many requests. Please slow down and try again shortly.",
  "retry_after_seconds": 37
}

IP Whitelisting

An optional restriction on top of your key. With no entries configured, a key authenticates from any address. Add a trusted address in the console and requests from every other address are rejected exactly as an invalid key is; no distinct error code identifies the whitelist as the cause.

Useful for server-to-server integrations with a fixed egress IP: a leaked key still can't be used from an untrusted address.

Request fields

Every product uses the same field vocabulary. Once you have integrated one, the others differ only in which fields they take; the API Reference lists the exact set per endpoint.

FieldUsed byWhat it is
client_id Every endpoint Your account identifier, issued at registration (for example URH-8F2C91A4D6B0E37F1A). Send it in the JSON body on POST calls, or as a query parameter on GET calls. It must match the API key you authenticate with.
biller Every product Who is being paid: a network (MTN, AIRTEL, GLO, 9MOBILE), a TV operator (DSTV, GOTV, STARTIMES), a disco (IKEDC, EKEDC, AEDC) or an examination board (WAEC, NECO, NABTEB, JAMB). Case-insensitive.
recipient Every product Who or what receives the purchase: a phone number for airtime and data, a smartcard number for cable TV, a meter number for electricity, a candidate profile ID for education.
product_code Data, Cable TV, Education Identifies the catalogue product you are buying, taken from that biller's /products list. Its price is fixed by the catalogue, so these calls do not need an amount.
meter_type Electricity prepaid or postpaid. Electricity has no product catalogue: you choose the meter type and the amount instead.
amount Airtime, Electricity The value to deliver, in NGN. Required where you choose the amount (airtime, electricity). For catalogue products it is optional and, if sent, must match the listed price — see AMOUNT_MISMATCH in the error reference.
quantity Education How many PINs to buy. Defaults to 1. The amount charged is the product price times this quantity.
customer_phone Cable TV, Electricity, Education The end customer's phone number, used for delivery notifications about the purchase. Distinct from recipient, which is the account being credited. Recommended on every call that accepts it.
idempotency_key Every purchase A value you generate that uniquely identifies one purchase attempt. Reusing it returns the original transaction instead of charging again. Ureh generates a separate reference for the transaction itself. See Idempotent requests.

Never send a price you calculated yourself. Ureh sets the amount for every purchase and debits exactly that.

Your first request

A single airtime purchase shows the whole flow. Every other product follows the same shape, only the fields change. See the API Reference for each one.

1. Send the request

# Authenticate with your Secret Key
curl -X POST https://api.ureh.io/v1/airtime/purchase \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
  "client_id": "URH-8F2C91A4D6B0E37F1A",
  "biller": "MTN",
  "recipient": "08012345678",
  "amount": 500,
  "idempotency_key": "5c2f9e2a-6b7a-4b31-9d3a-df2b6c9a4e10"
}'

2. Read the response

A 202 means the request was accepted and your wallet debited, not that it has settled. Use data.reference to check on it; it is a separate identifier Ureh generates, not the idempotency_key you sent. Validation and authentication failures return the same envelope with status: "error" and a code. The full list is in the API Reference.

{
  "status": "pending",
  "code": "PROCESSING",
  "message": "Transaction accepted.",
  "data": {
    "reference": "20246063015832257676576215",
    "recipient": "08012345678",
    "biller": "MTN",
    "amount": 500,
    "channel": "API_GATEWAY",
    "currency": "NGN",
    "created_at": "2024-08-25 14:32:07"
  }
}

3. Know when it settles

A transaction ends at success or failed. There are two ways to learn which.

Check its status with the reference you were given:

curl "https://api.ureh.io/v1/transactions/status?client_id=URH-8F2C91A4D6B0E37F1A&reference=20246063015832257676576215" \
  -H "Authorization: Bearer sk_live_xxxxxxxxxxxxxxxx"
{
  "status": "success",
  "data": {
    "reference": "20246063015832257676576215",
    "transaction_status": "success",
    "amount": 500,
    "service_amount": 500,
    "commission": 0,
    "amount_charged": 500,
    "commission_received": 0,
    "service_type": "VTU_AIRTIME",
    "biller": "MTN",
    "token": null,
    "created_at": "2024-08-25 14:32:07",
    "updated_at": "2024-08-25 14:32:11",
    "refunded": false,
    "completed_at": "2024-08-25 14:32:11"
  }
}

Or subscribe to webhooks once and be notified the moment it settles, with no polling. This is the recommended approach in production. See Webhooks for the delivery model and Polling vs. webhooks for the tradeoffs.

4. Where to go next