Coupon API — JavaScript example for Node.js
This dependency-free example implements the complete flow: find current Sport Line API outcomes, authenticate with Coupon API, place a single, accumulator, or multiple singles, and retrieve their current state.
Requirements and configuration
Use Node.js 20 or newer. Save the example as coupon-example.mjs and provide settings
through environment variables:
export COUPON_API_BASE_URL='https://YOUR_COUPON_API_DOMAIN'
export COUPON_LOGIN='YOUR_LOGIN'
export COUPON_PASSWORD='YOUR_PASSWORD'
export SPORT_LINE_BASE_URL='https://YOUR_SPORT_LINE_DOMAIN'
export SPORT_LINE_PACKAGE='YOUR_SPORT_LINE_API_KEY'
Optional variables:
| Variable | Default | Purpose |
|---|---|---|
COUPON_EXAMPLE_MODE | single | single, express, or multi |
COUPON_AMOUNT | 10 | Stake per request or per single with multi |
COUPON_CURRENCY | USD | Coupon currency |
SPORT_LINE_TYPE | live | live or line |
DOCUMENT_LANGUAGE | en | Sports-line and coupon language |
Never hard-code the login, password, JWT, or sports-line key in this file.
Complete runnable example
The same verified, language-independent source is rendered below for both documentation languages. It uses only standard Node.js APIs.
const requiredEnv = (name) => {
const value = process.env[name]?.trim();
if (!value) throw new Error(`${name} is not configured`);
return value;
};
const couponBaseUrl = requiredEnv('COUPON_API_BASE_URL').replace(/\/$/, '');
const couponLogin = requiredEnv('COUPON_LOGIN');
const couponPassword = requiredEnv('COUPON_PASSWORD');
const sportLineBaseUrl = requiredEnv('SPORT_LINE_BASE_URL').replace(/\/$/, '');
const sportLinePackage = requiredEnv('SPORT_LINE_PACKAGE');
const exampleMode = process.env.COUPON_EXAMPLE_MODE ?? 'single';
const amount = Number(process.env.COUPON_AMOUNT ?? 10);
const currency = process.env.COUPON_CURRENCY ?? 'USD';
const lineType = process.env.SPORT_LINE_TYPE ?? 'live';
const language = process.env.DOCUMENT_LANGUAGE ?? 'en';
if (!['single', 'express', 'multi'].includes(exampleMode)) {
throw new Error('COUPON_EXAMPLE_MODE must be single, express, or multi');
}
if (!['live', 'line'].includes(lineType)) {
throw new Error('SPORT_LINE_TYPE must be live or line');
}
if (!Number.isFinite(amount) || amount <= 0) {
throw new Error('COUPON_AMOUNT must be a positive number');
}
async function requestJson(url, options = {}) {
const response = await fetch(url, {
...options,
signal: AbortSignal.timeout(15_000),
headers: {
Accept: 'application/json',
...options.headers,
},
});
const payload = await response.json().catch(() => null);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${payload?.error_message ?? 'request failed'}`);
}
if (!payload) throw new Error('API returned invalid JSON');
return payload;
}
function requireCouponSuccess(payload, operation) {
if (payload.code !== 1) {
throw new Error(
`${operation}: ${payload.error_code ?? 'unknown'} ${payload.error_message ?? 'error'}`,
);
}
return payload.body;
}
async function login() {
const payload = await requestJson(`${couponBaseUrl}/api/partner/login`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username: couponLogin, password: couponPassword}),
});
return requireCouponSuccess(payload, 'Login').token;
}
async function sportLineGet(path) {
return requestJson(`${sportLineBaseUrl}${path}`, {
headers: {Package: sportLinePackage},
});
}
async function findGames(limit) {
const menu = await sportLineGet(`/v1/menu/${lineType}/${language}`);
const games = new Map();
for (const sport of menu.body ?? []) {
for (const country of sport.sub ?? []) {
for (const tournament of country.sub ?? []) {
const result = await sportLineGet(
`/v1/events/${sport.id}/${tournament.id}/sub/50/${lineType}/${language}`,
);
for (const group of result.body ?? []) {
for (const game of group.events_list ?? []) {
games.set(String(game.game_id), game);
if (games.size >= limit) return [...games.values()];
}
}
}
}
}
throw new Error(`Only ${games.size} suitable events found; ${limit} required`);
}
function findOutcome(value) {
if (Array.isArray(value)) {
for (const item of value) {
const outcome = findOutcome(item);
if (outcome) return outcome;
}
return null;
}
if (!value || typeof value !== 'object') return null;
if (
typeof value.oc_pointer === 'string' &&
Number.isFinite(value.oc_rate) &&
value.oc_rate > 1 &&
(value.oc_block === false || value.oc_block === 0 || value.oc_block == null) &&
value.op_id == null
) {
return value;
}
for (const child of Object.values(value)) {
const outcome = findOutcome(child);
if (outcome) return outcome;
}
return null;
}
async function getSelection(game) {
const event = await sportLineGet(
`/v1/event/${game.game_id}/group/${lineType}/${language}`,
);
const outcome = findOutcome(event.body);
if (!outcome) throw new Error(`No available outcome for game ${game.game_id}`);
const technicalPointer = outcome.oc_pointer.replaceAll('|', '#');
const playerPart = outcome.op_id == null ? '' : `#${outcome.op_id}`;
return {
pointer: `${lineType}#${technicalPointer}#${outcome.oc_rate}${playerPart}`,
gameId: game.game_id,
event: `${game.opp_1_name} — ${game.opp_2_name}`,
market: outcome.oc_group_name,
outcome: outcome.oc_name,
coefficient: outcome.oc_rate,
};
}
async function placeCoupons(token, pointers) {
const payload = await requestJson(`${couponBaseUrl}/api/partner/coupons/place`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
list_bets: pointers,
amount,
currency,
callback_url: null,
lang: language,
mode: 'reject',
mode_type: null,
multi: exampleMode === 'multi',
}),
});
return requireCouponSuccess(payload, 'Coupon placement').coupons;
}
async function couponRequest(token, path, options = {}) {
const payload = await requestJson(`${couponBaseUrl}${path}`, {
...options,
headers: {
Authorization: `Bearer ${token}`,
...options.headers,
},
});
return requireCouponSuccess(payload, path);
}
const couponStatuses = new Map([
[0, 'NEW'], [2, 'WIN'], [4, 'LOSE'], [8, 'RETURN'], [15, 'UPDATE'],
]);
const betStatuses = new Map([
[0, 'NET'], [1, 'WIN'], [2, 'LOSE'], [3, 'RETURN'], [4, 'RECALCULATE'],
[21, 'HALF_WIN'], [22, 'HALF_LOSE'], [23, 'PUSH'],
]);
function summarizeCoupon(coupon) {
return {
couponCode: coupon.coupon_code,
status: coupon.status,
statusName: couponStatuses.get(coupon.status) ?? 'UNKNOWN',
amount: coupon.amount,
coefficient: coupon.coef,
realWin: coupon.real_win,
events: (coupon.events_data ?? []).map((event) => ({
gameId: event.game_id,
bet: event.bet_name,
status: event.status,
statusName: betStatuses.get(event.status) ?? 'UNKNOWN',
})),
};
}
async function main() {
const requiredGames = exampleMode === 'single' ? 1 : 2;
const games = await findGames(requiredGames);
const selections = await Promise.all(games.map(getSelection));
console.log('Selected outcomes:', selections.map(({pointer, ...safe}) => safe));
const token = await login();
const created = await placeCoupons(token, selections.map(({pointer}) => pointer));
console.log('Created coupons:', created.map(summarizeCoupon));
const couponCodes = created.map((coupon) => coupon.coupon_code);
const current = await Promise.all(
couponCodes.map((code) => couponRequest(
token,
`/api/partner/coupons/get?coupon_code=${encodeURIComponent(code)}`,
)),
);
console.log('Current state:', current.map(summarizeCoupon));
const active = await couponRequest(token, '/api/partner/coupons/active');
console.log('Active coupons:', active.map(summarizeCoupon));
const calculated = await couponRequest(
token,
'/api/partner/coupons/calculated?time=120',
);
console.log('Recently settled:', calculated.map(summarizeCoupon));
const batch = await couponRequest(token, '/api/partner/coupons/results', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({coupon_ids: couponCodes}),
});
console.log('Batch result:', batch.coupons.map(summarizeCoupon));
}
main().catch((error) => {
console.error(error.message);
process.exitCode = 1;
});
Run a scenario
Single:
COUPON_EXAMPLE_MODE=single node coupon-example.mjs
Accumulator containing two different events:
COUPON_EXAMPLE_MODE=express node coupon-example.mjs
Two separate singles:
COUPON_EXAMPLE_MODE=multi node coupon-example.mjs
With multi, the complete COUPON_AMOUNT applies to every created single. Do not run
the placement again after a timeout until you have checked whether the first request
was accepted.
See the cURL example, Bet pointer, and Statuses and payouts.