API Reference v1
Mobile recharge and bill payments for India, Bangladesh and Singapore. One Bearer token, one corridor, no FX. Sandbox is free and instant.
From zero to a successful sandbox recharge in five steps.
developers@tachopay.com. We'll send a tpk_test_… key with 1,000 of test wallet credit in your chosen corridor (IN/BD/SG).curl https://api.tachopay.com/v1/partner/balance \
-H "Authorization: Bearer $API_KEY"
# → {"available":"1000.0000","currency":"BDT","corridor":"BD",...}curl https://api.tachopay.com/v1/partner/operators \
-H "Authorization: Bearer $API_KEY"
# → { "operators": [ { "id": 14, "name": "Grameenphone", ... } ] }curl -X POST https://api.tachopay.com/v1/partner/topup \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: my-first-recharge-$(date +%s)" \
-H "Content-Type: application/json" \
-d '{"operator_id": 14, "phone": "8801712340001", "amount": 100}'
# → 200 OK in ~200ms:
# { "order_id":"TP202600007", "status":"PENDING", "balance_after":"900.0000", ... }curl https://api.tachopay.com/v1/partner/transactions/TP202600007 \
-H "Authorization: Bearer $API_KEY"
# → { "status":"SUCCESS", "operator_ref":"MOCK-...", "settled_at":"..." }You're integrated.
Production needs the same code with a tpk_live_… key (issued after KYC).
Every request requires a Bearer token in the Authorization header. The token prefix decides the environment:
tpk_live_… — production. Real money moves.tpk_test_… — sandbox. Mock providers, deterministic outcomes, no real money.Authorization: Bearer tpk_live_<32 base64url chars>
Your token is shown ONCE.
We store only an HMAC-SHA256 hash. Lost token → regenerate (revokes the old one).Optional security additions (set in the partner portal):
Each partner is assigned one corridor at onboarding. Your wallet is in that corridor's native currency. No FX conversion is applied to your transactions.
IN
India · INR
Direct operator · BBPS
BD
Bangladesh · BDT
Direct operator
SG
Singapore · SGD
Direct operator
Sandbox uses a deterministic mock provider. The last 4 digits of the recipient phone (or consumer number for bills) control the outcome:
| Suffix | Outcome | Use it to test |
|---|---|---|
| …0001 | SUCCESS (instant) | Happy path |
| …0002 | FAILED + auto-refund (instant) | Failure handling, wallet credit-back |
| …0003 | PENDING → SUCCESS after 30s | Async settle, status polling |
| …0004 | PENDING → FAILED + refund after 30s | Async failure handling |
| …0005 | Stuck PENDING (never settles) | Webhook-failure / reconciliation |
{
"available": "8742.50",
"currency": "BDT",
"corridor": "BD",
"updated_at": "2026-06-06T04:21:25.960Z",
"environment": "sandbox"
}Returns all active mobile operators in your corridor.
{
"corridor": "BD",
"currency": "BDT",
"count": 5,
"operators": [
{
"id": 14,
"api_operator_id": "GT-3",
"name": "Grameenphone",
"category": "MOBILE_PREPAID",
"min_amount": "20.00",
"max_amount": "1000.00",
"local_currency": "BDT"
},
{ "id": 15, "name": "Robi", ... },
{ "id": 16, "name": "Banglalink", ... }
]
}Send a prepaid topup. Returns optimistic PENDING in 200-500ms; settlement runs async (single-step — no fetch required).
Authorization: Bearer $API_KEY Idempotency-Key: <unique-per-request> Content-Type: application/json
GET /v1/partner/operators.curl -X POST https://api.tachopay.com/v1/partner/topup \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"operator_id": 14,
"phone": "8801712340001",
"amount": 100,
"reference": "your-internal-ref-789"
}'const res = await fetch("https://api.tachopay.com/v1/partner/topup", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.TACHOPAY_KEY}`,
"Idempotency-Key": crypto.randomUUID(),
"Content-Type": "application/json",
},
body: JSON.stringify({
operator_id: 14,
phone: "8801712340001",
amount: 100,
reference: "your-internal-ref-789",
}),
});
const { order_id, status, balance_after } = await res.json();import os, uuid, requests
r = requests.post(
"https://api.tachopay.com/v1/partner/topup",
headers={
"Authorization": f"Bearer {os.environ['TACHOPAY_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"operator_id": 14,
"phone": "8801712340001",
"amount": 100,
"reference": "your-internal-ref-789",
},
timeout=30,
)
data = r.json()
order_id = data["order_id"]{
"order_id": "TP202600007",
"status": "PENDING",
"operator_id": 14,
"phone": "8801712340001",
"amount_local": "100.0000",
"currency": "BDT",
"balance_after": "900.0000",
"reference": "your-internal-ref-789",
"created_at": "2026-06-06T05:01:12.330Z"
}BBPS / utility bill payment is a two-step flow. Fetch the outstanding amount first (so the customer sees the real bill), then submit the payment with the fetched amount.
Fetch outstanding → submit payment → confirm settlement
curl -X POST https://api.tachopay.com/v1/partner/bill-info \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"biller_id": 14,
"consumer_number": "98765430001"
}'{
"biller_id": 14,
"biller_name": "Airtel",
"consumer_number": "98765430001",
"customer_name": "Test Customer A",
"amount": "500.00",
"currency": "BDT",
"due_date": "2026-06-13",
"bill_period": "2026-06",
"bill_reference": "BBPS-MOCK-1780730408152",
"fetched_at": "2026-06-06T07:20:08.152Z",
"instructions": "Submit this amount via POST /v1/partner/pay-bill ..."
}Submit the exact amount from step 1. BBPS requires ±1 unit match.
curl -X POST https://api.tachopay.com/v1/partner/pay-bill \
-H "Authorization: Bearer $API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"biller_id": 14,
"consumer_number": "98765430001",
"amount": 500.00,
"reference": "bill-jun-2026"
}'{
"order_id": "TP202600121",
"status": "PENDING",
"biller_id": 14,
"consumer_number": "98765430001",
"amount_local": "500.0000",
"currency": "BDT",
"balance_after": "12101.00",
"reference": "bill-jun-2026",
"created_at": "2026-06-06T07:20:08.294Z"
}Sandbox bill amounts
In sandbox, the consumer_number suffix decides the outstanding amount:…0001 → 500.00 · …0002 → 1500.00 · …0003 → 850.50 · …0004 → 2400.00 (overdue) · …0099 → 0.00The authoritative source of truth for any transaction. Status field is masked to the three customer-facing values: PENDING · SUCCESS · FAILED.
{
"order_id": "TP202600007",
"status": "SUCCESS",
"operator_ref": "GT-9F2A1B7C",
"amount_local": "100.0000",
"currency": "BDT",
"phone": "8801712340001",
"created_at": "2026-06-06T05:01:12.330Z",
"settled_at": "2026-06-06T05:01:14.880Z"
}{
"order_id": "TP202600008",
"status": "FAILED",
"amount_local": "100.0000",
"currency": "BDT",
"phone": "8801712340002",
"refunded_at": "2026-06-06T05:01:14.880Z"
}Configure a webhook URL in your partner portal to receive transaction.succeeded and transaction.failed events as they settle. 3 retries at 5-minute gaps; if all three fail, the delivery is marked FAILED and you should reconcile via GET /transactions/:order_id.
X-TachoPay-Signature: t=1717658472,v1=4d9b7c2a... # Verify with HMAC-SHA256: # signed_payload = t + "." + raw_request_body # expected = hmac_sha256(your_webhook_secret, signed_payload) # compare = timingSafeEqual(expected, v1)
{
"event": "transaction.succeeded",
"order_id": "TP202600007",
"operator_ref": "GT-9F2A1B7C",
"amount_local": "100.0000",
"currency": "BDT",
"reference": "your-internal-ref-789",
"settled_at": "2026-06-06T05:01:14.880Z"
}{
"event": "transaction.failed",
"order_id": "TP202600008",
"amount_local": "100.0000",
"currency": "BDT",
"refunded_at": "2026-06-06T05:01:14.880Z"
}Every value-moving call (/topup, /pay-bill) requires an Idempotency-Key header. This guarantees safe retries on network errors.
Idempotent-Replay: true).409 idempotency_conflict.400 idempotency_key_required.const idempotencyKey = uuid();
let attempt = 0;
while (attempt < 5) {
try {
return await topup({ idempotencyKey, ... });
} catch (networkError) {
attempt++;
await sleep(Math.pow(2, attempt) * 1000); // exponential backoff
// Retry with the SAME idempotency key — replay-safe.
}
}We expose the full Partner API as a Model Context Protocol (MCP) server. Any AI agent (Claude Desktop, OpenAI Agents SDK, custom orchestration) can connect with the same Bearer token and call our endpoints as tools — no glue code needed.
{
"mcpServers": {
"tachopay": {
"url": "https://api.tachopay.com/mcp",
"headers": {
"Authorization": "Bearer tpk_test_REPLACE_ME"
}
}
}
}| Tool | Purpose |
|---|---|
| tachopay_get_balance | Wallet balance |
| tachopay_list_operators | List operators / billers (filterable) |
| tachopay_topup | Mobile prepaid recharge |
| tachopay_pay_bill | Postpaid / BBPS bill payment |
| tachopay_get_transaction | Check transaction status |
All tools enforce the same money rules and idempotency as the REST API. Sandbox keys give the AI safe, deterministic playground access.
| Status | Code | Cause |
|---|---|---|
| 400 | validation_failed | Missing/malformed field |
| 400 | idempotency_key_required | Missing Idempotency-Key header |
| 401 | invalid_api_key | Token revoked, malformed, or wrong |
| 402 | insufficient_balance | Wallet below required amount |
| 403 | kyc_required | Live key without KYC approval |
| 403 | ip_not_allowed | Caller IP not in allowlist |
| 404 | operator_not_found_or_wrong_corridor | Operator id invalid for your corridor |
| 404 | not_found | order_id doesn't exist or isn't yours |
| 409 | idempotency_conflict | Same Idempotency-Key, different body |
| 422 | amount_out_of_range | Below operator min or above max |
| 429 | rate_limit_exceeded | Per-key limit hit |
| 500 | internal_error | Our problem — safe to retry with same Idempotency-Key |
Read once — your customer-support and reconciliation teams will thank you.
balance_after.PENDING, SUCCESS, or FAILED. Internal states (INITIATED, DISPUTED) are not exposed.Email us with your use case + expected volume. We'll send sandbox credentials, a Postman workspace, and an onboarding call within one business day.