SportAPI Documentation
EN
C Product documentationCoupon API
v1
Service & pricing ↗ Get access ↗
Coupon API / Single-bet integration

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:

FieldValue
list_betsContains one bet, so a single is created.
amountThe single stake is 10.
currencyThe partner currency is USD. Any string identifier is allowed.
callback_urlPartner handler URL.
langNames in the coupon are stored in English.
modereject prevents automatic acceptance of changed odds.
multifalse; 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.coupons contains 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 amount from 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:

  1. stores the coupon from body.coupons[0];
  2. associates it with the user;
  3. stores coupon_code as a string;
  4. stores the bet from events_data[0];
  5. records a debit of amount = 10 from the end user in its own system;
  6. 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_codeCausePartner action
501The odds changed.Show the current odds and request user confirmation when required by the partner flow.
502The outcome is no longer available.Remove or update the outcome in the bet slip.
503The outcome is blocked.Inform the user that placement is temporarily unavailable.
504Outcome validation ended with an error.Do not consider the coupon accepted; allow another attempt later.
507Insufficient 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_code appears 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 statusBet statusResultFinancial action
21WinCredit real_win.
42LossCredit nothing; real_win = 0.
83 or 23Return or pushCredit 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:

  1. verify the HMAC from the original body bytes;
  2. register batchId with a unique constraint;
  3. find the coupon by coupon_code;
  4. find the bet by coupon_code + uuid;
  5. check the coupon status;
  6. update the coupon and bet;
  7. credit realWin = 18.5 once;
  8. commit the transaction;
  9. 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:

  1. debits amount again;
  2. moves the coupon into a state awaiting a new result;
  3. does not treat status 15 as final;
  4. credits the new real_win after 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_bets contains one entry.
  • amount applies to one single.
  • The bet slip is not stored as an accepted coupon before code = 1.
  • A preliminary reservation is released after rejection.
  • coupon_code is stored as a string with leading zeros.
  • Coupon and bet data from the API response are stored.
  • currency is stored from the request when required by the partner.
  • Callback HMAC is verified before JSON parsing.
  • A repeated batchId is not processed financially again.
  • The final result is determined by the coupon status.
  • real_win or realWin is used for crediting.
  • Callbacks are supplemented with fallback polling.
  • Status 15 is handled as waiting for a new result.

Next section: Accumulator integration.