Token API

Send the device, get a request_id at once, and follow it to the unlock token.

English
Getting started

01How it works

You read the handset with your own tool and send what you read. You get a request_id at once. You send it back to status.php, and that starts the job on the server you chose; the same call then tells you how it is going, and hands you the unlock token to write to the phone. We never touch the phone, and you never deal with the server.

1Send

token.php checks everything and answers at once with a request_id, such as WT-API-6AF684545B. Nothing is sent to any server yet, and nothing is charged.

2Confirm within 30 seconds

Send the request_id to status.php. That first call is your receipt: only now does the job start. A request_id nobody confirms within 30 seconds is cancelled.

3Collect the token

Ask status.php again every few seconds until state is done — with the token — or error, with the reason and your credit back.

The endpoints

POST token.phpSend a token request: the request_id comes back at once.
POST status.phpConfirm a request_id (this starts the job) and follow it to the token.
POST history.phpYour jobs, by date and service, and how each one ended.
POST balance.phpYour credit and where it went.
POST servers.phpThe servers you may use, and your price on each.

https://xiaomi.source.af/api/reseller/v1/…

02Credentials

Four things identify you, and all four must name the same account. The token travels in the header; the other three travel in the body. Any one of them wrong gives the same unauthorized — we never say which, because that would tell an attacker what to keep.

FieldWhat it is
AuthorizationAccount token — Yours alone. Generate it in your panel under Your credentials, or ask us. We store only a hash of it, so it is shown once and cannot be recovered — generating a new one ends the old one at once.
api_keyAPI key — In the body, on every request.
emailEmail — The address on your account. If it changes, tell us, or the calls stop.
passwordAPI password — Your account's API password. By default it is your sign-in password. If you want a separate one, set it in your panel under Account → API password; Use my sign-in password switches back, and the separate one stops working at once. We can also set it for you. Either way it is never written to our logs and never stored with the job.

Every endpoint is POST over HTTPS. A GET is refused with method_not_allowed; a body that is not JSON is refused with bad_json.

Part 1 · Sending a token request

03The request

POSThttps://xiaomi.source.af/api/reseller/v1/token.php
the complete request
POST /api/reseller/v1/token.php HTTP/1.1
Host: xiaomi.source.af
Content-Type: application/json; charset=utf-8
Accept: application/json
Authorization: Bearer rst_YOUR_ACCOUNT_TOKEN

{
  "api_key":      "YOUR_API_KEY",
  "email":        "you@example.com",
  "password":     "YOUR_PASSWORD",
  "soc":          "frp",
  "serverid":     "server1",
  "configblob":   "eyJ0b2tlbiI6IjhmMjFjMGI0ZTlkNyIsImRldmljZU5hbWUiOiJm…",
  "request_date":     "2026-09-21T14:51:51.980Z",
  "request_language": "English"
}

Every field below goes in the JSON body.

FieldTypeRequiredWhat it is
api_keystringyesYour API key.
emailstringyesThe email on your account.
passwordstringyesYour API password — by default the same as your sign-in password, unless you set a separate one in your panel under Account → API password. See Credentials.
socstringyesWhich service. frp = FRP removal, fdl = Fastboot to EDL. There is no default — omitting it is an error rather than a guess that could charge you for the wrong service.
configblobstringyesThe complete reading of the handset, as base64 of a JSON object. What it must carry depends on soc — see The device blob.
serveridstringnoThe public id of the server that must run the job, e.g. server1 — from servers.php, or from your panel under Servers. Strict: that server runs it, or the call is refused with the reason. Leave it out and we choose the cheapest server taking jobs. Tool owners: always send it. See Choosing a server.
request_datestringnoOptional. When your server sent this request, ISO-8601 with Z or an offset. Echoed back untouched as client_sent and never used in our arithmetic. Leave it out and nothing changes, except that client_sent and client_clock_offset_seconds come back null.
request_languagestringnoOptional. The language for the customer-facing message of a refusal: one of English, Persian, Spanish, French, Turkish, Arabic (or a 2-letter code like en/fa). Omitted or unrecognised → the reply is in English. Send it on every call (token.php, status.php and the reads). The structured fields (such as flash_status, region, takes_on) come back regardless, so you can also build your own sentence in any language.

04The device blob

The blob is base64 of a JSON object carrying the complete reading of the handset — everything your tool reads in that mode, not only the token. What is required depends on soc: for frp the phone is in Mi Assistant mode (sideload / recovery), for fdl it is in Fastboot mode. Send every value exactly as the phone answered it — not shortened, not reformatted.

frp — the phone in Mi Assistant mode (sideload / recovery)

FieldRead withRequiredWhat it isExample
tokengetmitoken:yes — null is refusedThe FRP challenge, exactly as read8f21c0b4e9d7…
deviceNamegetdevice:yes — null is refusedThe codename with its region suffixfleur_global
versiongetversion:yes — null is refusedThe ROM buildOS2.0.207.0.VMTMIXM
serialgetsn:yes — null is refusedThe serial numberfb4d921c
codebasegetcodebase:yes — null if not readThe Android version15
regiongetregion:yes — null if not readThe ROM regionIR
cpuidgetcpuid:yes — null if not readThe CPU id, when the phone answers it0x000460E1
recoverygetrecoveryversion:yes — null if not readThe recovery version — used only by our list of unsupported devices2
branchgetbranch:no — null is fineThe ROM branchF
languagegetlanguage:no — null is fineThe device language — used only by our list of unsupported devicesfa
romzonegetromzone:no — null is fineThe ROM zone — used only by our list of unsupported devices2
frp · before base64
{
  "token":      "8f21c0b4e9d7…",
  "deviceName": "fleur_global",
  "version":    "OS2.0.207.0.VMTMIXM",
  "serial":     "fb4d921c",
  "codebase":   "15",
  "region":     "IR",
  "cpuid":      "0x000460E1",
  "recovery":   "2",
  "branch":     "F",
  "language":   "fa"
}

For frp, do not send productName. It is ignored: we derive the product from deviceName (fleur_global → fleur), exactly as our own tool does. The ROM build goes in version. An earlier version of this page said to put the ROM build in productName — if your client does, move it to version.

fdl — the phone in Fastboot mode

FieldRead withRequiredWhat it isExample
tokenoem get_token
(getvar:token)
yes — null is refusedThe fastboot token, exactly as readVQHzN2Ib…
productgetvar:productyes — null is refusedThe codename, without a regionfleur
serialgetvar:serialnoyes — null is refusedThe serial numberfb4d921c
cpuidgetvar:cpuidyes — null if not readThe CPU id, when the phone answers it0x000460E1
fdl · before base64
{
  "token":   "VQHzN2Ib…",
  "product": "fleur",
  "serial":  "fb4d921c",
  "cpuid":   "0x000460E1"
}

Every field may be sent as null (JSON null or the text "null") when the phone could not read it — never unknown, N/A or an empty text. What happens next depends on the field: marked yes — null is refused, the servers build the job from it, so the request is refused with incomplete_configblob, the field is named in nulls and nothing is charged — read the phone again and send the complete information; marked yes — null if not read, it must always be in the blob, and a null runs the job as usual (left out altogether, it is refused); marked no — null is fine, send it as null or leave it out — the job runs. A job sent with null fields runs on what you sent: if its token then does not work on the phone because a field was not read, it cannot be refunded. Send every field the phone answers.

cpuid is needed by some servers to check the device’s region. Always send it when the phone gives one (in Mi Assistant mode the 0x… serial is the CPU ID). Left out, it is taken as null and the job goes on.

frp · region null
{
  "token":      ",VQEBHQEQNzczMmNmNjE0MzcxMTgwZAIE1UIc…",
  "deviceName": "fog",
  "version":    "V13.0.8.0.SGEMIXM",
  "serial":     "0xd5421c9d",
  "codebase":   "12",
  "region":     null,
  "cpuid":      "0xd5421c9d",
  "recovery":   "2",
  "branch":     "F",
  "language":   "fa-IR"
}

Other names we accept for the same fields: authKey or auth_key for token; devicename or device_name for deviceName; osVersion or os_version for version; serial_number or serialNumber for serial (for fdl also serialno); productName or productname for product; cpu_id for cpuid.

Checked before anything is charged

Everything is checked before anything is reserved or charged. A required field (yes — null is refused) that is left out is named in missing; one sent as null is named in nulls; one that holds a failed reading — N/A, unknown, none, an error text — is named in invalid. Each is refused with HTTP 400 incomplete_configblob, with the mode the phone must be in as mode: Mi Assistant or Fastboot. Send the optional fields whenever the phone answers them; one that is missing, null or holds a failed reading is simply left out and never passed on — it does not stop the job. A blob whose token is missing or null is answered with missing_token.

incomplete_configblob
HTTP/1.1 400 Bad Request

{
  "status":  400,
  "error":   "incomplete_configblob",
  "message": "The field deviceName (null) is required and must be sent filled in — the server needs it to run this job. Read the device again and send the complete information. Nothing was charged. (configblob is not complete for frp (device in Mi Assistant mode) -- null: deviceName; missing: region. A field the phone does not report is sent as null, e.g. \"region\": null.)",
  "missing": ["region"],
  "invalid": [],
  "nulls":   ["deviceName"],
  "mode":    "Mi Assistant",
  "data":    { "state":"error", "start_date":{ … }, "end_date":{ … }, "response_date":{ … } }
}

# No request_id was given: nothing was created, held or charged.

We build each server's own upstream format from these fields ourselves, so you never send a shape that belongs to one particular server — and a different serverid never changes what you send.

What you put in, and what you get back:

You sendWe returnMeaning
deviceNamedevice_nameThe codename with its region suffix (frp)
—product_modelThe codename, without a region — we derive it from deviceName (frp)
productproduct_model
device_name
The codename, without a region (fdl)
versiondevice_versionThe ROM build (frp)
serialserial_numberThe serial number
codebasecodebaseThe Android version (frp)
branchbranchThe ROM branch (frp)
regionregionThe ROM region (frp)
cpuidcpu_idThe CPU id, when the phone answers it
token—Not repeated in device

A field that does not apply to the service comes back as null rather than missing, so one parser reads both services.

05Supported devices

Some build numbers are not supported, and some countries may not be supported either. To know which, ask the admin on Telegram or WhatsApp once you have your tool owner API.

A device on our own list of unsupported devices is refused by token.php at once with unsupported (422): no request_id, nothing sent to any server, nothing charged. The list can test every field of the configblob — model, firmware, Android version, branch, language, region, recovery version, ROM zone, serial and CPU id — so send everything your tool read.

Be careful: sending a model that is not supported costs you its credit — it is not refunded — and it can sometimes get your account banned.

The same unsupported device again and again: every refusal of a device as unsupported (unsupported) is counted for your account and that device over 5 minutes. The 4th refusal carries a warning in its message and strike.warning true (strike = {count, max, window_seconds, warning} is on every such refusal): sending this device again will block your account. One more refusal of it inside those 5 minutes restricts your account for 5 minutes: every new request is answered account_restricted (403). Stop at the warning and offer your customer a server that takes the device.

When a device is refused for its region and one of your servers is premium for that region, the unsupported refusal carries region, suggested, premium (as in check.php) and takes_on, and its message names that server: “Your device’s region is CN. This device must be done with Server 6.3 — a premium server: its FRP removal success rate for this device is very high.” Nothing was charged.

When the premium servers for the region are all off or out of free slots, nothing is suggested (suggested is null), slots_full is true and message says so: “★ Server 6.3 supports your device’s region AE, but its slots are temporarily full. Please check back a little later, or follow live server updates and slot openings on our Telegram channel …” Show it and let your customer try again later, rather than sending the device to a server that does not take its region. A premium server is always named with its star (★).

Ask the admin: Telegram: @RaminGSM WhatsApp: +93799691689

06Choosing a server

Every server has a public id that we gave it — server1, server2… — and a name, such as Server 1. Both are listed by servers.php, as serverid and name, and in your panel under Servers. To run a job on one, send its id as serverid.

the two cases
# 1. name the server -- exactly this one, or an error saying why not
{ "soc":"frp", "configblob":"…", "serverid":"server1" }

# 2. leave it out -- we take the cheapest server taking jobs
{ "soc":"frp", "configblob":"…" }

# every answer names the server that runs the job
-> { "data": { "request_id":"WT-API-6AF684545B", "serverid":"server1", "server_name":"Server 1", … } }

Leave serverid out and we choose for you: the cheapest server taking jobs that your account may use. Name one and it is strict: that server runs the job, or the call is refused — unknown_serverid (404) when no server has that id, server_not_allowed (403) when it is not enabled for your account, server_offline (503) when it cannot take this service right now. We never switch you to another server behind your back.

Some servers are made of several machines that can be chosen one by one. servers.php lists each machine as a server of its own, with its own price — for example serverid server6-2, name Server 6.2. Send that serverid and the job runs on exactly that machine, at its price, or is refused with the reason. Send the server's own id (server6) and we use the cheapest of its machines that is taking jobs.

We check the server before anything is reserved or charged. If it cannot take the job you get server_offline and nothing is spent — pick another from servers.php and send again, or leave serverid out and let us choose.

server_offline
{ "soc":"frp", "configblob":"…", "serverid":"server2" }

-> HTTP 503
   {
     "status":  503,
     "error":   "server_offline",
     "message": "The selected server is off right now (FRP). Use one of the other servers (servers.php lists them) or try again later.",
     "data":    { "state":"error", … }
   }

# No request_id was given: nothing was created, held or charged.
# We never move the request to another server for you.

Tool owners: always send serverid. Read the list with servers.php (or under Servers in your panel), let your customer pick, and send that id. The token then comes from that server, or the answer tells you exactly why not — it is never fetched from a different server.

Every answer names the server that runs the job, as serverid and server_name — also when you let us choose — so a slow server can be told apart from a slow device.

07Before you get a request_id

token.php gives a request_id only when every check below passes, in this order. The first check that fails is your answer — its error code — and there is no request_id: nothing was created, held or charged.

#What we checkIf it fails
1The request is a POST with a JSON body of at most 256 KB.method_not_allowed 405
bad_json 400
body_too_large 413
2Your four credentials name the same active account: the account token, api_key, email and password.unauthorized 401
too_many_failures 429
3The request comes from an address on your account’s allowed list, when you have set one.ip_not_allowed 403
4You are within your hourly request limit.rate_limited 429
The account is not restricted for sending an unsupported device again and again (see Supported devices).account_restricted 403
5soc is frp or fdl, that service is sold to your account, and it is switched on.missing_soc 400
bad_service 400
not_entitled 403
disabled 503
6configblob is valid and carries the complete reading the phone’s mode needs.missing_configblob 400
bad_configblob 400
missing_token 400
incomplete_configblob 400
7The device is not on our list of devices we do not serve.unsupported 422
8The server you named in serverid exists, is enabled for your account, and is taking jobs right now: switched on, priced, online and ready on our side.unknown_serverid 404
server_not_allowed 403
server_offline 503
You named no server: we take the cheapest server taking jobs that your account may use, and the answer names it.service_not_allowed 403
service_offline 503
A device-info pre-check on the chosen server: the device’s own region is read and checked against our list; a region the chosen server does not support is refused with unsupported (the reply carries the device’s region, takes_on, suggested and premium). The flash status is read too, but it decides nothing. This happens before a request_id is given, so nothing is charged. If the pre-check cannot be reached it is skipped, and the device list above still applies.unsupported 422
9The same device — the same device token, for the same service and the same server — was not sent in the last 30 minutes. If it was, and that job has started, you get that job back: its request_id and state, or its token — never a second job or a second charge. A job that failed, or whose request_id was never confirmed, is replaced by a new one.—
10Your credit covers the price of a job on that server — the price that servers.php shows for it. Checked here; the price is held only when the job starts.no_credit 402
Every check passed: you get the request_id at once, with state waiting. Nothing has been sent to any server, and nothing is held until you confirm it with status.php.

08Your request_id

The answer comes at once: HTTP 202 with state waiting. The request_id is the job’s WorkID — you see the same WT-API-6AF684545B in your panel and can search for it there. Keep it: confirming the job, following it, collecting the token and asking us about it all go by this request_id. A request_id given before 2026-10-17, such as Api123, still works everywhere and is answered in that form.

the answer from token.php
HTTP/1.1 202 Accepted

{
  "status": 202,
  "data": {
    "request_id":     "WT-API-6AF684545B",
    "soc":            "frp",
    "serverid":       "server1",
    "server_name":    "Server 1",
    "configblob":     "eyJ0b2tlbiI6IjhmMjFj…",
    "device":         { "product_model":"fleur", "device_name":"fleur_global", … },
    "state":          "waiting",
    "confirm_within": 30,
    "price":          1.2,
    "currency":       "USD",
    "response_token": null,
    "start_date":     { … },
    "end_date":       { … },
    "response_date":  { … }
  }
}

# Nothing has been sent to any server yet, and nothing is held.
# Send this request_id to status.php within 30 seconds: that starts the job.

Confirm it within 30 seconds. Send it to status.php: that call is your receipt and starts the job. A request_id nobody confirms within 30 seconds is cancelled — nothing was sent to any server and nothing was charged — and a later call with it answers request_expired: send the request again for a new request_id. So if an answer never reached you, nothing is running that you cannot follow.

FieldWhat it is
request_idOur id for this job — its WorkID, such as WT-API-6AF684545B. Confirm, follow and collect the job with it, and quote it to us in any question about it.
statewaiting
confirm_withinSeconds left to confirm it with status.php.
priceWhat the job will cost you on that server — held when it starts, charged only if the token comes.
serveridThe public id of the server that ran the job — the same serverid you can send.
server_nameThat server's public name, e.g. Server 1.

The same device — the same device token, for the same service and the same server — was not sent in the last 30 minutes. If it was, and that job has started, you get that job back: its request_id and state, or its token — never a second job or a second charge. A job that failed, or whose request_id was never confirmed, is replaced by a new one.

Part 2 · Checking a request

09Confirm and follow

POSThttps://xiaomi.source.af/api/reseller/v1/status.php

Send the request_id to status.php with the same four credentials. The first call within 30 seconds starts the job and answers pending at once; after that, ask again every retry_after seconds until state is no longer pending. A job usually takes 15–25 seconds.

status.php
POST /api/reseller/v1/status.php
{ "api_key":"…", "email":"…", "password":"…", "request_id":"WT-API-6AF684545B" }

# 1. the first call, within 30 seconds -- your receipt: the job starts now
-> HTTP 202  { "status":202, "data":{ "request_id":"WT-API-6AF684545B", "state":"pending", "retry_after":3, … } }

# 2. ask again every retry_after seconds
-> HTTP 202  { "status":202, "data":{ "request_id":"WT-API-6AF684545B", "state":"pending", "retry_after":3, … } }

# 3. the token
-> HTTP 200  { "status":200, "data":{ "request_id":"WT-API-6AF684545B", "state":"done",
                                      "response_token":"THE_UNLOCK_TOKEN_GOES_HERE", … } }

# Asked again later with the same request_id: the same token, at no extra cost.

What state says

StateHTTPWhat it means, and what to do
pending202The job is at the server. Ask again after retry_after seconds.
done200The token is in response_token: write it to the phone. The job is charged; the answer says what it cost and your credit after it.
error4xx · 5xxNo token. error says why — server_offline, unsupported, server_unavailable, failed or no_credit. The price is back on your credit in full, except when the server says it does not support the device (unsupported): that job is charged, as amount_charged shows.
expired410Not confirmed within 30 seconds: cancelled, nothing sent, nothing charged. Send the request again for a new request_id.
not_found404No request with this request_id belongs to your account.

The token is kept with the job, on our server. If an answer never reached you, ask status.php again with the same request_id: while the job runs you get pending, once it is done you get the token — as often as you ask, at no extra cost. A job that failed says why.

request_expired
# the first status.php call came more than 30 seconds after token.php answered
-> HTTP 410
   {
     "status":  410,
     "error":   "request_expired",
     "message": "This request_id was not confirmed with status.php within 30 seconds, so it was cancelled. Nothing was sent to any server and nothing was charged. Send a new request to get a new request_id.",
     "data":    { "request_id":"WT-API-6AF684545B", "state":"expired", "response_token":null, … }
   }

status.php has its own limit — 120 calls a minute per account — and does not count toward your hourly quota, so following a job never uses up your token requests.

10The response

One shape for every answer about a job, from token.php, status.php and history.php alike. state is waiting, pending, done, error or expired; the HTTP status says the same thing, so you can branch before parsing.

Token delivered
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{
  "status": 200,
  "data": {
    "request_id":  "WT-API-6AF684545B",
    "soc":         "frp",
    "serverid":    "server1",
    "server_name": "Server 1",
    "configblob":  "eyJ0b2tlbiI6IjhmMjFjMGI0ZTlkNyIsImRldmljZU5hbWUiOiJm…",

    "device": {
      "product_model":  "fleur",
      "serial_number":  "fb4d921c",
      "device_version": "OS2.0.207.0.VMTMIXM",
      "device_name":    "fleur_global",
      "cpu_id":         "0x000460E1",
      "codebase":       "14",
      "branch":         "F",
      "region":         "global",
      "soc":            "frp"
    },

    "state":          "done",
    "response_token": "THE_UNLOCK_TOKEN_GOES_HERE",
    "provider_ref":   "R7F3A9C21B04E",
    "amount_charged": 1.2,
    "credit":         93.8,
    "currency":       "USD",

    "start_date": {
      "client_sent":       "2026-09-21T14:51:51.980Z",
      "server_received":   "2026-09-21T14:51:52.392Z",
      "upstream_sent":     "2026-09-21T14:51:52.560Z",
      "upstream_received": "2026-09-21T14:51:57.392Z",
      "elapsed_seconds":   5.0
    },
    "end_date": {
      "upstream_answered": "2026-09-21T14:51:57.392Z",
      "server_answered":   "2026-09-21T14:52:00.392Z",
      "elapsed_seconds":   3.0
    },
    "response_date": {
      "total_seconds":               8.0,
      "client_clock_offset_seconds": 0.412
    }
  }
}

Every field in the answer:

FieldWhat it is
request_idOur id for this job — its WorkID, such as WT-API-6AF684545B. Confirm, follow and collect the job with it, and quote it to us in any question about it.
socThe service that ran.
serveridThe public id of the server that ran the job — the same serverid you can send.
server_nameThat server's public name, e.g. Server 1.
configblobThe configblob you sent, byte for byte.
deviceThe device, unpacked. Always present.
statewaiting, pending, done, error or expired.
response_tokenThe unlock token. This is what you write to the phone.
provider_refThe server's own job reference, for support questions.
amount_chargedWhat this job cost you.
creditYour credit after the charge.
currencyThe currency of both figures.

A failure

HTTP 4xx / 5xx
# the job started, and the server could not take it
-> HTTP 503
   {
     "status":  503,
     "error":   "server_offline",
     "message": "The selected server is off right now (FRP). Use one of the other servers (servers.php lists them) or try again later. Nothing was charged.",
     "data":    { "request_id":"WT-API-1C93E07B2D", "serverid":"server2", "server_name":"Server 2",
                  "state":"error", "response_token":null, … }
   }

# The price is back on your credit in full. The job was not moved to another server.

The three time blocks

Three blocks, and they add up: start_date.elapsed_seconds plus end_date.elapsed_seconds equals response_date.total_seconds, exactly, because all three are derived from the same instants.

In the worked example below the job took 5 seconds to fetch and 3 seconds to hand back, so 8 seconds in total.

FieldWhat it is
start_date — getting the token
client_sentwhen your server sent the request — exactly what you sent
server_receivedwhen the request reached our server
upstream_sentwhen we dispatched it to the token server
upstream_receivedwhen the token server's reply reached us
elapsed_secondsfrom arrival to token in hand — 5 seconds in the example
end_date — handing it back
upstream_answeredwhen the token server answered us
server_answeredwhen our server answered you
elapsed_secondsthe delivery leg alone — 3 seconds in the example
response_date — the whole thing
total_seconds5 + 3 = 8 seconds
client_clock_offset_secondsthe gap between your clock and ours

The time blocks are measured from when token.php received the request. end_date ends when the answer with the token left us, so it includes however long you waited between your status.php calls.

Why your own timestamp is not used in the sums
It is echoed in client_sent and it is yours, not ours — if your server clock runs a minute fast, arithmetic using it would report a negative duration. Every elapsed figure comes from our clock, and the gap between the two is reported on its own as client_clock_offset_seconds so you can see it rather than have it silently folded into a number you bill against.

11Device-info reads (soc)

Beyond frp and fdl, tool-owner accounts can also read a device's information. These services work over the same token.php then status.php flow, but they use no server and, instead of a token, return the device information as fields. They are free for now (a price may be set later).

The soc values

socWhat it doesInputAnswer in
frpUnlock FRP; returns a response token.configblob — the full device readingAt once
fdlFDL authorization; returns a response token.configblob — the full device readingAt once
read_sideloadReads the device info in Mi Assistant mode.configblob — only the device token is neededAt once
read_fastbootReads the device info in fastboot mode.configblob — only the device token is neededAt once
read_imeiLooks the IMEI up on the manufacturer's database.imei — the 15-digit IMEI1 to 5 minutes; keep polling
read_lockcodeReturns the device record behind a lock code.imei — the IMEI for the orderA few minutes; keep polling

These reads are for tool-owner accounts. read_imei is listed only while it is open to the API; otherwise it is available inside the XST tool.

Sending a read

A read is sent and checked exactly like frp and fdl: token.php gives you a request_id, your first status.php call starts it, and you poll until it is done. It uses no server and charges no server price.

token.php · read
# A DEVICE-TOKEN READ (Mi Assistant) -- soc = read_sideload. No serverid: a read
# uses no server. Only the device token inside configblob is needed.
POST /api/reseller/v1/token.php
{ "api_key":"…", "email":"…", "password":"…",
  "soc":"read_sideload", "configblob":"eyJ0b2tlbiI6IjhmMjFj…" }

-> HTTP 202
   { "status":202, "data":{ "request_id":"WT-API-3D7B19F0A4", "soc":"read_sideload",
                            "kind":"read", "state":"waiting", "confirm_within":30,
                            "price":0, "currency":"USD" } }

# read_fastboot is the same, with the token read in fastboot mode.

# AN IMEI LOOKUP -- soc = read_imei. No configblob: the caller types the IMEI.
POST /api/reseller/v1/token.php
{ "api_key":"…", "email":"…", "password":"…", "soc":"read_imei", "imei":"862127050000000" }

-> HTTP 202
   { "status":202, "data":{ "request_id":"WT-API-B82E6C0D51", "soc":"read_imei",
                            "kind":"read", "state":"waiting", "confirm_within":30, … } }

# read_lockcode is the same shape, with the IMEI used for the shop order.

What a finished read returns

Instead of a response_token, a read answers with a fields object — the device information as label/value pairs. The labels depend on the service; a field the source left empty is dropped.

status.php · read done
POST /api/reseller/v1/status.php
{ "api_key":"…", "email":"…", "password":"…", "request_id":"WT-API-3D7B19F0A4" }

# 1. the first call, within 30 seconds -- your receipt: the read starts now
-> HTTP 202  { "status":202, "data":{ "request_id":"WT-API-3D7B19F0A4", "soc":"read_sideload",
                                      "kind":"read", "state":"pending", "retry_after":5 } }

# 2. ask again every retry_after seconds, until state is "done"
-> HTTP 200
   {
     "status": 200,
     "data": {
       "request_id":     "WT-API-3D7B19F0A4",
       "soc":            "read_sideload",
       "kind":           "read",
       "state":          "done",
       "fields": {
         "Product":        "fleur",
         "IMEI":           "862127050000000",
         "IMEI 2":         "862127050000001",
         "Serial (SN)":    "fb4d921c",
         "Region":         "Iran (IR)",
         "Find My Device": "OFF"
       },
       "amount_charged": 0,
       "credit":         38,
       "currency":       "USD"
     }
   }

# A read answers with `fields` -- label/value pairs -- not a response_token.
# Asked again later with the same request_id: the same fields, at no extra cost.
read_imei · done
# read_imei: the answer takes about 1 to 5 minutes. Keep polling status.php until
# state is "done"; every call is "pending" with a retry_after until then.
# `fields` is a label->value map; which labels come back depends on the device and
# which info sources are enabled for your account, so treat it generically (loop
# over it) -- never assume a fixed set. Any of these may appear:
-> HTTP 200
   { "status":200, "data":{ "request_id":"WT-API-B82E6C0D51", "soc":"read_imei", "kind":"read", "state":"done",
       "fields": {
         "IMEI":            "862127050000000",
         "IMEI 2":          "862127050000001",
         "Product name":    "Xiaomi 14 12GB+512GB Black",
         "Brand":           "Xiaomi",
         "Model":           "Xiaomi 14",
         "Model code":      "2405CPX3DG",
         "Manufacturer":    "Xiaomi",
         "RAM":             "12 GB",
         "Storage":         "512 GB",
         "Color":           "Black",
         "Sold in":         "Global (MI)",
         "Region":          "Global",
         "Serial (SN)":     "fb4d921c",
         "SKU":             "MZB0ABCIN",
         "Wi-Fi MAC":       "A4:FF:9F:71:0E:EE",
         "Factory date":    "2024-02-14",
         "Activation":      "Never activated",
         "Repair":          "Available",
         "Flash status":    "Clean",
         "Production date": "2024-02-20 08:14 UTC",
         "Find My Device":  "OFF"
       },
       "amount_charged":0, "credit":38, "currency":"USD" } }
read_lockcode · done
# read_lockcode: an order placed at the shop. The answer takes a few minutes; keep
# polling status.php. Our own cron also follows the order, so it finishes even if
# you stop asking.
-> HTTP 200
   { "status":200, "data":{ "request_id":"WT-API-47A0F3E9C2", "soc":"read_lockcode", "kind":"read", "state":"done",
       "fields": {
         "Lock code":      "4YYZ-F7CO-FPDJ-GAU",
         "Product":        "fleur",
         "Serial Number":  "58583/65QU09211",
         "IMEI":           "862127050000000",
         "Find My Device": "ON"
       },
       "amount_charged":0, "credit":38, "currency":"USD" } }

servers.php lists the reads you may use in a reads array, each with its soc, name, needs and price, plus time (the delivery time; for read_lockcode the shop service's own, empty when not known) and field (the name of the input to show your customer, e.g. Lock Code). read_imei and read_lockcode are asynchronous (poll).

Part 3 · Your credit and prices

12Your job history

POSThttps://xiaomi.source.af/api/reseller/v1/history.php

Your jobs, newest first: when each was sent, to which server, and how it ended — by date and by service. The same four credentials; it costs no credit and counts toward your hourly quota.

FieldTypeRequiredWhat it is
fromstringnoA day, 2026-10-08 (or 2026-Oct-08), in UTC: the jobs sent on or after it.
tostringnoA day: the jobs sent on or before it, the whole day included.
socstringnofrp or fdl: only that service’s jobs.
limitnumbernoHow many jobs to list, 1–100. Default 1.
beforestringnoA request_id: the jobs older than it. Send the next_before of one answer to get the next page.
history.php
curl -X POST https://xiaomi.source.af/api/reseller/v1/history.php \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer rst_YOUR_ACCOUNT_TOKEN' \
  -d '{"api_key":"YOUR_API_KEY","email":"you@example.com","password":"YOUR_PASSWORD",
       "from":"2026-10-08","to":"2026-10-09","soc":"frp","limit":2}'

-> {
  "status": 200,
  "data": {
    "from":     "2026-10-08",
    "to":       "2026-10-09",
    "soc":      "frp",
    "total":    14,
    "counts":   { "done":11, "error":2, "expired":1, "pending":0, "waiting":0 },
    "spent":    13.2,
    "currency": "USD",
    "jobs": [
      { "request_id":"WT-API-6AF684545B", "door":"api", "soc":"frp", "serverid":"server1", "server_name":"Server 1",
        "state":"done", "error":null, "message":null, "amount":1.2, "currency":"USD",
        "device":{ "product_model":"fleur", "device_name":"fleur_global", "serial_number":"fb4d921c" },
        "created_at":"2026-10-09T14:51:52Z", "updated_at":"2026-10-09T14:52:00Z" },
      { "request_id":"WT-API-9F04A2C6E1", "door":"api", "soc":"frp", "serverid":"server2", "server_name":"Server 2",
        "state":"error", "error":"server_offline", "message":"The selected server is off right now (FRP). …",
        "amount":0, "currency":"USD", "device":{ … },
        "created_at":"2026-10-09T14:40:10Z", "updated_at":"2026-10-09T14:40:11Z" }
    ],
    "next_before": "WT-API-9F04A2C6E1"
  }
}

total and counts cover the whole range, not only this page: how many requests you sent, and how many ended done, error, expired, pending or waiting; spent is what they cost you. Each job under jobs has its request_id — its WorkID: WT-API-6AF684545B for one sent through this API, WT-WEB-… for one run in our own tool, as door says — with soc, serverid and server_name, state, error and message when it gave no token, amount (what it cost you), the device, and when it was sent and last changed. The token itself is not listed: status.php gives it by request_id.

13Credit and usage

POSThttps://xiaomi.source.af/api/reseller/v1/balance.php

How much credit is left, and where the rest went: the same figures as the cards at the top of your panel. It takes the same four credentials as a token request, costs no credit, and answers once every 10 seconds.

balance.php
curl -X POST https://xiaomi.source.af/api/reseller/v1/balance.php \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer rst_YOUR_ACCOUNT_TOKEN' \
  -d '{"api_key":"YOUR_API_KEY","email":"you@example.com","password":"YOUR_PASSWORD"}'

-> {
  "status": 200,
  "data": {
    "username": "you",
    "credit":   38,
    "currency": "USD",
    "usage": {
      "bought":        50,
      "spent":         12,
      "spent_frp":     10.8,
      "spent_fdl":     1.2,
      "spent_api":     9,
      "spent_xst":     3,
      "sent_to_users": 0,
      "left":          38
    },
    "prices": {
      "frp": [
        { "serverid":"server1", "name":"Server 1", "online":true,  "price":1.2 },
        { "serverid":"server2", "name":"Server 2", "online":true,  "price":1.5 },
        { "serverid":"server3", "name":"Server 3", "online":false, "price":1.4 }
      ],
      "fdl": [
        { "serverid":"server1", "name":"Server 1", "online":true,  "price":0.3 }
      ]
    }
  }
}

Under usage: bought is every credit added to your account, less what the admin took back; spent is what your jobs cost, less refunds — split by service into spent_frp and spent_fdl, and by door into spent_api (through this API) and spent_xst (in our own tool); sent_to_users is what you gave your own users if you were once a reseller; left is what remains, the same as credit. They add up: bought − sent_to_users − spent = left, and spent_frp + spent_fdl = spent. prices is what one job costs you on each server, by service: its serverid, name, whether it is online, and the price, in your currency.

Asked again within 10 seconds, it answers 429 too_soon with retry_after: wait that many seconds. Nothing is blocked.

14Servers and prices

POSThttps://xiaomi.source.af/api/reseller/v1/servers.php

It takes the same four credentials as a token request and costs no credit. Every call counts toward your hourly quota.

What you may use, and what each job costs you.

servers.php
curl -X POST https://xiaomi.source.af/api/reseller/v1/servers.php \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer rst_YOUR_ACCOUNT_TOKEN' \
  -d '{"api_key":"YOUR_API_KEY","email":"you@example.com","password":"YOUR_PASSWORD"}'

-> {
  "status": 200,
  "data": {
    "currency": "USD",
    "services": {
      "frp": {
        "price": 1.2,
        "providers": [
          { "serverid":"server1", "name":"Server 1", "online":true,  "price":1.2, "currency":"USD",
            "region_support":[], "region_not_support":["CN","IN"] },
          { "serverid":"server6-3", "name":"Server 6.3", "online":true,  "price":1.5, "currency":"USD",
            "region_support":["CN"], "region_not_support":["IN"] },
          { "serverid":"server3", "name":"Server 3", "online":false, "price":1.4, "currency":"USD",
            "region_support":["CN","IN"], "region_not_support":[] }
        ]
      },
      "fdl": { "price":0.3, "providers":[ … ] }
    }
  }
}

Each row under providers is one server you may use — every one, also those that are off right now (online false), the ones taking jobs first and cheapest first. serverid and name are what you send and what every answer names back, price is what a job on it costs you, and online says whether a request named to it is accepted now. Picking one that is off is your choice: token.php refuses it with server_offline before any request_id — use another server or try later. The price beside the list is what a request without serverid costs: the cheapest server taking jobs.

Each row also says which countries that server takes, from the device’s region: region_support — countries it takes although some of our servers do not (or that we name as taken there), region_not_support — countries it does not take. Both empty: every country. Show them to your customer as a reminder, e.g. “Server 6.3 — China: yes, India: no”. Only the country is published: our other conditions (a model, a build, a CPU ID…) are not listed and stay ours. The decision is always made here, before anything is sent — token.php refuses a device a server does not take with unsupported (422), and check.php answers it for one device.

The servers reply is safe to show your own customers: it carries our public server names and your price, never our cost and never the upstream provider.

15Which servers take a device

POSThttps://xiaomi.source.af/api/reseller/v1/check.php

A dry check: the same credentials and the same body as token.php (soc, configblob), the same checks of the configblob and the same errors — and then nothing: no request_id, no job, nothing reserved or charged, nothing sent to any server. Some servers do not take some devices (a build, a country…); this call tells you, for this device, which of your servers do. It has its own limit, 60 calls a minute, and does not count toward your hourly quota.

check.php
curl -X POST https://xiaomi.source.af/api/reseller/v1/check.php \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer rst_YOUR_ACCOUNT_TOKEN' \
  -d '{"api_key":"YOUR_API_KEY","email":"you@example.com","password":"YOUR_PASSWORD",
       "soc":"frp","configblob":"eyJ0b2tlbiI6IjhmMjFj…"}'

-> {
  "status": 200,
  "data": {
    "soc": "frp",
    "refused_everywhere": false,
    "suggested": "server6-3",
    "device": { "model":"fleur", "device":"fleur_global", "version":"OS2.0.207.0.VMTMIXM", "region":"US", "cpuid":"0x000460E1" },
    "servers": [
      { "serverid":"server1",   "name":"Server 1",   "online":true,  "price":1.2, "currency":"USD", "region_support":[],     "region_not_support":["CH","CN"], "takes":false, "reason":"China devices are not supported on this server" },
      { "serverid":"server6-3", "name":"Server 6.3", "online":true,  "price":2.0, "currency":"USD", "region_support":["CH","CN"], "region_not_support":[], "takes":true },
      { "serverid":"server5",   "name":"Server 5",   "online":false, "price":1.4, "currency":"USD", "region_support":[],     "region_not_support":["CH","CN"], "takes":false, "reason":"…" }
    ],
    "region": "CN",
    "region_checked": true,
    "flash_status": "CLEAN",
    "premium": [ { "serverid":"server6-3", "name":"Server 6.3", "online":true, "price":2.0, "currency":"USD" } ],
    "slots_full": false,
    "message": "Your device's region is CN. This device must be done with ★ Server 6.3 — a premium server: its FRP removal success rate for this device is very high."
  }
}

The rows under servers are the ones servers.php lists, in its order, also the ones that are off. takes says whether that server takes this device; when it does not, reason says why. A server can be off (online false) and still take the device: tell your customer to try it later. suggested is the first server that takes the device and is on (cheapest first), or null; refused_everywhere is true when none of your servers takes it. device is what we read from the configblob. When token.php refuses a device with unsupported (422), its reply carries takes_on: the serverids that do take it.

The answer also reads the device’s own region with the same device-info pre-check token.php runs — a phone can report a region it is not from. region is the region the servers were judged on: what the pre-check read when region_checked is true, else what the configblob said. flash_status is the flash status it read (empty when unknown). premium lists your servers that take this device and are premium for its region (they are made for devices of that country, so their success rate for it is very high); suggested is then one of them, and message is a sentence you may show your customer, e.g. “Your device’s region is CN. This device must be done with Server 6.3 — a premium server …” (empty when there is none). Pre-select that server for your customer, say why, and let them change the server, cancel, or go on. The pre-check’s answer is kept for 15 minutes, so the token.php that follows does not ask again.

Ask it after you read a device, to grey out the servers that would refuse it, or after token.php answered unsupported for the server your customer picked, to offer the one that takes it. A request without serverid is sent to the cheapest server that takes the device by itself.

Part 4 · Error codes

16Error codes

Every failure has the same shape: an error code to branch on and a message for a human. Branch on the code, never on the message text — wording changes, codes do not. Failures from token.php past the sign-in checks also carry the timing blocks, so a slow failure is as measurable as a slow success. Some add fields of their own: incomplete_configblob names the fields in missing and invalid, with mode; rate_limited carries limit and used. Whatever the code, a job that delivered no token costs nothing — refusals come before anything is reserved, and a job that fails after it was sent has its reservation refunded in full.

CodeHTTPWhat it means, and what to do
unauthorized401One of the four credentials is wrong, or the account is disabled. We deliberately do not say which.
ip_not_allowed403All four credentials are right, but the request came from an address that is not on your account’s allowed list. The reply names it in ip. Add it in your panel (Account → Allowed IPs), or send from an allowed address. Nothing was run or charged.
too_many_failures429Too many failed attempts from your address. Wait, then fix the credential before retrying.
rate_limited429You passed a limit: your hourly quota (token.php, history.php, balance.php, servers.php) or 120 status checks a minute. The reply carries limit, used and per.
too_soon429balance.php was asked again within 10 seconds. The reply says how many seconds to wait (retry_after and the Retry-After header); nothing is blocked.
method_not_allowed405You used GET. Every endpoint is POST.
bad_json400The body was not a JSON object. Set Content-Type: application/json.
body_too_large413The body is over 256 KB.
missing_soc400soc was not sent. It has no default on purpose.
bad_service400soc must be exactly frp or fdl.
not_entitled403That service is not sold to your account. Ask us to enable it.
disabled503We have that service switched off right now. Temporary.
missing_configblob400configblob was not sent.
bad_configblob400configblob is not valid base64 of a JSON object.
missing_token400The device token is missing from inside the blob.
incomplete_configblob400A field the servers need is left out (missing), sent as null (nulls), or holds a failed reading (invalid); mode is the mode the phone must be in. Read the phone again and send the complete information; nothing was charged.
use_serverid400You sent services or server_id, the older way of choosing a server by its position. It is no longer accepted: send serverid (from servers.php), or leave it out and let us choose.
use_status400You sent a request_id to token.php, which only starts new requests. Send the request_id to status.php.
unknown_serverid404No server has that serverid. Call servers.php, or look under Servers in your panel, for the list.
server_not_allowed403The server you named is not enabled for your account. We do not switch you to another one: pick one from your list, or leave serverid out.
service_not_allowed403You named no server and none is enabled for your account. Ask us to enable one.
server_offline503The server you named cannot take this service right now: switched off, offline, or not ready on our side. token.php checks it before giving a request_id, and the job checks it again when it starts; if it went off in between, or while the job ran, the reservation is refunded. Nothing was charged. Use another server from servers.php, or try again later.
service_offline503You named no server and none is taking jobs for this service right now. Nothing was charged. Retry shortly.
no_credit402Not enough credit for a job on that server — refused by token.php before any request_id, or, if your credit dropped in the meantime, when the job was to start. Nothing was charged. Top up, or pick a cheaper server.
bad_imei400The imei was missing or not valid: read_imei needs the 15-digit IMEI; read_lockcode needs the IMEI used for the order.
read_failed502A read (read_sideload, read_fastboot, read_imei or read_lockcode) could not be completed. Nothing was charged. Send it again, or try later.
missing_request_id400status.php was called without a request_id.
not_found404No request with this request_id belongs to your account.
request_expired410The request_id was not confirmed with status.php within 30 seconds, so it was cancelled. Nothing was sent or charged. Send the request to token.php again for a new request_id.
unsupported422This device cannot be served. If it is on our list of devices we do not serve, token.php refuses it: no request_id, nothing charged. If the server you chose says it does not support the device, the job is charged and not refunded — ask us which builds and countries are supported before sending (see Supported devices). Do not retry on the same server.
account_restricted403Your account is restricted for a few minutes: one device was refused as unsupported more than 3 times in 5 minutes and was sent again after the warning. Every new request is refused until then; status.php still answers for jobs already started. The reply carries reason (unsupported_device_repeated), retry_after (seconds), restricted_until and note, a sentence for your panel. Contact us if it was a mistake.
device_reboot409The server says this device token was already used. Nothing was charged. Reboot the phone, put it into sideload mode again so it gives a new token, and send a new request.
server_unavailable503The server did not answer in time, or — when you named none — no server was available. Nothing was charged. Send the same request again.
failed400The server you chose ran the job but gave no token for this device — a late failure. Nothing was charged: the reservation is refunded in full. Check the reading of the phone, or try another server.
bad_before400before in history.php is not a request_id.
bad_date400from or to in history.php is not a day such as 2026-10-08, or from is after to.
Part 5 · Security

17Security

Use HTTPS, always

Your password and key are in the body of every request. Over plain HTTP anyone on the path can read them and reuse them.

Your account token is shown once

This is your account token — the rst_… value in the Authorization header — not the unlock tokens. We keep only a hash of it, so we cannot email it to you later. Store it where you store your other secrets. If it leaks, generate a new one in your panel — the old one stops working immediately. The unlock tokens your jobs bring are a different thing: each is kept with its job, and status.php gives it to you again by request_id.

Keep the credentials out of what you ship

Call us from your own server, not from a tool you hand to end users. Anything you ship can be read.

Lock the API to your addresses

In your panel (Account → Allowed IPs) you can lock the API to the addresses your server calls from, so a stolen credential is useless anywhere else. A request from any other address is refused with ip_not_allowed. We can set it for you as well.

We never log your password

It is stripped before the request is stored with the job, so it is not in our database, our job log, or our admin screens.

The request_id is your receipt

Only your account can read a request_id; any other account gets not_found. A job starts only when you confirm its request_id within 30 seconds, so an answer lost on the way never leaves a paid job running that you do not know about.

Limits

Your hourly quota covers token.php, history.php, balance.php and servers.php; status.php allows 120 calls a minute on its own; balance.php answers once every 10 seconds; and after more than 40 failed sign-ins from one address in 15 minutes, that address is refused for a while with too_many_failures.

Part 6 · Code samples

18Code samples

One complete program for each task, in four languages — send a request, check it by its request_id, your job history, your credit, the servers. Put in your own credentials and run it; picking a language shows it in every block.

1Send a token request

Sends the reading of the phone to token.php and prints the request_id. Nothing runs yet: give that request_id to the next program within 30 seconds.

send.sh
#!/bin/sh
# 1. Send a token request. Prints the answer with the request_id; nothing runs
#    until you confirm it with status.sh within 30 seconds.
BASE='https://xiaomi.source.af/api/reseller/v1'
TOKEN='rst_YOUR_ACCOUNT_TOKEN'
AUTH='"api_key":"YOUR_API_KEY","email":"you@example.com","password":"YOUR_PASSWORD"'
post() { curl -s -X POST "$BASE/$1" -H 'Content-Type: application/json' -H "Authorization: Bearer $TOKEN" -d "{$AUTH$2}"; }

# The complete Mi Assistant reading, exactly as the phone answered
DEVICE='{"token":"8f21c0b4e9d7","deviceName":"fleur_global","version":"OS2.0.207.0.VMTMIXM","serial":"fb4d921c","codebase":"15","region":"IR","cpuid":"0x000460E1","recovery":"2","branch":"F","language":"fa"}'
BLOB=$(printf '%s' "$DEVICE" | base64 | tr -d '\n')

post token.php ',"soc":"frp","serverid":"server1","configblob":"'"$BLOB"'"'
echo
# -> {"status":202,"data":{"request_id":"WT-API-6AF684545B","state":"waiting","confirm_within":30,…}}
# next, within 30 seconds:  sh status.sh WT-API-6AF684545B
send.php
<?php
/* 1. Send a token request. Prints the request_id; nothing runs until you
      confirm it with status.php within 30 seconds. */
const BASE  = 'https://xiaomi.source.af/api/reseller/v1';
const TOKEN = 'rst_YOUR_ACCOUNT_TOKEN';
const AUTH  = ['api_key' => 'YOUR_API_KEY', 'email' => 'you@example.com', 'password' => 'YOUR_PASSWORD'];

function xst(string $endpoint, array $body): array
{
    $ch = curl_init(BASE . '/' . $endpoint);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode(AUTH + $body),
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json', 'Authorization: Bearer ' . TOKEN],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
    ]);
    $raw = curl_exec($ch);
    curl_close($ch);
    return json_decode((string)$raw, true) ?: ['error' => 'no_answer', 'message' => 'no answer from the server'];
}

/* What your tool read in Mi Assistant mode, exactly as the phone answered. */
$device = [
    'token'      => '8f21c0b4e9d7…',          // getmitoken:
    'deviceName' => 'fleur_global',           // getdevice:
    'version'    => 'OS2.0.207.0.VMTMIXM',    // getversion:
    'serial'     => 'fb4d921c',               // getsn:
    'codebase'   => '14',                     // getcodebase:
    'branch'     => 'F',                      // getbranch:
    'region'     => 'global',                 // getregion:
    'cpuid'      => '0x000460E1',             // getcpuid:  optional
];

$r = xst('token.php', [
    'soc'        => 'frp',
    'serverid'   => 'server1',                // the server that must run it
    'configblob' => base64_encode(json_encode($device)),
]);
if (isset($r['error'])) {
    exit("refused: {$r['error']} - {$r['message']}\n");     // nothing was created or charged
}
$rid = $r['data']['request_id'];
echo "request_id $rid\n";
echo "confirm it within {$r['data']['confirm_within']} seconds:  php status.php $rid\n";
send.py
"""1. Send a token request. Prints the request_id; nothing runs until you
   confirm it with status.py within 30 seconds."""
import base64, json, sys, time, requests

BASE  = "https://xiaomi.source.af/api/reseller/v1"
TOKEN = "rst_YOUR_ACCOUNT_TOKEN"
AUTH  = {"api_key": "YOUR_API_KEY", "email": "you@example.com", "password": "YOUR_PASSWORD"}

def xst(endpoint, body):
    try:
        r = requests.post(f"{BASE}/{endpoint}", json={**AUTH, **body},
                          headers={"Authorization": f"Bearer {TOKEN}"}, timeout=30)
        return r.json()
    except (requests.RequestException, ValueError):
        return {"error": "no_answer", "message": "no answer from the server"}

# What your tool read in Mi Assistant mode, exactly as the phone answered.
device = {
    "token":      "8f21c0b4e9d7…",          # getmitoken:
    "deviceName": "fleur_global",           # getdevice:
    "version":    "OS2.0.207.0.VMTMIXM",    # getversion:
    "serial":     "fb4d921c",               # getsn:
    "codebase":   "14",                     # getcodebase:
    "branch":     "F",                      # getbranch:
    "region":     "global",                 # getregion:
    "cpuid":      "0x000460E1",             # getcpuid:  optional
}

r = xst("token.php", {"soc": "frp", "serverid": "server1",      # the server that must run it
                      "configblob": base64.b64encode(json.dumps(device).encode()).decode()})
if "error" in r:
    sys.exit(f"refused: {r['error']} - {r.get('message')}")    # nothing was created or charged
rid = r["data"]["request_id"]
print("request_id", rid)
print(f"confirm it within {r['data']['confirm_within']} seconds:  python status.py {rid}")
Program.cs
// 1. Send a token request. Prints the request_id; nothing runs until you
//    confirm it with the status program within 30 seconds.
// .NET 6 or later: dotnet new console, then replace Program.cs with this file.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;

const string BASE  = "https://xiaomi.source.af/api/reseller/v1";
const string TOKEN = "rst_YOUR_ACCOUNT_TOKEN";

var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", TOKEN);

async Task<JsonNode> Xst(string endpoint, JsonObject body)
{
    body["api_key"]  = "YOUR_API_KEY";
    body["email"]    = "you@example.com";
    body["password"] = "YOUR_PASSWORD";
    try
    {
        var res = await http.PostAsync($"{BASE}/{endpoint}",
            new StringContent(body.ToJsonString(), Encoding.UTF8, "application/json"));
        return JsonNode.Parse(await res.Content.ReadAsStringAsync())!;
    }
    catch (Exception)
    {
        return new JsonObject { ["error"] = "no_answer", ["message"] = "no answer from the server" };
    }
}

// What your tool read in Mi Assistant mode, exactly as the phone answered.
var device = new JsonObject
{
    ["token"]      = "8f21c0b4e9d7…",          // getmitoken:
    ["deviceName"] = "fleur_global",           // getdevice:
    ["version"]    = "OS2.0.207.0.VMTMIXM",    // getversion:
    ["serial"]     = "fb4d921c",               // getsn:
    ["codebase"]   = "14",                     // getcodebase:
    ["branch"]     = "F",                      // getbranch:
    ["region"]     = "global",                 // getregion:
    ["cpuid"]      = "0x000460E1",             // getcpuid:  optional
};
var blob = Convert.ToBase64String(Encoding.UTF8.GetBytes(device.ToJsonString()));

var r = await Xst("token.php", new JsonObject { ["soc"] = "frp", ["serverid"] = "server1", ["configblob"] = blob });
if (r["error"] is not null)
{
    Console.WriteLine($"refused: {r["error"]} - {r["message"]}");   // nothing was created or charged
    return;
}
var rid = (string)r["data"]!["request_id"]!;
Console.WriteLine($"request_id {rid}");
Console.WriteLine($"confirm it within {r["data"]!["confirm_within"]} seconds:  dotnet run -- {rid}");

2Check a request and get the token

Takes the request_id from the first program. Its first call to status.php starts the job; then it asks every 3 seconds until the token comes — or the reason it did not.

status.sh
#!/bin/sh
# 2. Confirm a request and follow it to the token:  sh status.sh WT-API-6AF684545B
#    The first call starts the job (within 30 seconds of send.sh).
BASE='https://xiaomi.source.af/api/reseller/v1'
TOKEN='rst_YOUR_ACCOUNT_TOKEN'
AUTH='"api_key":"YOUR_API_KEY","email":"you@example.com","password":"YOUR_PASSWORD"'
post() { curl -s -X POST "$BASE/$1" -H 'Content-Type: application/json' -H "Authorization: Bearer $TOKEN" -d "{$AUTH$2}"; }
RID="${1:-WT-API-6AF684545B}"

while :; do
  ANSWER=$(post status.php ',"request_id":"'"$RID"'"')
  echo "$ANSWER"
  case "$ANSWER" in
    *'"state":"pending"'*|*'"state":"waiting"'*) sleep 3 ;;
    *) break ;;
  esac
done
# The last answer: "state":"done" with "response_token" -- write it to the phone --
# or "error" with the reason ("expired": send a new request).
status.php
<?php
/* 2. Confirm a request and follow it to the token:  php status.php WT-API-6AF684545B
      The first call starts the job (within 30 seconds of send.php). */
const BASE  = 'https://xiaomi.source.af/api/reseller/v1';
const TOKEN = 'rst_YOUR_ACCOUNT_TOKEN';
const AUTH  = ['api_key' => 'YOUR_API_KEY', 'email' => 'you@example.com', 'password' => 'YOUR_PASSWORD'];

function xst(string $endpoint, array $body): array
{
    $ch = curl_init(BASE . '/' . $endpoint);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode(AUTH + $body),
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json', 'Authorization: Bearer ' . TOKEN],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
    ]);
    $raw = curl_exec($ch);
    curl_close($ch);
    return json_decode((string)$raw, true) ?: ['error' => 'no_answer', 'message' => 'no answer from the server'];
}

$rid = $argv[1] ?? 'WT-API-6AF684545B';        // the request_id token.php gave you

for ($i = 0; $i < 100; $i++) {
    $r = xst('status.php', ['request_id' => $rid]);
    if (($r['error'] ?? '') === 'no_answer') { sleep(3); continue; }   // the network, not the job: ask again
    $state = $r['data']['state'] ?? 'error';
    if ($state !== 'pending' && $state !== 'waiting') { break; }
    echo "pending...\n";
    sleep((int)($r['data']['retry_after'] ?? 3));
}

if (($r['data']['state'] ?? '') === 'done') {
    $d = $r['data'];
    echo "token {$d['response_token']}\n";              // write this to the phone
    echo "from {$d['server_name']}, charged {$d['amount_charged']} {$d['currency']}, credit left {$d['credit']}\n";
} else {
    echo 'no token: ' . ($r['error'] ?? '?') . ' - ' . ($r['message'] ?? '') . "\n";
}
status.py
"""2. Confirm a request and follow it to the token:  python status.py WT-API-6AF684545B
   The first call starts the job (within 30 seconds of send.py)."""
import base64, json, sys, time, requests

BASE  = "https://xiaomi.source.af/api/reseller/v1"
TOKEN = "rst_YOUR_ACCOUNT_TOKEN"
AUTH  = {"api_key": "YOUR_API_KEY", "email": "you@example.com", "password": "YOUR_PASSWORD"}

def xst(endpoint, body):
    try:
        r = requests.post(f"{BASE}/{endpoint}", json={**AUTH, **body},
                          headers={"Authorization": f"Bearer {TOKEN}"}, timeout=30)
        return r.json()
    except (requests.RequestException, ValueError):
        return {"error": "no_answer", "message": "no answer from the server"}

rid = sys.argv[1] if len(sys.argv) > 1 else "WT-API-6AF684545B"     # the request_id token.php gave you

for _ in range(100):
    r = xst("status.php", {"request_id": rid})
    if r.get("error") == "no_answer":
        time.sleep(3)                    # the network, not the job: ask again
        continue
    if r.get("data", {}).get("state") not in ("pending", "waiting"):
        break
    print("pending...")
    time.sleep(r["data"].get("retry_after", 3))

d = r.get("data", {})
if d.get("state") == "done":
    print("token", d["response_token"])  # write this to the phone
    print("from", d["server_name"], "charged", d["amount_charged"], d["currency"], "credit left", d["credit"])
else:
    print("no token:", r.get("error"), "-", r.get("message"))
Program.cs
// 2. Confirm a request and follow it to the token:  dotnet run -- WT-API-6AF684545B
//    The first call starts the job (within 30 seconds of sending).
// .NET 6 or later: dotnet new console, then replace Program.cs with this file.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;

const string BASE  = "https://xiaomi.source.af/api/reseller/v1";
const string TOKEN = "rst_YOUR_ACCOUNT_TOKEN";

var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", TOKEN);

async Task<JsonNode> Xst(string endpoint, JsonObject body)
{
    body["api_key"]  = "YOUR_API_KEY";
    body["email"]    = "you@example.com";
    body["password"] = "YOUR_PASSWORD";
    try
    {
        var res = await http.PostAsync($"{BASE}/{endpoint}",
            new StringContent(body.ToJsonString(), Encoding.UTF8, "application/json"));
        return JsonNode.Parse(await res.Content.ReadAsStringAsync())!;
    }
    catch (Exception)
    {
        return new JsonObject { ["error"] = "no_answer", ["message"] = "no answer from the server" };
    }
}

var rid = args.Length > 0 ? args[0] : "WT-API-6AF684545B";      // the request_id token.php gave you

JsonNode r = new JsonObject();
for (var i = 0; i < 100; i++)
{
    r = await Xst("status.php", new JsonObject { ["request_id"] = rid });
    if ((string?)r["error"] == "no_answer") { await Task.Delay(3000); continue; }   // the network: ask again
    var state = (string?)r["data"]?["state"];
    if (state != "pending" && state != "waiting") break;
    Console.WriteLine("pending...");
    await Task.Delay(3000);
}

var d = r["data"];
if ((string?)d?["state"] == "done")
{
    Console.WriteLine($"token {d!["response_token"]}");   // write this to the phone
    Console.WriteLine($"from {d["server_name"]}, charged {d["amount_charged"]} {d["currency"]}, credit left {d["credit"]}");
}
else
{
    Console.WriteLine($"no token: {r["error"]} - {r["message"]}");
}

3Your job history

Lists your jobs between two days, for one service: how many you sent, how each ended, what they cost.

history.sh
#!/bin/sh
# 3. Your job history: the FRP jobs sent from 8 to 9 October, newest first.
BASE='https://xiaomi.source.af/api/reseller/v1'
TOKEN='rst_YOUR_ACCOUNT_TOKEN'
AUTH='"api_key":"YOUR_API_KEY","email":"you@example.com","password":"YOUR_PASSWORD"'
post() { curl -s -X POST "$BASE/$1" -H 'Content-Type: application/json' -H "Authorization: Bearer $TOKEN" -d "{$AUTH$2}"; }

post history.php ',"from":"2026-10-08","to":"2026-10-09","soc":"frp","limit":20'
echo
history.php
<?php
/* 3. Your job history: the FRP jobs sent from 8 to 9 October, newest first. */
const BASE  = 'https://xiaomi.source.af/api/reseller/v1';
const TOKEN = 'rst_YOUR_ACCOUNT_TOKEN';
const AUTH  = ['api_key' => 'YOUR_API_KEY', 'email' => 'you@example.com', 'password' => 'YOUR_PASSWORD'];

function xst(string $endpoint, array $body): array
{
    $ch = curl_init(BASE . '/' . $endpoint);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode(AUTH + $body),
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json', 'Authorization: Bearer ' . TOKEN],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
    ]);
    $raw = curl_exec($ch);
    curl_close($ch);
    return json_decode((string)$raw, true) ?: ['error' => 'no_answer', 'message' => 'no answer from the server'];
}

$r = xst('history.php', ['from' => '2026-10-08', 'to' => '2026-10-09', 'soc' => 'frp', 'limit' => 20]);
if (isset($r['error'])) { exit("refused: {$r['error']} - {$r['message']}\n"); }

$d = $r['data'];
echo "{$d['total']} requests: {$d['counts']['done']} done, {$d['counts']['error']} failed, "
   . "{$d['counts']['expired']} expired; spent {$d['spent']} {$d['currency']}\n";
foreach ($d['jobs'] as $j) {
    printf("#%-9s %s %s %-10s %-8s %-6s %s\n", $j['request_id'], $j['created_at'], strtoupper($j['soc']),
           $j['server_name'], $j['state'], $j['amount'], $j['error'] ?? '');
}
history.py
"""3. Your job history: the FRP jobs sent from 8 to 9 October, newest first."""
import base64, json, sys, time, requests

BASE  = "https://xiaomi.source.af/api/reseller/v1"
TOKEN = "rst_YOUR_ACCOUNT_TOKEN"
AUTH  = {"api_key": "YOUR_API_KEY", "email": "you@example.com", "password": "YOUR_PASSWORD"}

def xst(endpoint, body):
    try:
        r = requests.post(f"{BASE}/{endpoint}", json={**AUTH, **body},
                          headers={"Authorization": f"Bearer {TOKEN}"}, timeout=30)
        return r.json()
    except (requests.RequestException, ValueError):
        return {"error": "no_answer", "message": "no answer from the server"}

r = xst("history.php", {"from": "2026-10-08", "to": "2026-10-09", "soc": "frp", "limit": 20})
if "error" in r:
    sys.exit(f"refused: {r['error']} - {r.get('message')}")

d = r["data"]
c = d["counts"]
print(f"{d['total']} requests: {c['done']} done, {c['error']} failed, {c['expired']} expired; spent {d['spent']} {d['currency']}")
for j in d["jobs"]:
    print(f"#{j['request_id']:<9} {j['created_at']} {j['soc'].upper()} {j['server_name']:<10} {j['state']:<8} {j['amount']:<6} {j['error'] or ''}")
Program.cs
// 3. Your job history: the FRP jobs sent from 8 to 9 October, newest first.
// .NET 6 or later: dotnet new console, then replace Program.cs with this file.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;

const string BASE  = "https://xiaomi.source.af/api/reseller/v1";
const string TOKEN = "rst_YOUR_ACCOUNT_TOKEN";

var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", TOKEN);

async Task<JsonNode> Xst(string endpoint, JsonObject body)
{
    body["api_key"]  = "YOUR_API_KEY";
    body["email"]    = "you@example.com";
    body["password"] = "YOUR_PASSWORD";
    try
    {
        var res = await http.PostAsync($"{BASE}/{endpoint}",
            new StringContent(body.ToJsonString(), Encoding.UTF8, "application/json"));
        return JsonNode.Parse(await res.Content.ReadAsStringAsync())!;
    }
    catch (Exception)
    {
        return new JsonObject { ["error"] = "no_answer", ["message"] = "no answer from the server" };
    }
}

var r = await Xst("history.php", new JsonObject { ["from"] = "2026-10-08", ["to"] = "2026-10-09", ["soc"] = "frp", ["limit"] = 20 });
if (r["error"] is not null) { Console.WriteLine($"refused: {r["error"]} - {r["message"]}"); return; }

var d = r["data"]!;
var c = d["counts"]!;
Console.WriteLine($"{d["total"]} requests: {c["done"]} done, {c["error"]} failed, {c["expired"]} expired; spent {d["spent"]} {d["currency"]}");
foreach (var j in d["jobs"]!.AsArray())
    Console.WriteLine($"#{j!["request_id"],-9} {j["created_at"]} {((string)j["soc"]!).ToUpper()} {j["server_name"],-10} {j["state"],-8} {j["amount"],-6} {j["error"]}");

4Your credit

Prints your credit, what you spent on FRP and FDL, and your price on each server.

balance.sh
#!/bin/sh
# 4. Your credit, what you spent on FRP and FDL, and your price on each server.
BASE='https://xiaomi.source.af/api/reseller/v1'
TOKEN='rst_YOUR_ACCOUNT_TOKEN'
AUTH='"api_key":"YOUR_API_KEY","email":"you@example.com","password":"YOUR_PASSWORD"'
post() { curl -s -X POST "$BASE/$1" -H 'Content-Type: application/json' -H "Authorization: Bearer $TOKEN" -d "{$AUTH$2}"; }

post balance.php ''
echo
balance.php
<?php
/* 4. Your credit, what you spent on FRP and FDL, and your price on each server. */
const BASE  = 'https://xiaomi.source.af/api/reseller/v1';
const TOKEN = 'rst_YOUR_ACCOUNT_TOKEN';
const AUTH  = ['api_key' => 'YOUR_API_KEY', 'email' => 'you@example.com', 'password' => 'YOUR_PASSWORD'];

function xst(string $endpoint, array $body): array
{
    $ch = curl_init(BASE . '/' . $endpoint);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode(AUTH + $body),
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json', 'Authorization: Bearer ' . TOKEN],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
    ]);
    $raw = curl_exec($ch);
    curl_close($ch);
    return json_decode((string)$raw, true) ?: ['error' => 'no_answer', 'message' => 'no answer from the server'];
}

$r = xst('balance.php', []);
if (isset($r['error'])) { exit("refused: {$r['error']} - {$r['message']}\n"); }

$d = $r['data'];
$u = $d['usage'];
echo "credit {$d['credit']} {$d['currency']}\n";
echo "spent {$u['spent']}: FRP {$u['spent_frp']}, FDL {$u['spent_fdl']}\n";
foreach ($d['prices'] as $soc => $servers) {
    foreach ($servers as $s) {
        printf("  %s %-10s %-12s %-7s %s\n", strtoupper($soc), $s['serverid'], $s['name'], $s['online'] ? 'online' : 'offline', $s['price']);
    }
}
balance.py
"""4. Your credit, what you spent on FRP and FDL, and your price on each server."""
import base64, json, sys, time, requests

BASE  = "https://xiaomi.source.af/api/reseller/v1"
TOKEN = "rst_YOUR_ACCOUNT_TOKEN"
AUTH  = {"api_key": "YOUR_API_KEY", "email": "you@example.com", "password": "YOUR_PASSWORD"}

def xst(endpoint, body):
    try:
        r = requests.post(f"{BASE}/{endpoint}", json={**AUTH, **body},
                          headers={"Authorization": f"Bearer {TOKEN}"}, timeout=30)
        return r.json()
    except (requests.RequestException, ValueError):
        return {"error": "no_answer", "message": "no answer from the server"}

r = xst("balance.php", {})
if "error" in r:
    sys.exit(f"refused: {r['error']} - {r.get('message')}")

d = r["data"]
u = d["usage"]
print("credit", d["credit"], d["currency"])
print(f"spent {u['spent']}: FRP {u['spent_frp']}, FDL {u['spent_fdl']}")
for soc, servers in d["prices"].items():
    for s in servers:
        print(f"  {soc.upper()} {s['serverid']:<10} {s['name']:<12} {'online' if s['online'] else 'offline':<7} {s['price']}")
Program.cs
// 4. Your credit, what you spent on FRP and FDL, and your price on each server.
// .NET 6 or later: dotnet new console, then replace Program.cs with this file.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;

const string BASE  = "https://xiaomi.source.af/api/reseller/v1";
const string TOKEN = "rst_YOUR_ACCOUNT_TOKEN";

var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", TOKEN);

async Task<JsonNode> Xst(string endpoint, JsonObject body)
{
    body["api_key"]  = "YOUR_API_KEY";
    body["email"]    = "you@example.com";
    body["password"] = "YOUR_PASSWORD";
    try
    {
        var res = await http.PostAsync($"{BASE}/{endpoint}",
            new StringContent(body.ToJsonString(), Encoding.UTF8, "application/json"));
        return JsonNode.Parse(await res.Content.ReadAsStringAsync())!;
    }
    catch (Exception)
    {
        return new JsonObject { ["error"] = "no_answer", ["message"] = "no answer from the server" };
    }
}

var r = await Xst("balance.php", new JsonObject());
if (r["error"] is not null) { Console.WriteLine($"refused: {r["error"]} - {r["message"]}"); return; }

var d = r["data"]!;
var u = d["usage"]!;
Console.WriteLine($"credit {d["credit"]} {d["currency"]}");
Console.WriteLine($"spent {u["spent"]}: FRP {u["spent_frp"]}, FDL {u["spent_fdl"]}");
foreach (var (soc, servers) in d["prices"]!.AsObject())
    foreach (var s in servers!.AsArray())
        Console.WriteLine($"  {soc.ToUpper()} {s!["serverid"],-10} {s["name"],-12} {((bool)s["online"]! ? "online" : "offline"),-7} {s["price"]}");

5The servers

Lists the servers you may use, your price on each, and whether each takes jobs now.

servers.sh
#!/bin/sh
# 5. The servers you may use, your price on each, and whether they take jobs now.
BASE='https://xiaomi.source.af/api/reseller/v1'
TOKEN='rst_YOUR_ACCOUNT_TOKEN'
AUTH='"api_key":"YOUR_API_KEY","email":"you@example.com","password":"YOUR_PASSWORD"'
post() { curl -s -X POST "$BASE/$1" -H 'Content-Type: application/json' -H "Authorization: Bearer $TOKEN" -d "{$AUTH$2}"; }

post servers.php ''
echo
servers.php
<?php
/* 5. The servers you may use, your price on each, and whether they take jobs now. */
const BASE  = 'https://xiaomi.source.af/api/reseller/v1';
const TOKEN = 'rst_YOUR_ACCOUNT_TOKEN';
const AUTH  = ['api_key' => 'YOUR_API_KEY', 'email' => 'you@example.com', 'password' => 'YOUR_PASSWORD'];

function xst(string $endpoint, array $body): array
{
    $ch = curl_init(BASE . '/' . $endpoint);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_POSTFIELDS     => json_encode(AUTH + $body),
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json', 'Authorization: Bearer ' . TOKEN],
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 30,
    ]);
    $raw = curl_exec($ch);
    curl_close($ch);
    return json_decode((string)$raw, true) ?: ['error' => 'no_answer', 'message' => 'no answer from the server'];
}

$r = xst('servers.php', []);
if (isset($r['error'])) { exit("refused: {$r['error']} - {$r['message']}\n"); }

foreach ($r['data']['services'] as $soc => $svc) {
    echo strtoupper($soc) . " -- without serverid: {$svc['price']}\n";
    foreach ($svc['providers'] as $s) {
        printf("  %-10s %-12s %-7s %s %s\n", $s['serverid'], $s['name'], $s['online'] ? 'online' : 'offline', $s['price'], $s['currency']);
    }
}
servers.py
"""5. The servers you may use, your price on each, and whether they take jobs now."""
import base64, json, sys, time, requests

BASE  = "https://xiaomi.source.af/api/reseller/v1"
TOKEN = "rst_YOUR_ACCOUNT_TOKEN"
AUTH  = {"api_key": "YOUR_API_KEY", "email": "you@example.com", "password": "YOUR_PASSWORD"}

def xst(endpoint, body):
    try:
        r = requests.post(f"{BASE}/{endpoint}", json={**AUTH, **body},
                          headers={"Authorization": f"Bearer {TOKEN}"}, timeout=30)
        return r.json()
    except (requests.RequestException, ValueError):
        return {"error": "no_answer", "message": "no answer from the server"}

r = xst("servers.php", {})
if "error" in r:
    sys.exit(f"refused: {r['error']} - {r.get('message')}")

for soc, svc in r["data"]["services"].items():
    print(f"{soc.upper()} -- without serverid: {svc['price']}")
    for s in svc["providers"]:
        print(f"  {s['serverid']:<10} {s['name']:<12} {'online' if s['online'] else 'offline':<7} {s['price']} {s['currency']}")
Program.cs
// 5. The servers you may use, your price on each, and whether they take jobs now.
// .NET 6 or later: dotnet new console, then replace Program.cs with this file.
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;

const string BASE  = "https://xiaomi.source.af/api/reseller/v1";
const string TOKEN = "rst_YOUR_ACCOUNT_TOKEN";

var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", TOKEN);

async Task<JsonNode> Xst(string endpoint, JsonObject body)
{
    body["api_key"]  = "YOUR_API_KEY";
    body["email"]    = "you@example.com";
    body["password"] = "YOUR_PASSWORD";
    try
    {
        var res = await http.PostAsync($"{BASE}/{endpoint}",
            new StringContent(body.ToJsonString(), Encoding.UTF8, "application/json"));
        return JsonNode.Parse(await res.Content.ReadAsStringAsync())!;
    }
    catch (Exception)
    {
        return new JsonObject { ["error"] = "no_answer", ["message"] = "no answer from the server" };
    }
}

var r = await Xst("servers.php", new JsonObject());
if (r["error"] is not null) { Console.WriteLine($"refused: {r["error"]} - {r["message"]}"); return; }

foreach (var (soc, svc) in r["data"]!["services"]!.AsObject())
{
    Console.WriteLine($"{soc.ToUpper()} -- without serverid: {svc!["price"]}");
    foreach (var s in svc["providers"]!.AsArray())
        Console.WriteLine($"  {s!["serverid"],-10} {s["name"],-12} {((bool)s["online"]! ? "online" : "offline"),-7} {s["price"]} {s["currency"]}");
}

Something not working?

Send us the request_id and the error code. With those two we can see exactly what happened to that job; without them we are guessing.

Xiaomi Source Tool · Token API · 2026-10-27