API conventions
Patterns that repeat across Cheggout's APIs. Reading this once will save you debugging time later — several conventions here are genuinely surprising.
Transport
- HTTPS only, in both environments.
- JSON request and response bodies, UTF-8 encoded.
Content-Type: application/jsonon every request with a body.- Server-to-server calls originate from whitelisted IPs.
Status codes
This is the convention most likely to catch you out.
Deals Store responses carry the status inside the body, as a status field, in an HTTP-like
style:
{
"status": 401,
"errorCode": "UNAUTHORIZED",
"message": "Invalid AppKey/SecKey."
}
Rewards Center business outcomes also arrive in the body, as a boolean:
{ "status": false, "message": "failure", "rewards": [] }
Do not branch on the HTTP status line alone. Parse the response and check the body's status
field. A request that reached the platform and was answered — including with an error — may not be
reflected in the transport status.
The practical rule:
1. Did the HTTP call complete? → transport-level failure if not
2. What does body.status say? → the real outcome
3. Is this a business "no" or a fault? → see below
"No" is not an error
Several normal outcomes look like failures if you treat them as exceptions.
| Response | Meaning | Handle as |
|---|---|---|
{"status": false, "message": "failure", "rewards": []} | User not eligible right now | Show nothing. Not an error. |
BETTER LUCK NEXT TIME | No reward allocated for this reveal | A valid reveal outcome. Show the "no luck" state. |
403 NON_ELIGIBLE | Targeting rules exclude this user | Hide or disable the offer. |
409 COUPON_EXHAUSTED | Stock ran out | Show "sold out". |
429 MAX_LIMIT_REACHED | Per-user redemption limit hit | Show the limit message. |
None of these should trigger a retry, an alert, or a token refresh. A campaign with frequency caps will legitimately return "no" most of the time.
Error shapes
Error bodies are not uniform across product lines. Write a tolerant parser.
Signed APIs — failure:
{ "STATUS": "FAILURE", "ERRORSTRING": "Signature Mismatch!" }
Note the uppercase keys, and that this body carries no SIGN — signature verification must be
skipped for it.
Rewards Center — business failure:
{ "status": false, "message": "failure", "rewards": [] }
Rewards Center reveal — business failure (inside the BDATA payload):
{ "status": false, "message": "REWARD NOT FOUND" }
Deals Store — error:
{ "status": 404, "errorCode": "INVALID_OFFER", "message": "Offer not found or inactive." }
A defensive parser checks, in order: an uppercase STATUS/ERRORSTRING pair, then a numeric or
boolean status with errorCode/message, then the success shape.
The full catalogue is on Error codes.
TBD Error coverage is uneven. The Deals Store publishes a proper error table;
the Rewards Center and Bank APIs document only a few failure strings. Treat any undocumented
non-success body as a generic failure, log it with the endpoint and your extTransactionId, and
report it so it can be added.
The signed envelope
Signed APIs wrap both request and response:
{ "CHANNELID": "...", "SIGN": "...", "BDATA": "<base64 payload>" }
The business payload is inside BDATA. Endpoint pages document the payload fields, not the
envelope. Full construction rules: The signed envelope.
BDATA alone is not securityOne endpoint — FetchRewardInfo — uses the BDATA
wrapper without a SIGN. There, Base64 is an encoding layer, not a signature; the
authentication is the bearer token. Do not assume a BDATA field implies a signed request.
Field naming
Naming is not consistent across product lines, and several payloads match keys case-sensitively.
| Concept | Appears as |
|---|---|
| Your channel | CHANNELID, X-CHANNELID, channelName, bName |
| The user | virtualId; userID as the iOS SDK launch parameter |
| Your reference | extTransactionId |
| The card | rewardId, scratchcardId |
Two rules follow:
- Use the exact key the endpoint page documents.
getSecuredReferenceTokenreadsbName;GetAuthTokenreadschannelName. Sending the wrong one silently omits the value. - Do not assume a field is echoed back under the same name.
rewardIdis a number inGetRewardresponses and is sent as a string toFetchRewardInfo.
Idempotency
extTransactionId is your idempotency key on any call that can issue a reward or coupon.
| Rule | Detail |
|---|---|
| Uniqueness | Globally unique per event. Never reuse. |
| Format | Alphanumeric, up to 256 characters |
| Retry | Replay the same extTransactionId after a network failure |
| Never | Reuse one ID across different offers or different events |
Rewards Center — replaying an extTransactionId returns the same set of cards rather than
creating new ones. After a card has been scratched, the response returns unscratched cards first,
then scratched ones carrying full reward detail.
Deals Store — replaying returns the previously processed result, or is rejected as a duplicate.
TBD Idempotency has documented edge cases. The exact behaviour for a duplicate
on the Deals Store is ambiguous in the source contract, and replay behaviour after certain reward
types or expiries is not fully specified. Keep your own ledger of what you requested and what you
received, and reconcile on extTransactionId — do not rely on the platform's idempotency as your
only protection against double-issuance. See
Security best practices.
Dates and times
| Field | Documented format |
|---|---|
dateandtime (GetReward) | DDMMYYYYHHMMSS |
transDateAndTime (GetScratchCardForBank) | YYYY-MM-DD HH:mm:ss |
createdDateTime, endDate (responses) | YYYY-MM-DD HH:mm:ss.SSS |
offer_validity, coupon_validity | ISO 8601 date |
expiredDate (webhook) | ISO 8601 with offset |
TBD Time zones are not specified on most fields, and expireDate on token
responses has no documented format at all. Do not parse expireDate as a contract — drive token
refresh from authentication failures instead. See Tokens.
Amounts
- Transaction amounts in reward APIs are decimal values, two decimal places.
- Deep payment amounts are in paisa —
"amount": "54000"means ₹540.00. See Deep payment. - Coin costs in the deals catalogue are numeric, where
0means a free offer.
Pagination
Only GetAllScratchCardsByUserId documents
pagination, via page and limit query parameters. It is 1-based and returns a bare array with
no total count — detect the end by receiving fewer items than limit.
TBD Default and maximum limit, and pagination for catalogue endpoints, are not
documented.
Rate limits
TBD No rate limits, quotas or throttling behaviour are published. Design defensively:
- Cache what is cacheable — the catalogue especially.
- Pre-fetch tokens rather than minting per request.
- Back off exponentially on transport failures.
- Agree expected peak volumes with Cheggout before launch.
Timeouts and retries
TBD No timeout or retry contract is published. Sensible defaults until one exists:
| Call type | Suggested timeout | Retry |
|---|---|---|
| Token minting | 5s | Once, then fail |
| Reward creation | 5s | Once with the same extTransactionId |
| Reveal | 10s | Do not blind-retry — reveal is one-shot; reconcile via history instead |
| Catalogue | 10s | Safe to retry — read-only |
| Coupon delivery | 10s | Retry with the same extTransactionId |
The reveal rule matters: re-issuing a reveal after a timeout can leave you unable to recover the reward. Fetch the user's card list to find out what actually happened.
Versioning
TBD No API version policy is published. Paths are unversioned, and change notification happens through your Cheggout contact. See Versioning.