FortuneX
FortuneX › 运营商 API

FortuneX 运营商 API 对接指南(4D)

版本 1 · 基础路径 /api/b2b/v1 · 所有金额以 仙(sen) 为单位(RM 1.00 = 100),均为整数 · 币种 MYR · 除非字段另有说明,时间均为 Unix 毫秒 · 开奖日期和周期均按马来西亚时间(Asia/Kuala_Lumpur,UTC+8)。

目录

概览

基础地址https://api.fortunex.bet
第 1 版基础路径/api/b2b/v1
API 文档版本1.0.0
OpenAPI3.0.3
签名请求头X-FX-Signature

4D 投注流程

  1. 读取开放的奖池、开奖日期和限额。
  2. 下注。重试时请使用相同的 ref。
  3. 轮询该投注,直到不再是 pending 或 sending。
  4. 开奖即将截止前可取消。
  5. 开奖后读取结果。中奖只是记录,不会自动付款。

下载

供您的开发人员使用。与 FortuneX 工作人员发给新运营商的文件相同。

机器可读的接口描述为同目录下的 operator-openapi.yaml(OpenAPI 3.0)。本指南说明各部分如何配合使用。

1. 本 API 的用途

您可以通过 FortuneX 为您的玩家下 4D 投注,并读取 结果、余额 和 每周对账单。

  • 您的投注通过 FortuneX 的唯一主账户发送给 4D 供应商。您无需在供应商处开设账户。
  • 4D 投注 不会 调用您的钱包。您与 FortuneX 的关系是 预付余额 或 信用账期 之一(第 8 节)。
  • FortuneX 对 4D 不自动收取任何费用。每一笔投注和每一个结果都会被记录,以便您和 FortuneX 与供应商的记录核对。每笔投注都带有您自己的 ref 和 subAccount 用于核对。
  • 结果只是 记录,不派彩。中奖本身不会自动转账(第 9 节)。
  • 没有 Webhook。您需要 轮询 状态和结果。

2. 获取访问权限

FortuneX 工作人员在管理后台为您创建运营商账户和 API 密钥。每个密钥您将获得:

  • 密钥 ID(fxk_...),公开信息;
  • 密钥(secret)(fxs_...),只显示一次。FortuneX 加密保存,无法再次显示。如果遗失,请申请更换密钥。
  • IP 白名单:密钥只能在您提供给 FortuneX 的地址上使用。请告知 FortuneX 您所有的调用地址,包括备用服务器。

您可以另外申请一个 沙盒密钥(第 11 节)用于测试。沙盒密钥不会连到供应商,也不会产生任何资金变动。正式密钥和沙盒密钥是不同的密钥,互相不能替代。

密钥更换:FortuneX 创建新密钥,旧密钥在约定的重叠期内继续有效(默认 7 天),之后失效。工作人员也可以立即吊销密钥。

API 主机为 https://api.fortunex.bet(最终主机以 FortuneX 通知为准)。在 FortuneX 开启运营商 API 之前,所有路径都返回 404 not_found。

3. 为每个请求签名

每个请求(GET 或 POST)都带有四个请求头:

请求头值
X-FX-Key您的密钥 ID
X-FX-Timestamp当前 Unix 秒。必须在 FortuneX 时钟的 5 分钟以内。
X-FX-Nonce8 到 128 个字符,取自 A-Z a-z 0-9 _ - + = . :,每个请求都要新生成。每个 nonce 只能使用一次。
X-FX-SignatureHMAC-SHA256(secret, stringToSign) 的小写十六进制结果

stringToSign 由六行组成,用单个换行符(\n)连接,末尾没有换行:

METHOD            upper case: GET or POST
path              the request path, exactly as sent, without the query
query             the raw query string without the "?"; an empty line if there is none
timestamp         the same value as X-FX-Timestamp
nonce             the same value as X-FX-Nonce
sha256(body)      lower-case hex SHA-256 of the raw body bytes; of the empty string for a GET

即:第 1 行请求方法(大写 GET 或 POST);第 2 行请求路径(与实际发送完全一致,不含查询串);第 3 行原始查询串(不含 "?",没有则为空行);第 4 行时间戳(与 X-FX-Timestamp 相同);第 5 行 nonce(与 X-FX-Nonce 相同);第 6 行请求体原始字节的 SHA-256 小写十六进制(GET 请求为空字符串的哈希)。

容易出错的地方:

  • 对您 实际发送的原始字节 签名。签名后不要重新序列化 JSON。不要对路径或查询串做解码、重排或规范化。
  • 路径或查询串中含有换行符的请求会被拒绝。
  • 没有查询串的请求,第三行为空,所以字符串中会出现连续两个换行符。
  • 请求体使用 Content-Type: application/json 发送。请求体最大 64 KB。

完整示例

密钥(仅为示例,切勿使用):fxs_example_secret_never_use_in_production

请求体(必须与下面的字节完全一致,不要多加空格):

{"ref":"order-1001","subAccount":"site-A-user-77","drawDate":"2026-11-04","pools":["magnum"],"lines":["1234#1"]}
method     POST
path       /api/b2b/v1/4d/bets
query      (empty)
timestamp  1793700000
nonce      0f8e7d6c5b4a3921
sha256(body) = 86b0934218ce3411d21dff00918892f65ae64e1d2ba484d162736827178886ff

待签名字符串:

POST
/api/b2b/v1/4d/bets

1793700000
0f8e7d6c5b4a3921
86b0934218ce3411d21dff00918892f65ae64e1d2ba484d162736827178886ff

签名:5e55fd7bfac0380e225fdc9443f20be43d1e1018323b968263e21a3142981147

带查询串的 GET 示例:GET /api/b2b/v1/4d/bets?drawDate=2026-11-04&limit=20,时间戳 1793700001,nonce 1a2b3c4d5e6f7081,请求体为空,使用同一密钥得到的签名为 53616fcf295cab5289249468d3c92d599592d577ed3d5f9d62293b67350780e1。请用这两组数据作为您实现的测试向量。两组向量中的 nonce(16 位十六进制字符)是人工固定的,目的是让签名可以复现:验证您的代码时,请使用向量给出的时间戳和 nonce,而不是自行生成。实际调用时必须每次生成新的 nonce,如下面的代码所做的那样(24 位十六进制字符)。

代码

Node.js(无依赖):

const crypto = require('crypto');
function signedFetch(base, keyId, secret, method, path, query, bodyObj) {
  const body = bodyObj == null ? '' : JSON.stringify(bodyObj);
  const ts = String(Math.floor(Date.now() / 1000)), nonce = crypto.randomBytes(12).toString('hex');
  const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
  const toSign = [method, path, query || '', ts, nonce, bodyHash].join('\n');
  const sig = crypto.createHmac('sha256', secret).update(toSign).digest('hex');
  return fetch(base + path + (query ? '?' + query : ''), {
    method, body: method === 'GET' ? undefined : body,
    headers: { 'Content-Type': 'application/json', 'X-FX-Key': keyId, 'X-FX-Timestamp': ts, 'X-FX-Nonce': nonce, 'X-FX-Signature': sig },
  });
}

Python 3:

import hashlib, hmac, json, os, time, urllib.request
def signed(base, key_id, secret, method, path, query='', body_obj=None):
    body = b'' if body_obj is None else json.dumps(body_obj, separators=(',', ':')).encode()
    ts, nonce = str(int(time.time())), os.urandom(12).hex()
    to_sign = '\n'.join([method, path, query, ts, nonce, hashlib.sha256(body).hexdigest()])
    sig = hmac.new(secret.encode(), to_sign.encode(), hashlib.sha256).hexdigest()
    req = urllib.request.Request(base + path + ('?' + query if query else ''), data=body or None, method=method,
        headers={'Content-Type': 'application/json', 'X-FX-Key': key_id, 'X-FX-Timestamp': ts, 'X-FX-Nonce': nonce, 'X-FX-Signature': sig})
    return urllib.request.urlopen(req)

4. 响应与错误

成功:{ "ok": true, ... }。失败:{ "ok": false, "error": { "code": "...", "message": "..." } },并带有下表的 HTTP 状态码。请始终根据 code 判断,不要根据 message 文本判断。

认证失败统一返回刻意模糊的 401 unauthorized(密钥错误、签名错误、密钥不存在看起来都一样)。请求签名正确后,才会得到具体的错误代码。

HTTPcode含义
401unauthorized密钥未知或签名错误。请检查待签名字符串。
401key_revoked, key_expired密钥已不能使用。
401stale_timestamp您的时钟与 FortuneX 相差超过 5 分钟。
401replay该 nonce 已被使用。
403ip_not_allowed请求来自密钥白名单之外的地址。
403operator_suspended您的账户已被暂停:不能下新注。读取和取消仍可使用。
403bets_disabledFortuneX 已关闭真实 4D 投注。
403sandbox_key / sandbox_only在正式接口使用了沙盒密钥,或在沙盒接口使用了正式密钥。
404not_found路径不存在(或 API 尚未开启)。
404unknown_bet, unknown_statement在您的账户下 找不到。
400bad_json, bad_ref, bad_sub_account, bad_ticket, bad_date, bad_status, bad_cursor, bad_outcome, bad_win请求格式有误。bad_ticket 的原因写在 message 中(与 FortuneX 玩家看到的文字相同)。
400stake_too_small, stake_too_large投注单总额超出 GET /4d/games 中的限额。
402insufficient_prepaid预付运营商:余额不足以支付本次投注额。
402credit_limit_exceeded信用账期运营商:未结算信用加上本次投注额将超过您的额度。
402credit_not_configured信用账期运营商:4D 尚未设置信用额度。请联系 FortuneX。
409idempotency_mismatch该 ref 已用于另一笔不同的投注。
409game_unavailable您选择的某个彩池已关闭或未开放真实投注。
409capacityFortuneX 的未结投注总额已达上限。请稍后再试。
409cannot_cancel, cancel_pending见第 7 节。
409not_placed, already_settled仅限沙盒结果调用。
409use_4d_api您对 4D 游戏使用了通用投注接口。
405method_not_allowed只使用 GET 和 POST。
429rate_limited, operator_limit请求过快。见第 12 节。
503maintenance, busy请稍后重试。
500internal_error我方问题。请使用相同的 ref 重试(第 5 节)。

5. 幂等性与重试

ref 是您为每笔投注设定的幂等键:1 到 100 个字符,取自 A-Z a-z 0-9 : _ . -。它在每个运营商内唯一(正式和沙盒各自独立)。您也可以改用 Idempotency-Key 请求头传同样的值;如果两者同时存在,必须相同。

  • 用相同的 ref 和相同的投注单再次发送:返回 200 和同一笔投注,不会重复扣款。首次下注返回 201。
  • 用相同的 ref 但投注单不同(开奖日期、彩池、号码行或 subAccount):返回 **409 idempotency_mismatch**。
  • 如果下注请求超时或返回 5xx,请 **使用相同的 ref 重试**。切勿为同一个玩家操作另造新的 ref,否则会产生第二笔投注。
  • 每次重试都需要新的 nonce 和时间戳(这是一个新的签名请求)。
  • 除 429 外的 4xx 响应重试也不会改变结果;请修正请求。

6. 下注

请先通过 GET /4d/games 查看当前开放的彩池、接下来的开奖日期及截止时间,以及限额。

POST /4d/bets

{ "ref": "order-1001", "subAccount": "site-A-user-77", "drawDate": "2026-11-04", "pools": ["magnum", "damacai"], "lines": ["1234#1", "*2345#1"] }
  • subAccount:必填。您自己对该投注背后的玩家或网站的标识(1 到 100 个字符)。它与投注一起保存,并在每个视图中返回,以便 FortuneX 按子账户把您的销售与供应商的记录核对。
  • drawDate:YYYY-MM-DD。某一期的投注在 开奖当日马来西亚时间 18:59 截止。超过 30 天以后的日期会被拒绝。
  • pools:一个或多个:magnum、damacai、toto、singapore、sabah、sarawak、gd(Grand Dragon,豪龙)、nine(9Lotto)。sandakan 目前未在供应商处启用。当前开放的彩池请以 GET /4d/games 为准。
  • lines:1 到 50 行,文字格式与 FortuneX 自己的投注单相同。投注单成本 = 每行投注额 × 该行展开的号码数 × 彩池数量。示例:
号码行含义每个彩池成本
1234#1号码 1234,大(Big),RM 1RM 1.00
1234#1#1大 RM 1 和小(Small)RM 1RM 2.00
1234#1#1#1大、小和 A,各 RM 1RM 3.00
123#1#13D:C RM 1 和 A RM 1RM 2.00
1234**#1#1#1#1#1A1 至 A5 每个奖位 RM 1RM 5.00
*2345#1全排列(box):全部 24 种排列,每种大 RM 1RM 24.00
800020#16D,RM 1RM 1.00

单个号码的最小投注额为 RM 0.10(1234#0.1)。最小和最大投注单金额见 GET /4d/games(limits)。5D 和 6D 投注单遵循 FortuneX 自己的开关,开关关闭时会被拒绝。开关打开时,5D 号码行只能用于彩池 toto,6D 号码行只能用于 toto、gd 或 nine;如果把这类号码行与其他彩池放在同一张投注单里,会被拒绝并返回 400 bad_ticket(提示 "Untick the other pools")。随机("R")号码行必须由您在下注前自行生成。

成本由服务器计算。您不能自行传入价格。

响应 201:

{ "ok": true, "created": true, "bet": { "ref": "order-1001", "subAccount": "site-A-user-77", "sandbox": false, "funding": "prepaid", "drawDate": "2026-11-04",
  "pools": ["magnum"], "lines": ["1234#1"], "stakeSen": 100, "refundedSen": 0, "status": "pending", "statusText": "Waiting for approval", "reason": null,
  "result": { "settled": false, "winSen": null, "wins": null, "settledAt": null }, "canCancel": true, "cancelState": null, "cancelNote": null, "createdAt": 1793700000123, "orders": [] } }

投注生命周期

pending ──approved by FortuneX (or automatically)──▶ sending ──supplier answers──▶ accepted | partial | refused | check
   │                                                                                    │
   ├─ rejected (a reason is given) ─▶ rejected (stake returned)                         ├─ cancelled (you cancelled: stake returned)
   └─ you cancel ─▶ cancelled (stake returned)                                          └─ settled: the result is recorded (win or loss)
status含义投注额
pending等待 FortuneX 发送给供应商。冻结
sending正在发送。冻结
accepted供应商已接受。orders[] 显示供应商参考号。冻结
partial部分号码已售罄;被拒的部分已退回(refundedSen)。部分退回
refused供应商未接受。退回
rejectedFortuneX 未批准(会显示 reason),或在批准前已截止投注。退回
cancelled已取消。退回
check某个订单结果未知,FortuneX 工作人员正在与供应商核实。冻结,直到处理完毕

投注是等待人工批准还是立即发送,由 FortuneX 的设置决定;请轮询 GET /4d/bets/{ref},直到 status 不再是 pending 或 sending。

orders[] 中每个彩池一条:providerRef(FortuneX 就该订单与供应商使用的参考号)、betNo 和 receiptNo(供应商的编号,接受后才有),以及已接受/被拒的金额。与 FortuneX 或供应商对账时请使用这些信息。

查询投注

  • GET /4d/bets/{ref}:查询一笔投注。
  • GET /4d/bets?drawDate=&status=&subAccount=&limit=&before=:您的投注,最新的在前。limit 最大 200(默认 50)。如果 next 不为 null,把它作为 before 传入即可获取下一页。

7. 取消

POST /4d/bets/{ref}/cancel(请求体为空或 {})。规则与 FortuneX 自己的玩家相同:

  • 仍为 pending 的投注:立即取消,全额退回投注额。
  • 已下单的投注(accepted 或 partial),在 开奖即将截止之前(开奖日 18:59 前 2 分钟之内):属于 取消申请。响应显示 cancelState: "requested"。FortuneX 批准(或自动批准)后,供应商逐个订单取消,此时状态才变为 cancelled,投注额才退回。如果供应商拒绝,cancelState 变为 failed 或 declined,并带有 cancelNote,投注保持有效。请轮询该投注。
  • 开奖即将截止之后,或结果已记录之后:409 cannot_cancel。
  • 重复取消:409 cannot_cancel(申请等待期间为 cancel_pending)。

8. 预付账户与信用账期账户

FortuneX 会针对您以及每一类游戏(4D、体育、老虎机、真人娱乐场)分别设置为 预付 或 信用账期。目前通过本 API 只能使用 4D。GET /balance 和 GET /4d/games(funding)会显示适用于您的方式。

预付

  • 您的预付余额由 FortuneX 保管。您付款后,工作人员为您充值(并记录银行参考号)。
  • 下注时投注额 立即扣除。如果余额不足,投注被拒绝并返回 402 insufficient_prepaid,不会留下任何记录。并发下注也不会使余额低于零。
  • 退回的投注额(取消、被拒、售罄部分)只会准确退回余额一次。
  • 中奖 不会 自动派入您的余额。FortuneX 每周以人工方式,参考已记录的销售和结果,把您的利润记入账户。

信用账期

  • 下注时不扣款。该销售会记入您的 信用占用:即销售额减去退款,且尚未被已付款对账单结清的部分。
  • 每一类游戏有各自的 信用额度。如果您的占用加上本次投注额会超过额度,投注会被拒绝并返回 402 credit_limit_exceeded。信用账期游戏 没有额度(或为 0) 时,所有投注都会被拒绝(credit_not_configured)。
  • 每周一,FortuneX 为已结束的一周(周一 00:00 至下周一 00:00,马来西亚时间)出具 每周对账单:总销售、退款、记录的中奖结果、净额以及谁欠谁,付款期限为周期结束后 4 天(天数可调整)。对账单一经出具即为最终版。资金到账后,由 FortuneX 工作人员记录付款,您的信用额度随之恢复。
  • GET /statements 列出您的对账单;GET /statements/{number} 查看一份。均为只读。

GET /balance:

{ "ok": true, "balance": { "sandbox": false, "currency": "MYR", "prepaidBalanceSen": 250000,
  "games": [ { "category": "4d", "mode": "credit", "creditLimitSen": 1000000, "exposureSen": 420000, "availableSen": 580000 },
             { "category": "sports", "mode": "prepaid", "creditLimitSen": null, "exposureSen": null, "availableSen": null } ], "asOf": 1793700000123 } }

9. 结果

结果在开奖后根据供应商的官方结果自动记录,规则与 FortuneX 自己的玩家相同(使用 FortuneX 公布的赔率表;6D 若一个号码符合多个奖项,只按 最高奖项 赔付)。部分情况需要人工处理(供应商不公布的 6D 头奖号码、非常大额的中奖、需要核对的订单);此时该投注保持未结算,直到录入结果。

  • GET /4d/bets/{ref}/result:state 为 pending(尚未送达供应商,或正在核对)、open(已下单,未截止开奖)、awaiting_result(已截止,结果尚未记录)、settled(见 winSen 和 wins[],含奖项与号码)或 void(已退款或从未下单)。
  • GET /4d/results/{YYYY-MM-DD}:该期各彩池的官方号码(未公布则为 null)、已知的 6D 头奖号码、您 在该开奖日的投注(含投注额和 winSen)以及合计。

中奖不会产生任何资金转移。 它只是一条记录。预付账户每周由人工入账;信用账期账户则在每周对账单上结算净额。winSen 是按 FortuneX 赔率表计算出的该笔投注的中奖金额。

10. 对账

每笔投注都带有您的 ref、您的 subAccount、FortuneX 为每个订单生成的 providerRef,以及供应商的 betNo/receiptNo。查看某一天或某一期,请使用 GET /4d/bets?drawDate= 或 GET /4d/results/{date}。对账单 CSV(由 FortuneX 工作人员提供)列出每一笔销售、退款和结果,含时间、ref 和子账户。

11. 沙盒

沙盒密钥的行为与正式密钥相同,但投注是 模拟的:

  • 签名方式相同,/4d/*、GET /balance 和 GET /health 等接口相同,并带有 "sandbox": true 标记。其他所有正式接口返回 403 sandbox_key。
  • 沙盒投注使用与正式投注相同的投注单规则、限额和开放彩池进行检查,然后 立即接受。它从不发送给供应商,不影响任何余额、信用额度或对账单。
  • 沙盒的 ref 与正式的 ref 相互独立。
  • 模拟供应商拒绝:使用以 sbx-refuse 开头的 subAccount。投注变为 refused,投注额"退回"(refundedSen)。
  • 模拟预付余额不足:使用以 sbx-nofunds 开头的 subAccount,返回 402 insufficient_prepaid。
  • 模拟结果:POST /sandbox/4d/bets/{ref}/result,请求体 { "outcome": "win", "winSen": 350000 }(winSen 默认为投注额的十倍)或 { "outcome": "loss" }。仅适用于状态为 accepted 的沙盒投注,且只能一次;之后 GET .../result 显示 state: "settled" 和 simulated: true。
  • 沙盒取消是即时的。
  • 正式密钥不能调用 /sandbox/*(403 sandbox_only)。

12. 限制

  • 每个 IP 地址:每分钟 600 个请求(响应 429 rate_limited,并带有 Retry-After 响应头)。
  • 每个密钥:按您的密钥所设限制(默认每分钟 600 个请求)。
  • 每个运营商:每分钟最多 600 笔新投注(429 operator_limit)。正式和沙盒投注分开计算:沙盒投注不会占用正式限额。
  • 请求体最大 64 KB。
  • 工作人员可限制所有投注的全局未结投注总额(409 capacity)。

遇到 429 时,等待 Retry-After 指定的秒数,再用新的 nonce 重试同一请求。

13. 安全清单

  • 把密钥保存在密钥管理系统中,切勿放在浏览器或手机端代码里。只从您的服务器端签名请求。
  • 只从白名单中的 IP 地址调用;地址变更前请先通知 FortuneX。
  • 只使用 HTTPS,并验证服务器证书。
  • nonce 要不可预测且绝不重复使用;保持您的系统时钟准确(NTP)。
  • ref 要防止重复:由您自己的订单号生成,这样重试时会复用同一个 ref。
  • 记录请求 ID 和 ref,切勿记录密钥或签名。

14. 本 API 目前不提供的功能

Webhook(请改用轮询)、体育、老虎机和真人娱乐场投注,以及任何形式的资金转移:付款、充值和利润入账均由 FortuneX 工作人员在 API 之外记录。

接口参考

下面的接口参考由 OpenAPI 文件生成,内容为英文。

Account

GET /api/b2b/v1/health

Signed ping

响应
200 OK
响应结构(示例值)
{
  "ok": true,
  "time": 1793700000123,
  "operatorId": 1,
  "keyId": "fxk_example",
  "sandbox": true
}
错误 Failure. error.code is stable; message is for people. Common codes: unauthorized, stale_timestamp, replay, ip_not_allowed (401/403); bad_ticket, bad_ref, bad_sub_account, bad_date, stake_too_small, stake_too_large (400);
{
  "ok": false,
  "error": {
    "code": "insufficient_prepaid",
    "message": "The balance does not cover this stake."
  }
}

GET /api/b2b/v1/balance

Your prepaid balance and credit position

响应
200 OK
响应结构(示例值)
{
  "ok": true,
  "balance": {
    "sandbox": true,
    "currency": "MYR",
    "prepaidBalanceSen": 1793700000123,
    "games": [
      {
        "category": "4d",
        "mode": "prepaid",
        "creditLimitSen": 1793700000123,
        "exposureSen": 1793700000123,
        "availableSen": 1793700000123
      }
    ],
    "asOf": 1793700000123,
    "note": "string"
  }
}
错误 Failure. error.code is stable; message is for people. Common codes: unauthorized, stale_timestamp, replay, ip_not_allowed (401/403); bad_ticket, bad_ref, bad_sub_account, bad_date, stake_too_small, stake_too_large (400);
{
  "ok": false,
  "error": {
    "code": "insufficient_prepaid",
    "message": "The balance does not cover this stake."
  }
}

4D bets

GET /api/b2b/v1/4d/games

Pools, draw dates, minimum stakes and limits

响应
200 OK
响应结构(示例值) · GamesInfo
{
  "ok": true,
  "sandbox": true,
  "enabled": true,
  "funding": "prepaid",
  "currency": "string",
  "games": [
    {
      "id": "magnum",
      "name": "string",
      "open": true,
      "reason": "string",
      "supports5d6d": true
    }
  ],
  "drawDates": [
    {
      "drawDate": "2026-11-04",
      "day": "string",
      "closesAt": "2026-11-04T10:59:00Z",
      "pools": [
        "string"
      ],
      "special": true
    }
  ],
  "limits": {
    "minTicketSen": 100,
    "maxTicketSen": 1793700000123,
    "minStakePerNumberSen": 100,
    "maxLines": 1,
    "closesAt": "string",
    "cancelUntil": "string"
  }
}
错误 Failure. error.code is stable; message is for people. Common codes: unauthorized, stale_timestamp, replay, ip_not_allowed (401/403); bad_ticket, bad_ref, bad_sub_account, bad_date, stake_too_small, stake_too_large (400);
{
  "ok": false,
  "error": {
    "code": "insufficient_prepaid",
    "message": "The balance does not cover this stake."
  }
}

GET /api/b2b/v1/4d/bets

List your bets, newest first

参数
名称位置必填类型说明
drawDate查询否string (date)
status查询否pending | sending | accepted | partial | refused | rejected | cancelled | check
subAccount查询否stringmax length 100
limit查询否integermin 1, max 200, default 50
before查询否integerPass next from the previous page.
响应
200 OK
响应结构(示例值)
{
  "ok": true,
  "bets": [
    {
      "ref": "string",
      "subAccount": "string",
      "sandbox": true,
      "funding": "prepaid",
      "drawDate": "2026-11-04",
      "pools": [
        "string"
      ],
      "lines": [
        "string"
      ],
      "stakeSen": 100,
      "refundedSen": 100,
      "status": "pending",
      "statusText": "string",
      "reason": "string",
      "result": {
        "settled": true,
        "winSen": 1793700000123,
        "wins": [
          {}
        ],
        "settledAt": 1793700000123
      },
      "canCancel": true,
      "cancelState": "requested",
      "cancelNote": "string",
      "createdAt": 1793700000123,
      "orders": [
        {
          "pool": "string",
          "status": "sending",
          "stakeSen": 100,
          "acceptedSen": 1793700000123,
          "rejectedSen": 1793700000123,
          "providerRef": "string",
          "betNo": 1,
          "receiptNo": 1
        }
      ]
    }
  ],
  "next": 1
}
错误 Failure. error.code is stable; message is for people. Common codes: unauthorized, stale_timestamp, replay, ip_not_allowed (401/403); bad_ticket, bad_ref, bad_sub_account, bad_date, stake_too_small, stake_too_large (400);
{
  "ok": false,
  "error": {
    "code": "insufficient_prepaid",
    "message": "The balance does not cover this stake."
  }
}

POST /api/b2b/v1/4d/bets

Place a 4D bet

Prepaid: the stake is deducted at once (402 insufficient_prepaid if short). Credit term: the sale is recorded against your credit (402 credit_limit_exceeded / credit_not_configured). Sandbox key: the bet is simulated (see the Sandbox tag).

参数
名称位置必填类型说明
Idempotency-Key请求头否stringmax length 100
请求体 · PlaceBet
{
  "ref": "order-1001",
  "subAccount": "site-A-user-77",
  "drawDate": "2026-11-04",
  "pools": [
    "magnum"
  ],
  "lines": [
    "1234#1"
  ]
}
响应
200 The same ref was already placed with this ticket (nothing charged again)
响应结构(示例值) · BetEnvelope
{
  "ok": true,
  "created": true,
  "bet": {
    "ref": "string",
    "subAccount": "string",
    "sandbox": true,
    "funding": "prepaid",
    "drawDate": "2026-11-04",
    "pools": [
      "string"
    ],
    "lines": [
      "string"
    ],
    "stakeSen": 100,
    "refundedSen": 100,
    "status": "pending",
    "statusText": "string",
    "reason": "string",
    "result": {
      "settled": true,
      "winSen": 1793700000123,
      "wins": [
        {}
      ],
      "settledAt": 1793700000123
    },
    "canCancel": true,
    "cancelState": "requested",
    "cancelNote": "string",
    "createdAt": 1793700000123,
    "orders": [
      {
        "pool": "string",
        "status": "sending",
        "stakeSen": 100,
        "acceptedSen": 1793700000123,
        "rejectedSen": 1793700000123,
        "providerRef": "string",
        "betNo": 1,
        "receiptNo": 1
      }
    ]
  }
}
201 Placed
响应结构(示例值) · BetEnvelope
{
  "ok": true,
  "created": true,
  "bet": {
    "ref": "string",
    "subAccount": "string",
    "sandbox": true,
    "funding": "prepaid",
    "drawDate": "2026-11-04",
    "pools": [
      "string"
    ],
    "lines": [
      "string"
    ],
    "stakeSen": 100,
    "refundedSen": 100,
    "status": "pending",
    "statusText": "string",
    "reason": "string",
    "result": {
      "settled": true,
      "winSen": 1793700000123,
      "wins": [
        {}
      ],
      "settledAt": 1793700000123
    },
    "canCancel": true,
    "cancelState": "requested",
    "cancelNote": "string",
    "createdAt": 1793700000123,
    "orders": [
      {
        "pool": "string",
        "status": "sending",
        "stakeSen": 100,
        "acceptedSen": 1793700000123,
        "rejectedSen": 1793700000123,
        "providerRef": "string",
        "betNo": 1,
        "receiptNo": 1
      }
    ]
  }
}
错误 Failure. error.code is stable; message is for people. Common codes: unauthorized, stale_timestamp, replay, ip_not_allowed (401/403); bad_ticket, bad_ref, bad_sub_account, bad_date, stake_too_small, stake_too_large (400);
{
  "ok": false,
  "error": {
    "code": "insufficient_prepaid",
    "message": "The balance does not cover this stake."
  }
}

GET /api/b2b/v1/4d/bets/{ref}

One bet with its orders

参数
名称位置必填类型说明
ref路径是stringYour bet reference. pattern ^[A-Za-z0-9:_.-]{1,100}$
响应
200 OK
响应结构(示例值) · BetEnvelope
{
  "ok": true,
  "created": true,
  "bet": {
    "ref": "string",
    "subAccount": "string",
    "sandbox": true,
    "funding": "prepaid",
    "drawDate": "2026-11-04",
    "pools": [
      "string"
    ],
    "lines": [
      "string"
    ],
    "stakeSen": 100,
    "refundedSen": 100,
    "status": "pending",
    "statusText": "string",
    "reason": "string",
    "result": {
      "settled": true,
      "winSen": 1793700000123,
      "wins": [
        {}
      ],
      "settledAt": 1793700000123
    },
    "canCancel": true,
    "cancelState": "requested",
    "cancelNote": "string",
    "createdAt": 1793700000123,
    "orders": [
      {
        "pool": "string",
        "status": "sending",
        "stakeSen": 100,
        "acceptedSen": 1793700000123,
        "rejectedSen": 1793700000123,
        "providerRef": "string",
        "betNo": 1,
        "receiptNo": 1
      }
    ]
  }
}
错误 Failure. error.code is stable; message is for people. Common codes: unauthorized, stale_timestamp, replay, ip_not_allowed (401/403); bad_ticket, bad_ref, bad_sub_account, bad_date, stake_too_small, stake_too_large (400);
{
  "ok": false,
  "error": {
    "code": "insufficient_prepaid",
    "message": "The balance does not cover this stake."
  }
}

POST /api/b2b/v1/4d/bets/{ref}/cancel

Cancel a bet

参数
名称位置必填类型说明
ref路径是stringYour bet reference. pattern ^[A-Za-z0-9:_.-]{1,100}$
请求体 (可选)
{}
响应
200 The bet after the cancel (or the request)
响应结构(示例值) · BetEnvelope
{
  "ok": true,
  "created": true,
  "bet": {
    "ref": "string",
    "subAccount": "string",
    "sandbox": true,
    "funding": "prepaid",
    "drawDate": "2026-11-04",
    "pools": [
      "string"
    ],
    "lines": [
      "string"
    ],
    "stakeSen": 100,
    "refundedSen": 100,
    "status": "pending",
    "statusText": "string",
    "reason": "string",
    "result": {
      "settled": true,
      "winSen": 1793700000123,
      "wins": [
        {}
      ],
      "settledAt": 1793700000123
    },
    "canCancel": true,
    "cancelState": "requested",
    "cancelNote": "string",
    "createdAt": 1793700000123,
    "orders": [
      {
        "pool": "string",
        "status": "sending",
        "stakeSen": 100,
        "acceptedSen": 1793700000123,
        "rejectedSen": 1793700000123,
        "providerRef": "string",
        "betNo": 1,
        "receiptNo": 1
      }
    ]
  }
}
错误 Failure. error.code is stable; message is for people. Common codes: unauthorized, stale_timestamp, replay, ip_not_allowed (401/403); bad_ticket, bad_ref, bad_sub_account, bad_date, stake_too_small, stake_too_large (400);
{
  "ok": false,
  "error": {
    "code": "insufficient_prepaid",
    "message": "The balance does not cover this stake."
  }
}

Results

GET /api/b2b/v1/4d/bets/{ref}/result

The result of one bet

参数
名称位置必填类型说明
ref路径是stringYour bet reference. pattern ^[A-Za-z0-9:_.-]{1,100}$
响应
200 OK
响应结构(示例值)
{
  "ok": true,
  "result": {
    "ref": "string",
    "drawDate": "2026-11-04",
    "state": "pending",
    "closed": true,
    "status": "pending",
    "stakeSen": 100,
    "refundedSen": 100,
    "settled": true,
    "winSen": 1793700000123,
    "wins": [
      {}
    ],
    "settledAt": 1793700000123,
    "simulated": true
  }
}
错误 Failure. error.code is stable; message is for people. Common codes: unauthorized, stale_timestamp, replay, ip_not_allowed (401/403); bad_ticket, bad_ref, bad_sub_account, bad_date, stake_too_small, stake_too_large (400);
{
  "ok": false,
  "error": {
    "code": "insufficient_prepaid",
    "message": "The balance does not cover this stake."
  }
}

GET /api/b2b/v1/4d/results/{drawDate}

The official numbers of a draw and your bets of that date

参数
名称位置必填类型说明
drawDate路径是string (date)
响应
200 OK
响应结构(示例值) · DrawResults
{
  "ok": true,
  "drawDate": "2026-11-04",
  "closed": true,
  "pools": {
    "example": {
      "name": "string",
      "first": "string",
      "second": "string",
      "third": "string",
      "special": [
        "string"
      ],
      "consolation": [
        "string"
      ],
      "source": "string"
    }
  },
  "sixFirst": {
    "example": "string"
  },
  "bets": [
    {
      "ref": "string",
      "subAccount": "string",
      "status": "pending",
      "stakeSen": 100,
      "refundedSen": 100,
      "settled": true,
      "winSen": 1793700000123
    }
  ],
  "totals": {
    "bets": 1,
    "stakeSen": 100,
    "refundedSen": 100,
    "settled": 1,
    "awaiting": 1,
    "winSen": 100
  }
}
错误 Failure. error.code is stable; message is for people. Common codes: unauthorized, stale_timestamp, replay, ip_not_allowed (401/403); bad_ticket, bad_ref, bad_sub_account, bad_date, stake_too_small, stake_too_large (400);
{
  "ok": false,
  "error": {
    "code": "insufficient_prepaid",
    "message": "The balance does not cover this stake."
  }
}

Statements

GET /api/b2b/v1/statements

Your weekly credit statements (production keys, read only)

响应
200 OK
响应结构(示例值)
{
  "ok": true,
  "statements": [
    {
      "number": "FXS-acme-20261026",
      "periodStart": 1793700000123,
      "periodEnd": 1793700000123,
      "periodLabel": "2026-10-26 to 2026-11-01",
      "salesSen": 100,
      "refundsSen": 100,
      "winsSen": 100,
      "netSen": 1793700000123,
      "saleCount": 1,
      "refundCount": 1,
      "resultCount": 1,
      "winCount": 1,
      "byCategory": {
        "example": {
          "salesSen": 100,
          "refundsSen": 100,
          "winsSen": 100,
          "netSen": 1793700000123
        }
      },
      "owes": "operator",
      "amountSen": 100,
      "dueAt": 1793700000123,
      "status": "issued",
      "overdue": true,
      "payment": {
        "direction": "received",
        "amountSen": 100,
        "reference": "string",
        "recordedAt": 1793700000123
      },
      "issuedAt": 1793700000123
    }
  ]
}
错误 Failure. error.code is stable; message is for people. Common codes: unauthorized, stale_timestamp, replay, ip_not_allowed (401/403); bad_ticket, bad_ref, bad_sub_account, bad_date, stake_too_small, stake_too_large (400);
{
  "ok": false,
  "error": {
    "code": "insufficient_prepaid",
    "message": "The balance does not cover this stake."
  }
}

GET /api/b2b/v1/statements/{number}

One statement

参数
名称位置必填类型说明
number路径是stringThe statement number (for example FXS-acme-20261026) or its id.
响应
200 OK
响应结构(示例值)
{
  "ok": true,
  "statement": {
    "number": "FXS-acme-20261026",
    "periodStart": 1793700000123,
    "periodEnd": 1793700000123,
    "periodLabel": "2026-10-26 to 2026-11-01",
    "salesSen": 100,
    "refundsSen": 100,
    "winsSen": 100,
    "netSen": 1793700000123,
    "saleCount": 1,
    "refundCount": 1,
    "resultCount": 1,
    "winCount": 1,
    "byCategory": {
      "example": {
        "salesSen": 100,
        "refundsSen": 100,
        "winsSen": 100,
        "netSen": 1793700000123
      }
    },
    "owes": "operator",
    "amountSen": 100,
    "dueAt": 1793700000123,
    "status": "issued",
    "overdue": true,
    "payment": {
      "direction": "received",
      "amountSen": 100,
      "reference": "string",
      "recordedAt": 1793700000123
    },
    "issuedAt": 1793700000123
  }
}
错误 Failure. error.code is stable; message is for people. Common codes: unauthorized, stale_timestamp, replay, ip_not_allowed (401/403); bad_ticket, bad_ref, bad_sub_account, bad_date, stake_too_small, stake_too_large (400);
{
  "ok": false,
  "error": {
    "code": "insufficient_prepaid",
    "message": "The balance does not cover this stake."
  }
}

Sandbox

POST /api/b2b/v1/sandbox/4d/bets/{ref}/result

Simulate the result of a sandbox bet (sandbox key only)

参数
名称位置必填类型说明
ref路径是stringYour bet reference. pattern ^[A-Za-z0-9:_.-]{1,100}$
请求体
{
  "outcome": "win",
  "winSen": 350000
}
响应
200 The bet, now settled
响应结构(示例值) · BetEnvelope
{
  "ok": true,
  "created": true,
  "bet": {
    "ref": "string",
    "subAccount": "string",
    "sandbox": true,
    "funding": "prepaid",
    "drawDate": "2026-11-04",
    "pools": [
      "string"
    ],
    "lines": [
      "string"
    ],
    "stakeSen": 100,
    "refundedSen": 100,
    "status": "pending",
    "statusText": "string",
    "reason": "string",
    "result": {
      "settled": true,
      "winSen": 1793700000123,
      "wins": [
        {}
      ],
      "settledAt": 1793700000123
    },
    "canCancel": true,
    "cancelState": "requested",
    "cancelNote": "string",
    "createdAt": 1793700000123,
    "orders": [
      {
        "pool": "string",
        "status": "sending",
        "stakeSen": 100,
        "acceptedSen": 1793700000123,
        "rejectedSen": 1793700000123,
        "providerRef": "string",
        "betNo": 1,
        "receiptNo": 1
      }
    ]
  }
}
错误 Failure. error.code is stable; message is for people. Common codes: unauthorized, stale_timestamp, replay, ip_not_allowed (401/403); bad_ticket, bad_ref, bad_sub_account, bad_date, stake_too_small, stake_too_large (400);
{
  "ok": false,
  "error": {
    "code": "insufficient_prepaid",
    "message": "The balance does not cover this stake."
  }
}

数据类型

Sen

Whole sen (RM 1.00 = 100).

integer (int64)

BetStatus

pending | sending | accepted | partial | refused | rejected | cancelled | check

PlaceBet

字段类型说明
ref 必填stringYour idempotency key and reference.
subAccount 必填stringYour own reference for the player or site behind the bet. Stored and returned for matching.
drawDate 必填string (date)Bets close 18:59 Malaysian time on the draw day.
pools 必填array of magnum | damacai | toto | singapore | sabah | sarawak | gd | ninePools to bet on (each pool costs the ticket total once). A 5D line is accepted only with toto; a 6D line only with toto, gd or nine (otherwise 400 bad_ticket).
lines 必填array of stringBet slip lines in the FortuneX text format. Smallest stake on one number is RM 0.10.

Order

字段类型说明
poolstring
statussending | accepted | refused | uncertain | cancelled
stakeSeninteger (sen)Whole sen (RM 1.00 = 100).
acceptedSeninteger (int64), may be nullWhole sen (RM 1.00 = 100).
rejectedSeninteger (int64), may be nullWhole sen (RM 1.00 = 100).
providerRefstringThe reference FortuneX used for this order with the supplier.
betNointeger, may be nullThe supplier's bet number, once accepted.
receiptNointeger, may be null

Bet

字段类型说明
refstring
subAccountstring
sandboxboolean
fundingprepaid | credit | sandbox
drawDatestring (date)
poolsarray of string
linesarray of string
stakeSeninteger (sen)Whole sen (RM 1.00 = 100).
refundedSeninteger (sen)Whole sen (RM 1.00 = 100).
statuspending | sending | accepted | partial | refused | rejected | cancelled | check
statusTextstring
reasonstring, may be null
resultobject
canCancelboolean
cancelStaterequested | cancelling | declined | failed, may be null
cancelNotestring, may be null
createdAtinteger (int64)
ordersarray of Order

BetEnvelope

字段类型说明
okboolean
createdbooleanOnly on placement.
betBet Bet

BetResult

字段类型说明
refstring
drawDatestring (date)
statepending | open | awaiting_result | settled | void
closedboolean
statuspending | sending | accepted | partial | refused | rejected | cancelled | check
stakeSeninteger (sen)Whole sen (RM 1.00 = 100).
refundedSeninteger (sen)Whole sen (RM 1.00 = 100).
settledboolean
winSeninteger (int64), may be nullWhole sen (RM 1.00 = 100).
winsarray of object, may be null
settledAtinteger (int64), may be null
simulatedbooleanTrue for a sandbox bet.

Balance

字段类型说明
sandboxboolean
currencystring
prepaidBalanceSeninteger (int64), may be nullWhole sen (RM 1.00 = 100).
gamesarray of object
asOfinteger (int64)
notestring

GamesInfo

字段类型说明
okboolean
sandboxboolean
enabledbooleanFalse when real 4D bets are off or FortuneX is in maintenance.
fundingprepaid | credit | sandbox
currencystring
gamesarray of object
drawDatesarray of object
limitsobject

DrawResults

字段类型说明
okboolean
drawDatestring (date)
closedboolean
poolsobjectOfficial numbers per pool id, or null when not published yet.
sixFirstobjectThe 6D first prize for gd / nine where known.
betsarray of object
totalsobject

Statement

Final once issued. net = sales - refunds - wins. Positive net: you owe FortuneX; negative: FortuneX owes you.

字段类型说明
numberstring
periodStartinteger (int64)
periodEndinteger (int64)
periodLabelstring
salesSeninteger (sen)Whole sen (RM 1.00 = 100).
refundsSeninteger (sen)Whole sen (RM 1.00 = 100).
winsSeninteger (sen)Whole sen (RM 1.00 = 100).
netSeninteger (int64)
saleCountinteger
refundCountinteger
resultCountinteger
winCountinteger
byCategoryobjectSales, refunds, wins and net per game category (today only 4d).
owesoperator | fortunex | nobody
amountSeninteger (sen)Whole sen (RM 1.00 = 100).
dueAtinteger (int64)
statusissued | paid
overdueboolean
paymentobject, may be null
issuedAtinteger (int64)

版本

本文档描述运营商 API 的 1.0.0 版本。基础路径带有版本号(/v1);版本号来自 OpenAPI 文件,文档更新时本页会重新生成。

返回顶部