Starship Rewards API
Recharges

Recharge Charges API

Quote face, discount and payable for a prospective recharge without placing it — the pre-submit price preview

Recharge Charges API

Prices a recharge before you commit to it. Given an operator and an amount, it returns what the top-up is worth, the discount your client is entitled to, and exactly what would leave your wallet.

Nothing is placed, nothing is reserved and no money moves. This is a quote endpoint.

Endpoint

POST /api/v1/recharge/charges

Authentication: API Key + Secret required

It is a POST rather than a GET because pricing is per-client and dynamic: the answer depends on who is asking, so it is neither cacheable nor safely expressible as a shareable URL. Responses are never cached.

Request Headers

X-API-Key: sk_7Fq2XmR9vTb4NcZ1yWd6Ke
X-API-Secret: your-secret-here
Content-Type: application/json
HeaderRequiredDescription
X-API-KeyYesYour API key
X-API-SecretYesYour API secret
Content-TypeYesMust be application/json

No Idempotency-Key is needed. This endpoint changes nothing, so it is safe to call repeatedly. Only POST /api/v1/recharges requires an idempotency key.

Request Body

{
  "operator_id": 1,
  "amount": 199
}
FieldTypeRequiredValidationDescription
operator_idnumberYesMust exist and be activeFrom GET /api/v1/recharge/operators
amountnumber | stringYesWhole rupees, greater than zeroThe face value of the prospective recharge

Amount is exact, never a float

amount accepts a JSON number (199) or a string ("199"). Both are parsed as an exact decimal — money never passes through floating point on the way in.

It must be a whole rupee. 199.50 is rejected with 400, not rounded and not truncated. The reason is upstream: the recharge vendor truncates fractional input, so accepting 199.50 would debit your wallet for ₹199.50 and credit the phone with ₹199. Rather than let the amount change silently under you, the API refuses it.

This endpoint does not check the recharge type. It takes no is_postpaid or is_special field, so it cannot tell you whether the operator will accept the recharge you have in mind — only what it would cost. A quote succeeding is not a guarantee that the matching submit will. Capability checks (supports_prepaid, supports_postpaid, supports_special), circle requirements, and per-client operator blocks are all enforced at submit.

Response

Success Response

Status Code: 200 OK

{
  "face": 199.00,
  "discount_pct": 2.50,
  "discount": 4.98,
  "payable": 194.02
}

Response Fields

FieldTypeDescription
facenumberThe recharge amount — what reaches the phone. Echoes your amount.
discount_pctnumberThe discount percentage resolved for your client and this operator
discountnumberface × discount_pct ÷ 100, rounded to 2 decimal places
payablenumberface − discount — what your wallet would be debited

All four are returned with two decimal places.

Show payable to your user, not face. face is what the phone receives; payable is what you are charged. The gap between them is your margin.

Note the asymmetry: face is always a whole rupee, but payable legitimately carries paise. A 2.5% discount on ₹199 is ₹4.98, leaving ₹194.02 payable.

How your discount is resolved

The percentage comes from the first of these that matches — a three-tier precedence:

TierRuleBeats
1A discount configured for your client and this specific operatorEverything
2Your client's default discount, applying to all operatorsTier 3
3The platform default discount—

A tier-1 rule always wins regardless of how the rules are ordered internally. If no rule matches at any tier and no platform default is configured, the discount is 0 and payable equals face — a missing configuration never invents a discount.

Because tier 1 is per-operator, the same amount can quote differently across operators. Quote per operator rather than caching one percentage and applying it everywhere.

Should you call this before every submit?

It is optional. POST /api/v1/recharges re-prices server-side and debits the authoritative amount, so skipping the quote never risks being charged something different from what the pricing rules say.

Call it when you need to show a price — a confirmation screen, a basket total, a margin calculation. Skip it in a server-to-server flow that submits without user confirmation, and save the rate-limit budget.

Errors

StatusCodeMessageCause
400E_VALIDATION_FAILUREinvalid request bodyBody is not valid JSON, or a field has the wrong type
400E_VALIDATION_FAILUREoperator_id is requiredoperator_id missing or 0
400E_VALIDATION_FAILUREamount must be greater than zeroamount is zero or negative
400E_VALIDATION_FAILUREamount must be a whole rupeeamount has a fractional part
400E_VALIDATION_FAILUREunknown operator_idNo such operator, or the operator is inactive
401E_UNAUTHORIZED_ACCESS—Missing, invalid or non-whitelisted credentials
500INTERNAL_SERVER_ERRORFailed to calculate recharge chargesPricing lookup failed, or a configured discount percentage is outside [0,100]

Errors use the standard envelope:

{
  "error": {
    "name": "ValidationException",
    "code": "E_VALIDATION_FAILURE",
    "message": "amount must be a whole rupee"
  }
}

Branch on error.code, never on error.message — see Error Codes.

Examples

# Quote a 199 rupee top-up
curl -X POST "{{host}}/api/v1/recharge/charges" \
  -H "X-API-Key: sk_7Fq2XmR9vTb4NcZ1yWd6Ke" \
  -H "X-API-Secret: your-secret-here" \
  -H "Content-Type: application/json" \
  -d '{
    "operator_id": 1,
    "amount": 199
  }'

# Amount as a string — parsed identically
curl -X POST "{{host}}/api/v1/recharge/charges" \
  -H "X-API-Key: sk_7Fq2XmR9vTb4NcZ1yWd6Ke" \
  -H "X-API-Secret: your-secret-here" \
  -H "Content-Type: application/json" \
  -d '{
    "operator_id": 1,
    "amount": "199"
  }'

# Rejected — fractional rupees
curl -X POST "{{host}}/api/v1/recharge/charges" \
  -H "X-API-Key: sk_7Fq2XmR9vTb4NcZ1yWd6Ke" \
  -H "X-API-Secret: your-secret-here" \
  -H "Content-Type: application/json" \
  -d '{
    "operator_id": 1,
    "amount": 199.50
  }'
# => 400 { "error": { "code": "E_VALIDATION_FAILURE", "message": "amount must be a whole rupee" } }

Next steps