ECOBYTE
Corporate APIv1

Corporate API

A server-to-server API for reselling recharge and bill payments. Everything is JSON over HTTPS, authenticated with an API token issued to you and restricted to your own IP addresses.

Base URL: https://freedata.in

One surface

SurfaceWhat you get
/api/corp/v1/*Proper HTTP status codes, a machine-readable error string, and a numeric code support can trace. JSON in, JSON out, POST for anything that moves money.
Each service answers in its own JSON shape. Read the endpoint you are calling rather than assuming a shared envelope.

Authentication

Send your API token on every request. The header form is preferred, because a token in a query string ends up in proxy and web-server access logs:

curl -H "X-Api-Token: <your-token>" \
     "https://freedata.in/api/corp/v1/balance"

Three transports are accepted, in this order of preference:

  • X-Api-Token: <token> — recommended
  • Authorization: Bearer <token>
Account passwords and PINs are never accepted. The credential is an issued API token and nothing else. Send it in a header — never in a query string, where it would be written to every proxy and access log in the path.

IP allowlist

Your account is restricted to the source IP addresses you register with us (individual addresses or CIDR ranges). A call from anywhere else is refused with result code 2428. Tell us before you change egress addresses, or your traffic stops.

Rate limit

Requests are capped per minute per account. Exceeding it returns 429 with result code 123; retry after a short pause.

Errors

A refusal carries a real HTTP status plus a body like:

{"status":"rejected","error":"ip_not_allowed","code":2428,"message":"IP Validation failed."}

Retries and idempotency

Every money endpoint is idempotent on your own referenceurid on recharge, request_id everywhere else. This is the single most important rule in this document.

  • If you do not receive a response, retry with the SAME reference. We return the original outcome instead of transacting again. Generating a new reference on a retry is how integrations double-charge their customers.
  • Reusing a reference for different details (a different number, operator or amount) is rejected with 2555 Duplicate urid. We will never answer you with a transaction that is not the one you asked about.
  • A reference must be unique across your account and at most 64 characters.

Pending is not failure

A PENDING / pending result means the transaction has been accepted and may still complete. Never resend it. Wait for the callback, or poll the status endpoint. Treating pending as a failure and retrying is the classic way to pay twice.

Status callbacks

Give us an HTTPS URL and we will call it when a transaction reaches its final state (we do not call for pending). Delivery is retried with a growing back-off until your endpoint answers 2xx.

GET https://your-server.example/callback
      ?v=2
      &status=SUCCESS
      &orderid=R2607230429028DB15
      &urid=ABC123
            &amount=100.0000
      &account=9876543210
      &ts=1755400000
      &nonce=3f1c0a9b2e5d4867b1a0c7d9e2f34856
      &hash=<hmac>

Verify hash before trusting a callback. It is an HMAC-SHA256, keyed with your API token, over every parameter we send except hash itself — sorted by key and joined as key=value with &. Build the pre-image from what actually arrived rather than from a fixed list of names, so a field we add later is covered automatically:

$params = $_GET;
unset($params['hash']);
ksort($params, SORT_STRING);

$pairs = [];
foreach ($params as $k => $v) {
    $pairs[] = $k . '=' . $v;
}

$expected = hash_hmac('sha256', implode('&', $pairs), $yourApiToken);
if (!hash_equals($expected, (string) ($_GET['hash'] ?? ''))) {
    http_response_code(403);
    exit;
}

// Freshness: refuse anything outside a five-minute window.
if (abs(time() - (int) ($_GET['ts'] ?? 0)) > 300) {
    http_response_code(403);
    exit;
}

// Replay: refuse a nonce you have already seen inside that window.
if ($youHaveSeen[$_GET['nonce']] ?? false) {
    http_response_code(403);
    exit;
}

// Duplicate delivery: we retry until you answer 2xx, so the SAME orderid may
// legitimately arrive more than once with different ts/nonce. Book it once,
// keyed on orderid.
Do not verify a hand-written list of field names. Every parameter is signed, including amount and account. A verifier that hashes only some of them will happily accept a callback whose amount or destination account was rewritten in transit — the signature over the fields it does check still matches.
Your callback URL must be a publicly resolvable https:// address. Private, loopback and link-local addresses are refused. Callbacks are informational: the status endpoint is always authoritative, so an integration that only polls is still correct.

Service status

Read live from the platform as this page was served. Query it yourself at any time with GET /api/corp/v1/services rather than hardcoding a service list.

ServiceNameStatusNote
rechargeMobile & DTH rechargelive
bbpsBill payments (BBPS)no vendor activeEndpoints are live but no vendor is currently switched on — calls will return the service's unavailable outcome and nothing is charged.

"No vendor active" means the endpoints work but nothing is switched on upstream: calls are answered safely and nothing is charged.

Endpoints

Core (v1)

The modern surface for new integrations. JSON in, JSON out, machine-readable string errors alongside a numeric code support can trace.

GET/api/corp/v1/services
What you can call today
Read this instead of hardcoding a service list.
enabled means the endpoints exist. ready means a vendor is switched on right now — if it is false the calls still answer safely and nothing is charged.
{
    "status": "success",
    "code": 200,
    "client": "Acme Pvt Ltd",
    "services": [
        {
            "service": "recharge",
            "name": "Mobile & DTH recharge",
            "enabled": true,
            "ready": true,
            "note": null
        }
    ]
}
GET/api/corp/v1/balance
Your wallet balance
{
    "status": "success",
    "code": 200,
    "balance": "5000.0000"
}
GET/api/corp/v1/transactions
Your transaction history, all services
ParameterInDescription
pagequeryoptionalDefault 1.
page_sizequeryoptionalDefault 20, maximum 100.
statusqueryoptionalsuccess | pending | failed.
servicequeryoptionalrecharge | bbps | money_transfer | travel.
fromqueryoptionalyyyy-mm-dd.
toqueryoptionalyyyy-mm-dd.
qqueryoptionalSearch by account, our ref, or your reference.
{
    "page": 1,
    "page_size": 20,
    "total": 4,
    "transactions": [
        {
            "ref": "R2607…",
            "request_id": "ABC123",
            "service": "recharge",
            "account": "9876543219",
            "amount": "10.0000",
            "status": "failed"
        }
    ]
}

Recharge (v1)

Mobile and DTH recharge. Money endpoints are POST; status is GET.

POST/api/corp/v1/rechargemoney
Place a recharge
ParameterInDescription
accountbodyrequiredCustomer number or DTH subscriber id.
operatorbodyrequiredLIVE operator code (e.g. LIVJIO00003) or our catalog code (e.g. jio). A bare number is an operator id, never an operator code.
amountbodyrequiredFace value in rupees.
uridbodyrequiredYour unique reference (idempotency key), max 64 characters.
circlebodyoptionalCircle/state hint.
customer_namebodyoptionalFor your own records.
status is success | pending | failed. A rejected request returns status:"rejected" with an error string and the matching numeric code.
{
    "status": "success",
    "code": 200,
    "order_id": "R2607…",
    "urid": "ABC123",
    "operator_ref": "OP998877",
    "amount": "100.0000",
    "account": "9876543210",
    "balance": "4900.0000",
    "message": "Recharge successful."
}
GET/api/corp/v1/recharge/{ref}
Status by our order id
{
    "status": "success",
    "code": 200,
    "order_id": "R2607…",
    "urid": "ABC123",
    "operator_ref": "OP998877",
    "amount": "100.0000",
    "account": "9876543210",
    "refunded": false
}
GET/api/corp/v1/recharge
Status by your own reference
ParameterInDescription
uridqueryrequiredThe reference you sent.
GET/api/corp/v1/operators
Operator catalog with codes and limits
GET/api/corp/v1/recharge/plans
Tariff plans for an operator/circle
ParameterInDescription
operatorqueryrequiredOperator code.
circlequeryoptionalCircle name.
GET/api/corp/v1/recharge/circle
Operator and circle lookup for a number
ParameterInDescription
mobilequeryrequiredCustomer number.

Bill payments — BBPS (v1)

Browse the biller directory, render the biller's input form from its customer_params schema, fetch the live bill, then pay. Billers whose fetch_requirement is MANDATORY must be fetched before payment.

GET/api/corp/v1/bbps/categories
Biller categories with counts
GET/api/corp/v1/bbps/billers
Paginated biller directory
ParameterInDescription
categoryqueryoptionalExact category string from /bbps/categories.
qqueryoptionalName search.
pagequeryoptionalDefault 1.
page_sizequeryoptionalDefault 50, max 100.
GET/api/corp/v1/bbps/billers/{id}
One biller WITH its input-form schema
customer_params[].name must be echoed back verbatim on fetch and pay.
GET/api/corp/v1/bbps/quote
Price preview — no money, no vendor call
ParameterInDescription
biller_idqueryrequiredBiller id.
amountqueryrequiredBill amount in rupees.
{
    "ok": true,
    "amount": "500.00",
    "convenience_fee": "0.00",
    "total": "500.00",
    "charge": "500.00"
}
POST/api/corp/v1/bbps/fetch
Fetch the live bill (no money)
ParameterInDescription
biller_idbodyrequiredBiller id.
paramsbodyrequiredThe biller's customer parameters, as a name→value object or a [{name,value}] list.
customer_mobilebodyoptionalCustomer contact number.
The returned fetch_ref binds to the bill you fetched and must be sent on pay.
POST/api/corp/v1/bbps/paymoney
Pay the bill
ParameterInDescription
biller_idbodyrequiredBiller id.
paramsbodyrequiredSame customer parameters used on fetch.
amountbodyrequiredAmount in rupees.
request_idbodyrequiredYour unique reference (idempotency key), max 64 characters.
fetch_refbodyoptionalRequired for billers whose fetch_requirement is MANDATORY.
GET/api/corp/v1/bbps/{ref}
Status of one of your bill payments

Operator codes

On /api/corp/v1/* send the code column; on send the Code column. It is accepted on their own surface, so an existing integration keeps working unchanged. Fetch this programmatically from GET /api/corp/v1/operators.

OperatorTypeCodeNeeds circle
AirtelmobileLIVAIR00001no
BSNLmobileLIVBSN00002no
JiomobileLIVJIO00003no
MTNLmobileno
Vi (Vodafone Idea)mobileLIVVIL00004no
Airtel Digital TVdthLIVAIR00005no
d2h (Videocon)dthLIVVID00009no
Dish TVdthLIVDIS00006no
Sun DirectdthLIVSUN00007no
Tata PlaydthLIVTAT00008no

Result codes

The same catalog throughout: code on v1.

CodeMeaning
101Invalid state code
102Invalid opcode value
103Invalid Amount
104Invalid user mobile number
105Invalid pin provided
106Invalid urid value
107Invalid login details
108Operator down time
111Transaction already running
112Insufficient balance
113Internal server error
115Duplicate recharge not allowed within 10 minutes
116Invalid order id
117Order id not found
120Recharge is pending
121Transaction on hold - a support review is in progress
122Recharge failed
123Server is busy
124Service is down
126Invalid data provided
127Max limit for each customer is only 100000 Rs
200SUCCESS
201Recharge is pending
213Invalid Number
331Your account has been suspended, Please contact your administrator
351Recharge amount not in range
1144This operator service has been temporarily unavailable
2428IP Validation failed
2555Duplicate urid
2666Your account has been blocked for wrong credentials, please contact customer support
200 success · 201 pending (accepted, still in progress — do not resend) · everything else is a refusal or a failure. A failed transaction is refunded to your wallet automatically.