End-to-End Example: Accumulator
An accumulator is one coupon that combines several bets on different matches.
The main differences from a single are:
list_betscontains several pointers;multimust befalse;- one
coupon_codeis created; amountapplies to the entire accumulator;- one
amountis automatically deducted from the SportAPI client balance; - the partner separately deducts one
amountfrom the end user in its own system; - bets may be settled at different times;
- one coupon may receive several successive callbacks.
Overall Flow
The user selects outcomes from several different matches
↓
The partner creates one accumulator bet slip
↓
POST /api/partner/coupons/place
multi = false
↓
code = 1
↓
One coupon
Automatic deduction of amount from the SportAPI client balance
Separate deduction of amount from the user in the partner system
↓
Successive bet status changes
↓
Final result + payout to the user according to real_win
Accumulator Restrictions
Validate the bet slip before submitting it:
- an accumulator must contain at least two bets;
- one accumulator may contain no more than 15 events;
- the bets must belong to different matches;
- a main event cannot be combined with one of its sub-events;
- different sub-events of the same match cannot be combined.
Forbidden combinations include:
- the match result and corners in the same match;
- the match result and a separate half;
- corners and fouls in the same match;
- different periods, sets, or other sub-events of the same match.
To determine whether outcomes belong to the same match, consider not only game_id, but also the relationship with the main event. The SportAPI Coupon Settlement System performs the final validation as well.
If this rule is violated, the API returns:
error_code = 506
Step 1. Obtain a JWT
Authenticate through:
POST /api/partner/login
The following variables are used in the examples below:
BASE_URL="https://coupon-api.example.com"
TOKEN="<jwt-token>"
The partner receives the actual BASE_URL and credentials from the manager.
See Authentication for details.
Step 2. Prepare the Pointers
In this example, the user selected three outcomes from three different matches:
line#737779544#1#1#0#1.80
line#737880112#8#6#2.5#1.50
line#738004921#17#9#3.5#2.00
Each pointer must be obtained from a selected sports-line outcome and submitted without modification.
Initial odds:
1.80 × 1.50 × 2.00 = 5.40
For an amount of 10, the initial potential payout is:
10 × 5.40 = 54
After successful creation, the API returns the actual original_coef and potential_win. Store and display the response values, not only the preliminary calculation from the bet slip.
Step 3. Show the Bet Slip to the User
Before confirmation, the partner interface must clearly show:
- that one accumulator will be created;
- the three selected bets;
- the odds of each bet;
- the preliminary total odds;
- the amount of the single accumulator;
- the potential payout;
- the rule for accepting changed odds.
In this example:
type: accumulator
bets: 3
amount: 10 USD
total amount to deduct: 10 USD
preliminary odds: 5.40
preliminary payout: 54 USD
The bet slip is a preliminary selection, not an accepted coupon.
Step 4. Create the Accumulator
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.80",
"line#737880112#8#6#2.5#1.50",
"line#738004921#17#9#3.5#2.00"
],
"amount": 10,
"currency": "USD",
"callback_url": "https://partner.example.com/api/coupon-result",
"lang": "en",
"mode": "reject",
"mode_type": null,
"multi": false
}'
The key field is:
{
"multi": false
}
It combines all three list_bets entries into one accumulator.
If callbacks are not used, omit callback_url or submit null.
Do Not Confuse This with multi = true
For the same three pointers:
| Mode | Result | Total amount |
|---|---|---|
multi = false | One accumulator containing three bets | 10 |
multi = true | Three separate singles | 10 × 3 = 30 |
multi = true does not create an accumulator. The full amount applies to every single created.
Step 5. Process Successful Creation
Shortened response example:
{
"code": 1,
"body": {
"coupons": [
{
"coupon_code": "000000000350",
"amount": 10,
"win": 54,
"potential_win": 54,
"real_win": null,
"coef": 5.4,
"original_coef": 5.4,
"calculate_coef": null,
"has_return": false,
"date": 1784970000000,
"status": 0,
"calculate_date": null,
"coupon_type": 2,
"events_count": 3,
"events_data": [
{
"id": 912,
"game_id": 737779544,
"raw_pointer": "line#737779544#1#1#0#1.80",
"bet_name": "First team to win",
"status": 0,
"coef": 1.8,
"calc_coef": null,
"calculate_date": null
},
{
"id": 913,
"game_id": 737880112,
"raw_pointer": "line#737880112#8#6#2.5#1.50",
"bet_name": "Total over 2.5",
"status": 0,
"coef": 1.5,
"calc_coef": null,
"calculate_date": null
},
{
"id": 914,
"game_id": 738004921,
"raw_pointer": "line#738004921#17#9#3.5#2.00",
"bet_name": "Total over 3.5",
"status": 0,
"coef": 2,
"calc_coef": null,
"calculate_date": null
}
]
}
]
},
"error_code": null,
"error_message": null,
"date": 1784970000015,
"time_ms": 48,
"path": "/api/partner/coupons/place"
}
Verify:
code = 1
body.coupons.length = 1
coupon_type = 2
events_count = 3
events_data.length = 3
Only after these checks:
- store the coupon;
- store all three bets;
- store
coupon_codeas a string; - record one deduction of
amount = 10from the end user in the partner system; - mark the local coupon as accepted.
Recommended bet keys:
000000000350 + 912
000000000350 + 913
000000000350 + 914
Do not make three financial deductions from the end user: these are bets within one accumulator, not separate coupons. SportAPI has already deducted one amount from the client balance upon successful creation.
currency is not included in the response or callback model. If it is required, store USD from the original request.
If the Accumulator Is Rejected
When code = 0:
- do not store the bet slip as an accepted coupon;
- do not perform the final deduction;
- release the preliminary reservation if one was used;
- process
error_codeandbody.changes.
Multiple Bets from the Same Match
Example:
{
"code": 0,
"body": null,
"error_code": 506,
"error_message": "<bets from the same match cannot be combined>",
"date": 1784970000000,
"time_ms": 12,
"path": "/api/partner/coupons/place"
}
Update the bet slip so that only one outcome remains from each match.
Changed or Unavailable Outcome
Any accumulator entry may fail validation:
error_code | Reason |
|---|---|
501 | The odds changed. |
502 | The outcome is absent from the current feed. |
503 | The outcome is blocked. |
504 | Outcome validation failed. |
507 | The client balance is insufficient to create the accumulator. |
Problematic entries are returned in body.changes. In reject mode, a change to even one set of odds prevents the original accumulator from being accepted.
With 507, the accumulator is not created and the SportAPI client balance remains unchanged.
Step 6. Store the Initial State
Immediately after creation:
coupon status = 0
bet statuses = [0, 0, 0]
real_win = null
calculate_coef = null
calculate_date = null
Status 0 means that the accumulator has not received a final result yet.
Step 7. Process Successive States
Accumulator bets may finish at different times. Every callback contains a complete current snapshot of all bets.
For this accumulator, the sequence may be:
[0, 0, 0] → coupon created
[1, 0, 0] → first bet won
[1, 3, 0] → second bet returned
[1, 3, 1] → third bet won; accumulator completed
Here:
0— the bet is unsettled;1— win;3— return.
First Intermediate State
After the first bet wins, the callback may look like this:
{
"event": "coupons.settled",
"batchId": "batch-version-001",
"clientId": 17,
"couponCount": 1,
"coupons": [
{
"coupon_code": "000000000350",
"realWin": 0,
"calculate_coefficient": 0,
"status": 0,
"calculate_date": 1784973600000,
"events_data": [
{
"uuid": "912",
"status": 1,
"calculate_coefficient": 1.8,
"calculate_date": 1784973600000,
"calculate_score": "2:1",
"timer": 0
},
{
"uuid": "913",
"status": 0,
"calculate_coefficient": null,
"calculate_date": null,
"calculate_score": "",
"timer": 0
},
{
"uuid": "914",
"status": 0,
"calculate_coefficient": null,
"calculate_date": null,
"calculate_score": "",
"timer": 0
}
]
}
]
}
The partner:
- verifies the HMAC;
- stores the new
batchId; - updates bet
912; - leaves the coupon active;
- does not credit
realWin = 0, becausestatus = 0; - returns HTTP
200.
realWin = 0 in an intermediate snapshot does not mean that the coupon lost.
Second Intermediate State
The second bet is returned:
batchId = batch-version-002
coupon status = 0
bet statuses = [1, 3, 0]
This is a new state of the same coupon:
coupon_coderemains000000000350;batchIdchanges;- bet
913receives a settlement multiplier of1; - no financial payout may be made yet.
The return of one bet does not return the whole accumulator. That bet simply does not increase the total settlement multiplier.
Final Winning State
The third bet wins:
bet statuses = [1, 3, 1]
Settlement multipliers:
first bet: 1.80
second bet: 1
third bet: 2.00
Result:
calculate_coef = 1.80 × 1 × 2.00 = 3.60
real_win = 10 × 3.60 = 36
coupon status = 2
Final callback:
{
"event": "coupons.settled",
"batchId": "batch-version-003",
"clientId": 17,
"couponCount": 1,
"coupons": [
{
"coupon_code": "000000000350",
"realWin": 36,
"calculate_coefficient": 3.6,
"status": 2,
"calculate_date": 1784980800000,
"events_data": [
{
"uuid": "912",
"status": 1,
"calculate_coefficient": 1.8,
"calculate_date": 1784973600000,
"calculate_score": "2:1",
"timer": 0
},
{
"uuid": "913",
"status": 3,
"calculate_coefficient": 1,
"calculate_date": 1784977200000,
"calculate_score": "0:0",
"timer": 0
},
{
"uuid": "914",
"status": 1,
"calculate_coefficient": 2,
"calculate_date": 1784980800000,
"calculate_score": "4:0",
"timer": 0
}
]
}
]
}
After verification and storage, the partner credits the user once:
realWin = 36
Use the value returned in the callback, or real_win from the full API. The formula above explains the result, but must not replace the value calculated by the API.
Separate Branch: First Loss
Consider another accumulator:
[1, 0, 0] → first bet won
[1, 2, 0] → second bet lost
As soon as a bet receives status 2:
coupon status = 4
realWin = 0
The system immediately sends a loss callback even if the third bet is not settled yet.
The partner:
- updates the coupon to status
4; - records the financial result as a loss;
- does not make a credit;
- records that the result has already been processed financially;
- returns HTTP
200.
Settlement of the remaining bets continues, but separate intermediate callbacks are not sent after the first loss.
When all bets have been settled, a full snapshot arrives:
[1, 2, 1]
coupon status = 4
realWin = 0
new batchId
This is not a second loss or a new financial operation. Update the remaining bet statuses, but do not process the balance again.
Multiple Versions and Deduplication
For one accumulator:
| Situation | coupon_code | batchId | Action |
|---|---|---|---|
| New intermediate state | Same | New | Update the coupon and bets. |
| Final result | Same | New | Update the data and process the payout once. |
| Repeat of the same HTTP delivery | Same | Same | Do not repeat business operations; return 200. |
Do not treat every new batchId as a new bet or a new financial operation.
Store:
coupon_code → coupon identifier
uuid → bet identifier within the callback
batchId → delivery version identifier
Half Results in an Accumulator
Half Win
Two bets:
WIN: coef = 1.80, factor = 1.80
HALF_WIN: coef = 2.00, factor = (2.00 + 1) / 2 = 1.50
For amount = 10:
calculate_coef = 1.80 × 1.50 = 2.70
real_win = 27
Half Loss
Two bets:
WIN: coef = 1.80, factor = 1.80
HALF_LOSE: factor = 0.5
For amount = 10:
calculate_coef = 1.80 × 0.5 = 0.90
real_win = 9
HALF_LOSE does not reduce the accumulator to zero. A full loss with bet status 2 has a multiplier of 0 and reduces the entire coupon to zero.
Always use the final real_win returned by the API for crediting.
Retrieval Through the API
The complete current state is available by coupon code:
COUPON_CODE="000000000350"
curl --request GET \
--url "$BASE_URL/api/partner/coupons/get?coupon_code=$COUPON_CODE" \
--header "Accept: application/json" \
--header "Authorization: Bearer $TOKEN"
When processing the response:
- update the coupon by
coupon_code; - iterate over the complete
events_dataarray; - update bets by
coupon_code + id; - do not treat status
0as final; - use
real_winonly for a final state that has not yet been processed.
Fallback polling is recommended even when callbacks are working:
GET /api/partner/coupons/calculated?time=10
Return for Recalculation
If the accumulator is returned for recalculation:
coupon status = 15
one or more bets have status = 4
If the previous result was already processed financially, on first receiving this state:
- deduct the accumulator
amountonce again; - move the coupon into a state awaiting a new result;
- do not treat status
15as final; - credit the new
real_winafter the next final state.
The repeated deduction is made once for each new transition to status 15, not once for every bet with status 4.
Final Algorithm
1. Verify that 2 to 15 different matches are selected.
2. Show the user one accumulator and one total amount.
3. Submit all pointers with multi = false.
4. With code = 0, do not create an accepted coupon or deduct the amount.
5. With code = 1, store one coupon and all events_data.
6. Deduct amount once.
7. Update the coupon and bets with every new state.
8. Do not credit funds while status = 0.
9. Process real_win for the first financially new final result.
10. Acknowledge a repeated batchId without repeated processing.
11. Perform fallback reconciliation through the API.
Checklist
- The accumulator contains 2 to 15 bets.
- Each bet belongs to a separate match.
- A main event is not combined with any of its sub-events.
multiisfalse.amountapplies to the entire accumulator.- The user sees one total deduction amount.
- The coupon is stored only after
code = 1. - All bets from
events_dataare stored. - One initial deduction is made.
- Every callback signature is verified against the raw request body.
- Intermediate coupon status
0does not trigger a payout. - A new
batchIdupdates the existingcoupon_code. - A repeated
batchIddoes not repeat processing. - The first loss is processed immediately.
- The final full snapshot of a losing accumulator does not create a second financial operation.
HALF_WIN,HALF_LOSE, return, and push are handled using the finalreal_win.- Status
15starts one new result-waiting cycle. - Callbacks are supplemented with fallback polling.
More information:
- Singles, Accumulators, and
multi; - Settlement Lifecycle;
- Statuses and Payout Calculation;
- Result Callbacks;
- Callback Signature Verification;
- Retries and Idempotency.
Next section: Recovery After a Missed Callback.