Developers
Build apps that trade on a user's behalf and earn a share of the revenue they generate. Your users authorize your app with OAuth 2.0, you receive a scoped token, and you trade over the real-time /ws/v1 gateway.
/ws/v1 gateway and REST /api/v1/contracts attribute trades to your app when placed with your OAuth-issued token. Your app earns a flat 10% of the net house profit those users generate.| REST API | https://api.miletrades.site |
| WebSocket gateway | wss://api.miletrades.site/ws/v1 |
| OAuth authorize | https://api.miletrades.site/api/v1/oauth/authorize |
| OAuth token | https://api.miletrades.site/api/v1/oauth/token |
app_id and a one-time app_secret.access_token for that user.authorize with the token.proposal) and buy (buy). Track it to settlement.Request only the scopes your app needs.
| read | View markets, balance, portfolio and history. |
| trade | Buy and sell contracts on the user's behalf. |
| payments | Deposit and withdraw from the user's account. |
| admin | Manage API apps and tokens (satisfies any scope check). |
Register on the Developer apps page (or POST https://api.miletrades.site/api/v1/apps with your logged-in session). Provide a name, one or more redirect_uris, and the scopes you need. Revenue-share is a flat platform rate (see Earnings) — you don't set it.
// Response (the secret is shown ONCE — store it securely)
{
"app": {
"app_id": "08123456", // your public client id
"name": "My Trading App",
"redirect_uris": ["https://myapp.com/callback"],
"scopes": ["read", "trade"],
"verified": false,
"created_at": "2026-08-07T..."
},
"app_secret": "mps_xxxxxxxx" // shown once; not needed for the PKCE flow
}MilePredictions uses the authorization-code flow with PKCE (S256). PKCE is required — no client secret is used in the exchange.
function base64url(bytes) {
return btoa(String.fromCharCode(...new Uint8Array(bytes)))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
const verifier = base64url(crypto.getRandomValues(new Uint8Array(32)));
const challenge = base64url(
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))
);
// keep 'verifier' for the token exchange; send 'challenge' nowhttps://api.miletrades.site/api/v1/oauth/authorize
?app_id=YOUR_APP_ID
&redirect_uri=https://myapp.com/callback // must exactly match a registered URI
&response_type=code
&scope=read trade // space- or comma-separated; subset of your app's scopes
&state=RANDOM_ANTI_CSRF
&code_challenge=CHALLENGE
&code_challenge_method=S256The user sees a consent screen, signs in, and approves. On approval they're redirected to redirect_uri?code=mpc_…&state=… (the code is one-time, valid 5 minutes). On denial: ?error=access_denied&state=….
curl -X POST https://api.miletrades.site/api/v1/oauth/token \
-d grant_type=authorization_code \
-d code=mpc_xxxxxxxx \
-d redirect_uri=https://myapp.com/callback \
-d code_verifier=YOUR_VERIFIER \
-d app_id=YOUR_APP_ID
// 200 OK
{
"access_token": "mp_xxxxxxxx", // a scoped, app-bound token (does not expire)
"token_type": "bearer",
"scope": "read trade",
"app_id": "08123456"
}Errors return {"error":"...","error_description":"..."} (e.g. invalid_grant, unsupported_grant_type).
The gateway is a Deriv-style JSON WebSocket. Connect, authorize with the user's token, then call. Pass your app_id as a query param so trades are attributed to your app.
Each request is a JSON object whose first key names the call. Optional envelope fields: req_id (echoed back), subscribe: 1 (stream updates), passthrough. Responses carry msg_type, echo_req, the payload under the msg_type key, and subscription.id when streaming. Errors: {"error":{"code":"...","message":"..."}}.
const ws = new WebSocket("wss://api.miletrades.site/ws/v1?app_id=YOUR_APP_ID");
ws.onopen = () => ws.send(JSON.stringify({ authorize: "mp_USER_TOKEN" }));
ws.onmessage = (ev) => {
const msg = JSON.parse(ev.data);
if (msg.error) return console.error(msg.error.code, msg.error.message);
switch (msg.msg_type) {
case "authorize":
// Ask for a price: "digit over 1" on Volatility 100, $1 stake.
ws.send(JSON.stringify({
proposal: 1, req_id: 1,
contract_type: "DIGITOVER", // OVER/UNDER/MATCH/DIFFER/EVEN/ODD
symbol: "volatility_100", // call active_symbols to list markets
amount: 1.0, // stake in the account's currency
barrier: 1 // 0-9 for over/under & match/differ; omit for even/odd
}));
break;
case "proposal":
// Buy at (up to) the quoted ask price.
ws.send(JSON.stringify({
buy: msg.proposal.id, price: msg.proposal.ask_price, req_id: 2
}));
break;
case "buy":
console.log("Bought", msg.buy.contract_id, "for", msg.buy.buy_price);
// Subscribe to follow it to settlement (~5 ticks later).
ws.send(JSON.stringify({
proposal_open_contract: msg.buy.contract_id, subscribe: 1, req_id: 3
}));
break;
case "proposal_open_contract": {
const c = msg.proposal_open_contract;
if (c.is_sold) console.log("Settled:", c.status, "profit", c.profit);
break;
}
}
};| Call | Scope | Shape |
|---|---|---|
| authorize | — | {"authorize":"mp_..."} |
| active_symbols | — | {"active_symbols":"brief"} |
| ticks | — | {"ticks":"volatility_100","subscribe":1} |
| ticks_history | — | {"ticks_history":"volatility_100","count":100} |
| balance | read | {"balance":1,"account":"real","subscribe":1} |
| portfolio | read | {"portfolio":1} |
| proposal | read | {"proposal":1,"contract_type":"DIGITEVEN","symbol":"volatility_100","amount":1.0} |
| buy | trade | {"buy":"prop_...","price":1.05} |
| proposal_open_contract | read | {"proposal_open_contract":"<id>","subscribe":1} |
| statement / profit_table | read | {"statement":1,"limit":50} |
| forget / forget_all | — | {"forget":"sub_..."} |
DIGITOVER/OVER, DIGITUNDER/UNDER, DIGITMATCH/MATCH, DIGITDIFF/DIFFER, DIGITEVEN/EVEN, DIGITODD/ODD (case-insensitive). Over/under and match/differ take a barrier (0–9); even/odd take none. Contracts run ~5 ticks (≈5s); entry and duration are set by the server.
Rate limit ≈ 30 messages/sec (burst 60) per connection — over-limit returns a RateLimit error. The server sends WebSocket PING frames every 25s; you can also send {"ping":1}. Error codes: InvalidToken, AuthorizationRequired, UnrecognisedRequest, InputValidationFailed, RateLimit, UnknownSymbol, PermissionDenied, InternalError.
Present the token as Authorization: Bearer mp_…. Scope gates apply to token callers. Reads (list/get) need any valid token. Trades placed here are attributed to your app and earn the same 10% revenue-share as the gateway.
| Endpoint | Scope | |
|---|---|---|
| POST | /api/v1/contracts | trade |
| POST | /api/v1/contracts/bulk | trade |
| GET | /api/v1/contracts?status=open|closed | read |
| POST | /api/v1/deposits/{mpesa|ugx|tzs|crypto} | payments |
| POST | /api/v1/withdrawals/{mpesa|ugx|tzs|crypto} | payments |
| POST | /api/v1/accounts | trade |
| POST | /api/v1/accounts/transfer | payments |
| GET | /api/v1/currencies | read |
curl -X POST https://api.miletrades.site/api/v1/contracts \
-H "Authorization: Bearer mp_USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"symbol": "volatility_100",
"contract_type": "over", // even|odd|match|differ|over|under
"stake_cents": 100, // minor units of the wallet currency
"wallet_kind": "real", // "real" | "demo"
"params": { "threshold": 1 } // over/under -> threshold 0-9; match/differ -> digit 0-9; even/odd -> none
}'
// 200 OK
{ "contract": { "id": "...", "status": "pending", "payout_multiplier_bp": 12375, ... },
"new_balance_cents": 41500 }Your app earns a flat 10% of the net house profit (NGR = stake − payouts on settled real-money contracts, when the house wins) generated by the users who trade through your app. This is a platform rate — the same for every app. Attribution happens on both the gateway buy call and REST POST /api/v1/contracts, as long as the trade is placed with your OAuth-issued token (which carries your app_id).
GET https://api.miletrades.site/api/v1/apps/earnings // Authorization: Bearer <your session or admin token>
{
"apps": [
{ "app_id":"08123456", "name":"My Trading App",
"ngr_cents":124500, "commission_cents":12450, "users":42 } // 10% of NGR
]
}Earnings also appear on the Developer apps page and are claimed on Refer & Earn.
| app_id | Public client id — 8-digit numeric string. |
| mps_… | App secret (client secret), shown once at registration. |
| mpc_… | OAuth authorization code — one-time, 5-minute TTL. |
| mp_… | Access token — scoped, app-bound, does not expire. |
Need the exact request/response fields for deposits, withdrawals, or transfers? Ask us and we'll add them here.