End-to-End Example: Single
A single is a coupon containing one bet.
This guide covers the complete flow:
Obtain a JWT
↓
Obtain an outcome pointer
↓
Submit the coupon
↓
Receive code = 1
↓
Store the coupon and bet
↓
Receive the result through callback or API
↓
Perform the financial operation once
What you need
Obtain from the manager:
- API base URL;
- client account username and password;
- callback connection and secret phrase if the partner wants to use callbacks.
The examples use this placeholder address:
BASE_URL="https://coupon-api.example.com"
Replace it with the address provided by the manager.
Step 1. Obtain a JWT
curl --request POST \
--url "$BASE_URL/api/partner/login" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data '{
"username": "<username>",
"password": "<password>"
}'
A successful response contains the token:
{
"code": 1,
"body": {
"token": "eyJhbGciOiJIUzI1NiJ9...",
"username": "partner-demo"
},
"error_code": null,
"error_message": null,
"date": 1784970000000,
"time_ms": 35,
"path": "/api/partner/login"
}
Store body.token and include it in subsequent requests:
Authorization: Bearer <token>
See Authentication for details.
Step 2. Prepare the bet
The user selects a sports-line outcome. The partner receives the complete pointer for that outcome:
line#737779544#1#1#0#1.85
In this pointer:
line → prematch
737779544 → event ID
1 → bet group ID
1 → outcome ID
0 → outcome parameter
1.85 → odds shown to the user
Submit the pointer without modification. Do not construct it from team, market, or outcome names.
See Bet pointer for details.
Step 3. Prepare the financial operation
Before submitting the coupon, the partner system should verify:
- the user;
- available balance;
- allowed amount and currency;
- the ability to perform one debit of
amount.
At this stage, the bet slip is only a preliminary selection. The SportAPI Coupon Settlement System has not yet accepted the coupon.
The partner may reserve the amount in its internal system, but the bet can be considered finally accepted only after a successful API response.
Step 4. Place the single
Submit one pointer in list_bets:
TOKEN="<jwt-token>"
curl --request POST \
--url "$BASE_URL/api/partner/coupons/place" \
--header "Accept: application/json" \
--header "Authorization: Bearer $TOKEN" \
--header "Content-Type: application/json" \
--data '{
"list_bets": [
"line#737779544#1#1#0#1.85"
],
"amount": 10,
"currency": "USD",
"callback_url": "https://partner.example.com/api/coupon-result",
"lang": "en",
"mode": "reject",
"mode_type": null,
"multi": false
}'
In this example:
| Field | Value |
|---|---|
list_bets | Contains one bet, so a single is created. |
amount | The single stake is 10. |
currency | The partner currency is USD. Any string identifier is allowed. |
callback_url | Partner handler URL. |
lang | Names in the coupon are stored in English. |
mode | reject prevents automatic acceptance of changed odds. |
multi | false; one entry creates one single in either case. |
If callbacks are not used, omit callback_url or provide null.
To accept odds changes automatically, use mode = "accept" and the corresponding mode_type. All options are described in Odds changes and outcome availability.
Step 5. Check the placement response
Example successful response:
{
"code": 1,
"body": {
"coupons": [
{
"coupon_code": "000000000272",
"amount": 10,
"win": 18.5,
"potential_win": 18.5,
"real_win": null,
"coef": 1.85,
"original_coef": 1.85,
"calculate_coef": null,
"has_return": false,
"date": 1784970000000,
"status": 0,
"asian": false,
"calculate_date": null,
"coupon_type": 1,
"events_count": 1,
"events_data": [
{
"id": 912,
"game_id": 737779544,
"raw_pointer": "line#737779544#1#1#0#1.85",
"line_type": "line",
"is_live": false,
"bet_group_id": 1,
"bet_group_name": "Match result",
"bet_id": 1,
"bet_name": "First team to win",
"status": 0,
"opp1": "Team A",
"opp2": "Team B",
"coef": 1.85,
"calc_coef": null,
"calculate_date": null,
"calculate_score": null,
"rate": "0"
}
]
}
]
},
"error_code": null,
"error_message": null,
"date": 1784970000015,
"time_ms": 45,
"path": "/api/partner/coupons/place"
}
The coupon is accepted only when all of the following are true:
- the HTTP request completed successfully;
code = 1;body.couponscontains the created coupon.
Important. Do not store the bet slip as an accepted coupon or perform the final debit from the end user before receiving this confirmation. The outcome may have disappeared, become blocked, or changed its odds.
On a successful response, the SportAPI Coupon Settlement System has already atomically debited
amountfrom the integration’s client balance. This does not replace the separate financial operation in the partner user’s wallet.
Even for one single, body.coupons is an array.
Step 6. Store the coupon and process the user balance
After code = 1, the partner:
- stores the coupon from
body.coupons[0]; - associates it with the user;
- stores
coupon_codeas a string; - stores the bet from
events_data[0]; - records a debit of
amount = 10from the end user in its own system; - changes the local bet state to “accepted.”
Recommended keys:
coupon: coupon_code
bet: coupon_code + events_data[].id
In the example:
coupon_code = "000000000272"
bet id = 912
The partner chooses which coupon and bet fields to store. At minimum, subsequent settlement requires:
coupon_code;amount;- the submitted
currency, when used; - coupon status;
events_data[].id;- bet status.
currency is not included in the full response model or callback, so store it from the placement request when required.
If the coupon is not accepted
Example rejection caused by changed odds:
{
"code": 0,
"body": {
"changes": [
{
"game_id": 737779544,
"bet_coefficient": 1.85,
"actual_coefficient": 1.75,
"change_type": 2,
"status": "rejected"
}
]
},
"error_code": 501,
"error_message": "Coefficient is change",
"date": 1784970000000,
"time_ms": 20,
"path": "/api/partner/coupons/place"
}
Primary rejection reasons:
error_code | Cause | Partner action |
|---|---|---|
501 | The odds changed. | Show the current odds and request user confirmation when required by the partner flow. |
502 | The outcome is no longer available. | Remove or update the outcome in the bet slip. |
503 | The outcome is blocked. | Inform the user that placement is temporarily unavailable. |
504 | Outcome validation ended with an error. | Do not consider the coupon accepted; allow another attempt later. |
507 | Insufficient SportAPI client balance. | Do not store the coupon; replenish the client balance or change the amount. |
When code = 0:
- no accepted coupon is created;
- no
coupon_codeappears for the new bet; - the final debit must not be performed;
- release any preliminary amount reservation.
With 507, no coupon was created and the SportAPI client balance was not changed.
Do not blindly retry an indeterminate request
If the connection ends before the placement response is received, do not automatically repeat POST /coupons/place without ruling out successful processing of the first request. Otherwise, one user selection may create two coupons.
Send this situation to a separate reconciliation process and check available data before resubmitting.
Step 7. Receive the result
A single has one bet, so after that bet is settled the coupon receives a result:
| Coupon status | Bet status | Result | Financial action |
|---|---|---|---|
2 | 1 | Win | Credit real_win. |
4 | 2 | Loss | Credit nothing; real_win = 0. |
8 | 3 or 23 | Return or push | Credit real_win, normally equal to amount. |
For a half win or half loss, the bet may have status 21 or 22. Use the provided real_win rather than calculating the payout independently from the status.
The result can be received through callback, API, or both.
Option A. Receiving through callback
Callbacks must be enabled in advance by the manager, and callback_url must be provided when placing the coupon.
Example callback for a winning single:
POST /api/coupon-result HTTP/1.1
Host: partner.example.com
Content-Type: application/json
X-Coupon-Signature: sha256=<hex_hmac_sha256>
{
"event": "coupons.settled",
"batchId": "d407e986f3a64d9d36a77bf532322ef8",
"clientId": 17,
"couponCount": 1,
"coupons": [
{
"coupon_code": "000000000272",
"realWin": 18.5,
"calculate_coefficient": 1.85,
"status": 2,
"calculate_date": 1784973600000,
"events_data": [
{
"uuid": "912",
"status": 1,
"calculate_coefficient": 1.85,
"calculate_date": 1784973600000,
"calculate_score": "2:1",
"timer": 0
}
]
}
]
}
The partner handler must:
- verify the HMAC from the original body bytes;
- register
batchIdwith a unique constraint; - find the coupon by
coupon_code; - find the bet by
coupon_code + uuid; - check the coupon status;
- update the coupon and bet;
- credit
realWin = 18.5once; - commit the transaction;
- return HTTP
200.
Recommended response:
{
"success": true,
"processed": 1
}
Do not credit funds again if the same batchId was already processed.
See also:
Option B. Receiving through the API
Callbacks are optional. The current state can be requested with the stored code:
COUPON_CODE="000000000272"
curl --request GET \
--url "$BASE_URL/api/partner/coupons/get?coupon_code=$COUPON_CODE" \
--header "Accept: application/json" \
--header "Authorization: Bearer $TOKEN"
Shortened response for a winning single:
{
"code": 1,
"body": {
"coupon_code": "000000000272",
"amount": 10,
"real_win": 18.5,
"calculate_coef": 1.85,
"status": 2,
"calculate_date": 1784973600000,
"events_data": [
{
"id": 912,
"status": 1,
"calc_coef": 1.85,
"calculate_date": 1784973600000,
"calculate_score": "2:1"
}
]
},
"error_code": null,
"error_message": null,
"date": 1784973600100,
"time_ms": 6,
"path": "/api/partner/coupons/get"
}
Check:
code = 1
body.status = 2
body.real_win = 18.5
Then update the local coupon and credit body.real_win once.
For regular fallback monitoring, use:
GET /api/partner/coupons/calculated?time=10
The practical polling flow is described in Polling fallback.
Idempotency of the financial result
Callbacks and polling may return the same result.
The partner must guarantee:
one settlement result
→ no more than one credit
Example unique financial-operation key:
000000000272:0:final_credit
The partner chooses the key format. It must prevent a repeated callback, polling result, or business process from creating a second credit.
Rare scenario: status 15
If an already settled single is returned for recalculation:
coupon status = 15
bet status = 4
If the previous result was already financially processed, on first receiving this new state the partner:
- debits
amountagain; - moves the coupon into a state awaiting a new result;
- does not treat status
15as final; - credits the new
real_winafter the new final status.
The operations must be idempotent. A repeated callback with the same batchId must not repeat the debit.
Complete algorithm
1. Obtain a JWT.
2. Obtain the complete bet pointer.
3. Check or reserve the user balance.
4. Send POST /api/partner/coupons/place.
5. If code = 0, do not create an accepted coupon and release the reservation.
6. If code = 1, store body.coupons[0] and debit amount.
7. Receive the result through callback or API.
8. Check the final status.
9. Credit the provided real_win / realWin no more than once.
10. Periodically reconcile results through the API.
Checklist
- A client JWT is used.
- The outcome pointer is submitted without modification.
list_betscontains one entry.amountapplies to one single.- The bet slip is not stored as an accepted coupon before
code = 1. - A preliminary reservation is released after rejection.
coupon_codeis stored as a string with leading zeros.- Coupon and bet data from the API response are stored.
currencyis stored from the request when required by the partner.- Callback HMAC is verified before JSON parsing.
- A repeated
batchIdis not processed financially again. - The final result is determined by the coupon status.
real_winorrealWinis used for crediting.- Callbacks are supplemented with fallback polling.
- Status
15is handled as waiting for a new result.
Next section: Accumulator integration.