01Fonctionnement
Vous lisez le téléphone avec votre propre outil et envoyez ce que vous avez lu. Vous recevez un request_id immédiatement. Vous le renvoyez à status.php, ce qui lance la tâche sur le serveur choisi ; ce même appel vous dit ensuite où elle en est et vous remet le token de déverrouillage à écrire sur le téléphone. Nous ne touchons jamais au téléphone et vous n’avez jamais affaire au serveur.
token.php vérifie tout et répond aussitôt avec un request_id, comme WT-API-6AF684545B. Rien n’est encore envoyé à aucun serveur, et rien n’est débité.
Envoyez le request_id à status.php. Ce premier appel est votre reçu : c’est seulement là que la tâche démarre. Un request_id que personne ne confirme sous 30 secondes est annulé.
Interrogez de nouveau status.php toutes les quelques secondes jusqu’à ce que state soit done — avec le token — ou error, avec la raison et votre crédit rendu.
Les points d’accès
| POST token.php | Envoyer une demande de token : le request_id revient aussitôt. |
| POST status.php | Confirmer un request_id (cela lance la tâche) et le suivre jusqu’au token. |
| POST history.php | Vos tâches, par date et par service, et comment chacune s’est terminée. |
| POST balance.php | Votre crédit et où il est allé. |
| POST servers.php | Les serveurs que vous pouvez utiliser, et votre prix sur chacun. |
https://xiaomi.source.af/api/reseller/v1/…
02Identifiants
Quatre éléments vous identifient, et tous les quatre doivent désigner le même compte. Le jeton voyage dans l’en-tête ; les trois autres dans le corps. Si l’un d’eux est erroné, la réponse est toujours unauthorized — nous ne disons jamais lequel, car cela indiquerait à un attaquant ce qu’il doit conserver.
| Champ | Description |
|---|---|
| Authorization | Jeton de compte — À vous seul. Générez-le dans votre panneau, sous Vos identifiants, ou demandez-le-nous. Nous n’en stockons qu’un hachage : il n’est affiché qu’une seule fois et ne peut pas être récupéré — en générer un nouveau invalide immédiatement l’ancien. |
| api_key | Clé API — Dans le corps, à chaque requête. |
| E-mail — L’adresse de votre compte. Si elle change, prévenez-nous, sinon les appels cessent de fonctionner. | |
| password | Mot de passe API — Le mot de passe API de votre compte. Par défaut, c’est votre mot de passe de connexion. Si vous en voulez un distinct, définissez-le dans votre panneau, sous Compte → Mot de passe API ; Utiliser mon mot de passe de connexion rétablit le réglage par défaut, et le mot de passe distinct cesse immédiatement de fonctionner. Nous pouvons aussi le définir pour vous. Dans tous les cas, il n’est jamais écrit dans nos journaux ni stocké avec la tâche. |
Chaque point de terminaison est en POST sur HTTPS. Un GET est refusé avec method_not_allowed ; un corps qui n’est pas du JSON est refusé avec bad_json.
03La requête
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"
}Tous les champs ci-dessous vont dans le corps JSON.
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| api_key | string | oui | Votre clé API. |
| string | oui | L’e-mail de votre compte. | |
| password | string | oui | Votre mot de passe API — par défaut identique à votre mot de passe de connexion, sauf si vous en avez défini un distinct dans votre panneau, sous Compte → Mot de passe API. Voir Identifiants. |
| soc | string | oui | Quel service. frp = suppression du FRP, fdl = Fastboot vers EDL. Il n’y a pas de valeur par défaut — l’omettre provoque une erreur plutôt qu’une supposition qui pourrait vous facturer le mauvais service. |
| configblob | string | oui | La lecture complète du téléphone, sous forme d’encodage base64 d’un objet JSON. Ce qu’elle doit contenir dépend de soc — voir Le blob de l’appareil. |
| serverid | string | non | L’id public du serveur qui doit exécuter la tâche, p. ex. server1 — depuis servers.php, ou depuis votre panneau sous Serveurs. Strict : ce serveur l’exécute, ou l’appel est refusé avec la raison. Omettez-le et nous choisissons le serveur le moins cher qui accepte des tâches. Propriétaires d’outil : envoyez-le toujours. Voir Choisir un serveur. |
| request_date | string | non | Facultatif. Le moment où votre serveur a envoyé cette requête, en ISO-8601 avec Z ou un décalage. Renvoyé tel quel dans client_sent et jamais utilisé dans nos calculs. Si vous l’omettez, rien ne change, sauf que client_sent et client_clock_offset_seconds reviennent à null. |
| request_language | string | non | Optional. 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. |
04Le blob de l’appareil
Le blob est l’encodage base64 d’un objet JSON contenant la lecture complète du téléphone — tout ce que votre outil lit dans ce mode, pas seulement le token. Ce qui est obligatoire dépend de soc : pour frp, le téléphone est en mode Mi Assistant (sideload / recovery) ; pour fdl, il est en mode Fastboot. Envoyez chaque valeur exactement telle que le téléphone l’a renvoyée — ni raccourcie, ni reformatée.
frp — le téléphone en mode Mi Assistant (sideload / recovery)
| Champ | Lu avec | Obligatoire | Description | Exemple |
|---|---|---|---|---|
| token | getmitoken: | oui — null est refusé | Le challenge FRP, exactement tel que lu | 8f21c0b4e9d7… |
| deviceName | getdevice: | oui — null est refusé | Le nom de code avec son suffixe de région | fleur_global |
| version | getversion: | oui — null est refusé | Le build de la ROM | OS2.0.207.0.VMTMIXM |
| serial | getsn: | oui — null est refusé | Le numéro de série | fb4d921c |
| codebase | getcodebase: | oui — null si non lu | La version d’Android | 15 |
| region | getregion: | oui — null si non lu | La région de la ROM | IR |
| cpuid | getcpuid: | oui — null si non lu | Le CPU ID, si le téléphone le renvoie | 0x000460E1 |
| recovery | getrecoveryversion: | oui — null si non lu | La version du recovery — utilisée seulement par notre liste d’appareils non pris en charge | 2 |
| branch | getbranch: | non — null est accepté | La branche de la ROM | F |
| language | getlanguage: | non — null est accepté | La langue de l’appareil — utilisée seulement par notre liste d’appareils non pris en charge | fa |
| romzone | getromzone: | non — null est accepté | La ROM zone — utilisée seulement par notre liste d’appareils non pris en charge | 2 |
{
"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"
}Pour frp, n’envoyez pas productName. Il est ignoré : nous déduisons le produit de deviceName (fleur_global → fleur), exactement comme le fait notre propre outil. Le build de la ROM va dans version. Une version antérieure de cette page indiquait de mettre le build de la ROM dans productName — si votre client le fait, déplacez-le dans version.
fdl — le téléphone en mode Fastboot
| Champ | Lu avec | Obligatoire | Description | Exemple |
|---|---|---|---|---|
| token | oem get_token (getvar:token) | oui — null est refusé | Le token fastboot, exactement tel que lu | VQHzN2Ib… |
| product | getvar:product | oui — null est refusé | Le nom de code, sans région | fleur |
| serial | getvar:serialno | oui — null est refusé | Le numéro de série | fb4d921c |
| cpuid | getvar:cpuid | oui — null si non lu | Le CPU ID, si le téléphone le renvoie | 0x000460E1 |
{
"token": "VQHzN2Ib…",
"product": "fleur",
"serial": "fb4d921c",
"cpuid": "0x000460E1"
}Tout champ peut être envoyé à null (null JSON ou le texte "null") quand le téléphone n’a pas pu le lire — jamais unknown, N/A ni un texte vide. La suite dépend du champ : marqué oui — null est refusé, les serveurs construisent la tâche à partir de lui, donc la requête est refusée avec incomplete_configblob, le champ est nommé dans nulls et rien n’est débité — relisez le téléphone et envoyez les informations complètes ; marqué oui — null si non lu, il doit toujours figurer dans le blob, et un null laisse la tâche s’exécuter normalement (omis complètement, il est refusé) ; marqué non — null est accepté, envoyez-le à null ou omettez-le — la tâche s’exécute. Une tâche envoyée avec des champs null s’exécute avec ce que vous avez envoyé : si son token ne fonctionne ensuite pas sur le téléphone parce qu’un champ n’a pas été lu, elle ne peut pas être remboursée. Envoyez tous les champs que le téléphone renvoie.
Certains serveurs ont besoin de cpuid pour vérifier la région de l’appareil. Envoyez-le toujours quand le téléphone en donne un (en mode Mi Assistant, le numéro de série 0x… est le CPU ID). S’il est omis, il est pris comme null et le travail continue.
{
"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"
}Autres noms que nous acceptons pour les mêmes champs : authKey ou auth_key pour token ; devicename ou device_name pour deviceName ; osVersion ou os_version pour version ; serial_number ou serialNumber pour serial (pour fdl, également serialno) ; productName ou productname pour product ; cpu_id pour cpuid.
Vérifié avant tout débit
Tout est vérifié avant toute réservation ou tout débit. Un champ obligatoire (oui — null est refusé) omis est nommé dans missing ; envoyé à null, dans nulls ; contenant une lecture échouée — N/A, unknown, none, un texte d’erreur —, dans invalid. Chacun est refusé avec HTTP 400 incomplete_configblob, avec dans mode le mode dans lequel le téléphone doit se trouver : Mi Assistant ou Fastboot. Envoyez les champs facultatifs chaque fois que le téléphone les renvoie ; un champ absent, null ou contenant une lecture échouée est simplement écarté et jamais transmis — il n’empêche pas la tâche. Un blob dont le token est absent ou null reçoit la réponse missing_token.
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.Nous construisons nous-mêmes, à partir de ces champs, le format amont propre à chaque serveur : vous n’envoyez donc jamais une structure spécifique à un serveur donné — et changer de serverid ne modifie jamais ce que vous envoyez.
Ce que vous envoyez, et ce que vous recevez en retour :
| Vous envoyez | Nous renvoyons | Signification |
|---|---|---|
| deviceName | device_name | Le nom de code avec son suffixe de région (frp) |
| — | product_model | Le nom de code, sans région — nous le déduisons de deviceName (frp) |
| product | product_model device_name | Le nom de code, sans région (fdl) |
| version | device_version | Le build de la ROM (frp) |
| serial | serial_number | Le numéro de série |
| codebase | codebase | La version d’Android (frp) |
| branch | branch | La branche de la ROM (frp) |
| region | region | La région de la ROM (frp) |
| cpuid | cpu_id | Le CPU ID, si le téléphone le renvoie |
| token | — | Non répété dans device |
Un champ qui ne s’applique pas au service revient avec la valeur null au lieu d’être absent : un même parseur lit ainsi les deux services.
05Appareils pris en charge
Certains numéros de build ne sont pas pris en charge, et certains pays peuvent ne pas l’être non plus. Pour savoir lesquels, demandez à l’administrateur sur Telegram ou WhatsApp une fois votre API de propriétaire d’outil obtenue.
Un appareil de notre propre liste d’appareils non pris en charge est refusé aussitôt par token.php avec unsupported (422) : pas de request_id, rien d’envoyé à aucun serveur, rien de facturé. La liste peut tester chaque champ du configblob — modèle, firmware, version d’Android, branche, langue, région, version du recovery, ROM zone, numéro de série et CPU id — envoyez donc tout ce que votre outil a lu.
Attention : envoyer un modèle non pris en charge vous coûte son crédit — il n’est pas remboursé — et peut parfois faire bannir votre compte.
Le même appareil non pris en charge, encore et encore : chaque refus d’un appareil comme non pris en charge (unsupported) est compté pour votre compte et cet appareil sur 5 minutes. Le 4e refus porte un avertissement dans son message et strike.warning à true (strike = {count, max, window_seconds, warning} figure sur chacun de ces refus) : renvoyer cet appareil bloquera votre compte. Un refus de plus dans ces 5 minutes restreint votre compte pendant 5 minutes : chaque nouvelle requête reçoit account_restricted (403). Arrêtez-vous à l’avertissement et proposez à votre client un serveur qui accepte l’appareil.
Quand un appareil est refusé pour sa région et qu’un de vos serveurs est premium pour cette région, le refus unsupported porte region, suggested, premium (comme dans check.php) et takes_on, et son message nomme ce serveur : “La région de votre appareil est CN. Cet appareil doit être traité avec Server 6.3 — un serveur premium : son taux de réussite de suppression FRP pour cet appareil est très élevé.” Rien n’a été débité.
Quand les serveurs premium de la région sont tous éteints ou sans place libre, rien n’est suggéré (suggested vaut null), slots_full vaut true et message le dit : “★ Server 6.3 prend en charge la région AE de votre appareil, mais ses places sont pleines pour le moment. Revenez un peu plus tard, ou suivez notre chaîne Telegram …” Affichez-le et laissez votre client réessayer plus tard, plutôt que d’envoyer l’appareil à un serveur qui n’accepte pas sa région. Un serveur premium est toujours nommé avec son étoile (★).
Quand le serveur choisi refuse lui-même un appareil, la réponse unsupported de status.php porte server_message : la phrase du serveur lui-même, sans aucun nom de fournisseur, p. ex. “This device is not supported (China Device Not Supported) · CPUID: 0xe4f01f4c · Product: star · IMEI: 867869053687315” ; le message se termine aussi par elle. data.amount_charged indique si le travail a été débité : un serveur qui refuse un appareil avant d’exécuter le travail ne débite rien, et nous non plus.
Demandez à l’administrateur : Telegram: @RaminGSM WhatsApp: +93799691689
06Choisir un serveur
Chaque serveur a un identifiant public que nous lui avons attribué — server1, server2… — et un nom, comme Server 1. Les deux sont listés par servers.php, sous serverid et name, ainsi que dans votre panneau, sous Serveurs. Pour exécuter une tâche sur l’un d’eux, envoyez son identifiant dans serverid.
# 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", … } }Omettez serverid et nous choisissons pour vous : le serveur le moins cher qui accepte des tâches et que votre compte peut utiliser. Si vous en indiquez un, la règle est stricte : ce serveur exécute la tâche, ou l’appel est refusé — unknown_serverid (404) si aucun serveur n’a cet identifiant, server_not_allowed (403) s’il n’est pas activé pour votre compte, server_offline (503) s’il ne peut pas assurer ce service en ce moment. Nous ne vous redirigeons jamais vers un autre serveur à votre insu.
Certains serveurs se composent de plusieurs machines que l’on peut choisir une à une. servers.php liste chaque machine comme un serveur à part entière, avec son propre prix — par exemple serverid server6-2, name Server 6.2. Envoyez ce serverid et la tâche s’exécute exactement sur cette machine, à son prix, ou est refusée avec le motif. Envoyez l’id du serveur lui-même (server6) et nous utilisons la moins chère de ses machines qui accepte des tâches.
Nous vérifions le serveur avant toute réservation ou tout débit. S’il ne peut pas prendre la tâche, vous recevez server_offline et rien n’est dépensé — choisissez-en un autre dans servers.php et renvoyez, ou omettez serverid et laissez-nous choisir.
{ "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.Propriétaires d’outil : envoyez toujours serverid. Lisez la liste avec servers.php (ou sous Serveurs dans votre panneau), laissez votre client choisir et envoyez cet id. Le token vient alors de ce serveur, ou la réponse dit exactement pourquoi non — il n’est jamais pris sur un autre serveur.
Chaque réponse nomme le serveur qui exécute la tâche, avec serverid et server_name — même quand vous nous laissez choisir — pour distinguer un serveur lent d’un appareil lent.
07Avant de recevoir un request_id
token.php ne donne un request_id que lorsque toutes les vérifications ci-dessous sont réussies, dans cet ordre. La première qui échoue est votre réponse — avec son code error — et il n’y a pas de request_id : rien n’a été créé, retenu ni débité.
| # | Ce que nous vérifions | En cas d’échec |
|---|---|---|
| 1 | La requête est un POST avec un corps JSON de 256 Ko au plus. | method_not_allowed 405bad_json 400body_too_large 413 |
| 2 | Vos quatre identifiants désignent le même compte actif : le token du compte, api_key, email et password. | unauthorized 401too_many_failures 429 |
| 3 | La requête vient d’une adresse de la liste autorisée de votre compte, si vous en avez défini une. | ip_not_allowed 403 |
| 4 | Vous êtes dans votre limite de requêtes par heure. | rate_limited 429 |
| Le compte n’est pas restreint pour avoir envoyé encore et encore un appareil non pris en charge (voir Appareils pris en charge). | account_restricted 403 | |
| 5 | soc vaut frp ou fdl, ce service est vendu à votre compte et il est activé. | missing_soc 400bad_service 400not_entitled 403disabled 503 |
| 6 | configblob est valide et contient la lecture complète dont le mode du téléphone a besoin. | missing_configblob 400bad_configblob 400missing_token 400incomplete_configblob 400 |
| 7 | L’appareil ne figure pas sur notre liste des appareils que nous ne servons pas. | unsupported 422 |
| 8 | Le serveur indiqué dans serverid existe, est activé pour votre compte et accepte des tâches en ce moment : allumé, tarifé, en ligne et prêt de notre côté. | unknown_serverid 404server_not_allowed 403server_offline 503 |
| Vous n’avez indiqué aucun serveur : nous prenons le serveur le moins cher qui accepte des tâches et que votre compte peut utiliser, et la réponse le nomme. | service_not_allowed 403service_offline 503 | |
Une vérification préalable des informations de l’appareil sur le serveur choisi : la région propre de l’appareil est lue et comparée à notre liste ; une région que le serveur choisi ne prend pas en charge est refusée avec unsupported (la réponse porte region de l’appareil, takes_on, suggested et premium). L’état de flash est lu aussi, mais il ne décide de rien. Cela se passe avant qu’un request_id soit donné, donc rien n’est débité. Si la vérification préalable ne répond pas, elle est sautée et la liste d’appareils ci-dessus s’applique toujours. | unsupported 422 | |
| 9 | Le même appareil — le même token d’appareil, pour le même service et le même serveur — n’a pas été envoyé dans les 30 dernières minutes. S’il l’a été et que cette tâche a démarré, vous récupérez cette tâche : son request_id et son état, ou son token — jamais une seconde tâche ni un second débit. Une tâche qui a échoué, ou dont le request_id n’a jamais été confirmé, est remplacée par une nouvelle. | — |
| 10 | Votre crédit couvre le prix d’une tâche sur ce serveur — le price que servers.php affiche pour lui. C’est vérifié ici ; le prix n’est retenu qu’au démarrage de la tâche. | no_credit 402 |
Toutes les vérifications sont réussies : vous recevez le request_id aussitôt, avec state waiting. Rien n’a été envoyé à aucun serveur, et rien n’est retenu tant que vous ne l’avez pas confirmé avec status.php. | ||
08Votre request_id
La réponse arrive aussitôt : HTTP 202 avec state waiting. Le request_id est le WorkID de la tâche — vous voyez le même WT-API-6AF684545B dans votre panneau et pouvez l’y rechercher. Gardez-le : confirmer la tâche, la suivre, récupérer le token et nous poser une question à son sujet passent tous par ce request_id. Un request_id donné avant le 2026-10-17, comme Api123, fonctionne toujours partout et reçoit sa réponse sous cette forme.
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.Confirmez-le sous 30 secondes. Envoyez-le à status.php : cet appel est votre reçu et lance la tâche. Un request_id que personne ne confirme sous 30 secondes est annulé — rien n’a été envoyé à aucun serveur et rien n’a été débité — et un appel ultérieur avec lui répond request_expired : renvoyez la demande pour un nouveau request_id. Ainsi, si une réponse ne vous est jamais parvenue, rien ne tourne que vous ne puissiez suivre.
| Champ | Description |
|---|---|
| request_id | Notre id pour cette tâche — son WorkID, comme WT-API-6AF684545B. Il sert à confirmer, suivre et récupérer la tâche, et à nous la citer pour toute question. |
| state | waiting |
| confirm_within | Secondes restantes pour le confirmer avec status.php. |
| price | Ce que la tâche vous coûtera sur ce serveur — retenu au démarrage, débité seulement si le token arrive. |
| serverid | L’identifiant public du serveur qui a exécuté la tâche — le même serverid que celui que vous pouvez envoyer. |
| server_name | Le nom public de ce serveur, p. ex. Server 1. |
Le même appareil — le même token d’appareil, pour le même service et le même serveur — n’a pas été envoyé dans les 30 dernières minutes. S’il l’a été et que cette tâche a démarré, vous récupérez cette tâche : son request_id et son état, ou son token — jamais une seconde tâche ni un second débit. Une tâche qui a échoué, ou dont le request_id n’a jamais été confirmé, est remplacée par une nouvelle.
09Confirmer et suivre
Envoyez le request_id à status.php avec les mêmes quatre identifiants. Le premier appel dans les 30 secondes lance la tâche et répond pending aussitôt ; ensuite, interrogez de nouveau toutes les retry_after secondes jusqu’à ce que state ne soit plus pending. Une tâche prend en général 15–25 secondes.
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.Ce que dit state
| État | HTTP | Signification et marche à suivre |
|---|---|---|
pending | 202 | La tâche est au serveur. Réinterrogez après retry_after secondes. |
done | 200 | Le token est dans response_token : écrivez-le sur le téléphone. La tâche est débitée ; la réponse indique son coût et votre crédit après. |
error | 4xx · 5xx | Pas de token. error dit pourquoi — server_offline, unsupported, server_unavailable, failed ou no_credit. Le prix revient intégralement sur votre crédit, sauf quand le serveur dit ne pas prendre l’appareil en charge (unsupported) : cette tâche est débitée, comme l’indique amount_charged. |
expired | 410 | Non confirmé sous 30 secondes : annulé, rien envoyé, rien débité. Renvoyez la demande pour un nouveau request_id. |
not_found | 404 | Aucune demande avec ce request_id n’appartient à votre compte. |
Le token est conservé avec la tâche, sur notre serveur. Si une réponse ne vous est jamais parvenue, interrogez de nouveau status.php avec le même request_id : tant que la tâche tourne vous recevez pending, une fois terminée vous recevez le token — aussi souvent que vous le demandez, sans frais. Une tâche qui a échoué dit pourquoi.
# 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 a sa propre limite — 120 appels par minute et par compte — et ne compte pas dans votre quota horaire : suivre une tâche n’entame jamais vos demandes de token.
10La réponse
Une seule forme pour toute réponse sur une tâche, qu’elle vienne de token.php, status.php ou history.php. state vaut waiting, pending, done, error ou expired ; le statut HTTP dit la même chose, vous pouvez donc décider avant d’analyser.
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
}
}
}Tous les champs de la réponse :
| Champ | Description |
|---|---|
| request_id | Notre id pour cette tâche — son WorkID, comme WT-API-6AF684545B. Il sert à confirmer, suivre et récupérer la tâche, et à nous la citer pour toute question. |
| soc | Le service exécuté. |
| serverid | L’identifiant public du serveur qui a exécuté la tâche — le même serverid que celui que vous pouvez envoyer. |
| server_name | Le nom public de ce serveur, p. ex. Server 1. |
| configblob | Le configblob que vous avez envoyé, octet pour octet. |
| device | L’appareil, décodé. Toujours présent. |
| state | waiting, pending, done, error ou expired. |
| response_token | Le token de déblocage. C’est ce que vous écrivez sur le téléphone. |
| provider_ref | La référence de tâche propre au serveur, pour les questions au support. |
| amount_charged | Ce que cette tâche vous a coûté. |
| credit | Votre crédit après le débit. |
| currency | La devise des deux montants. |
Un échec
# 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.Les trois blocs de temps
Trois blocs, qui s’additionnent : start_date.elapsed_seconds plus end_date.elapsed_seconds égale exactement response_date.total_seconds, car les trois sont calculés à partir des mêmes instants.
Dans l’exemple détaillé ci-dessous, la tâche a mis 5 secondes à récupérer le résultat et 3 secondes à le renvoyer, soit 8 secondes au total.
| Champ | Description |
|---|---|
| start_date — l’obtention du token | |
| client_sent | quand votre serveur a envoyé la requête — exactement ce que vous avez envoyé |
| server_received | quand la requête a atteint notre serveur |
| upstream_sent | quand nous l’avons transmise au serveur de tokens |
| upstream_received | quand la réponse du serveur de tokens nous est parvenue |
| elapsed_seconds | de l’arrivée au token en main — 5 secondes dans l’exemple |
| end_date — le renvoi du token | |
| upstream_answered | quand le serveur de tokens nous a répondu |
| server_answered | quand notre serveur vous a répondu |
| elapsed_seconds | l’étape de livraison seule — 3 secondes dans l’exemple |
| response_date — l’ensemble | |
| total_seconds | 5 + 3 = 8 secondes |
| client_clock_offset_seconds | l’écart entre votre horloge et la nôtre |
Les blocs de temps sont mesurés depuis la réception de la demande par token.php. end_date se termine quand la réponse avec le token nous a quittés : il inclut donc le temps que vous avez attendu entre vos appels à status.php.
Pourquoi votre propre horodatage n’est pas utilisé dans les calculs
Cet horodatage est renvoyé dans client_sent et c’est le vôtre, pas le nôtre — si l’horloge de votre serveur avance d’une minute, un calcul basé sur lui donnerait une durée négative. Chaque durée écoulée provient de notre horloge, et l’écart entre les deux est indiqué séparément dans client_clock_offset_seconds, pour que vous puissiez le voir au lieu qu’il soit discrètement intégré à un chiffre sur lequel vous facturez.
11Lectures d’informations de l’appareil (soc)
Au-delà de frp et fdl, les comptes propriétaires d’outil peuvent aussi lire les informations d’un appareil. Ces services fonctionnent selon le même flux token.php puis status.php, mais n’utilisent aucun serveur et, au lieu d’un token, renvoient les informations de l’appareil sous forme de champs (fields). Ils sont gratuits pour l’instant (un prix pourra être fixé plus tard).
Les valeurs de soc
| soc | Ce qu’il fait | Entrée | Réponse en |
|---|---|---|---|
| frp | Déverrouillage FRP ; renvoie un token de réponse. | configblob — la lecture complète de l’appareil | Aussitôt |
| fdl | Autorisation FDL ; renvoie un token de réponse. | configblob — la lecture complète de l’appareil | Aussitôt |
| read_sideload | Lit les informations de l’appareil en mode Mi Assistant. | configblob — seul le token de l’appareil est nécessaire | Aussitôt |
| read_fastboot | Lit les informations de l’appareil en mode fastboot. | configblob — seul le token de l’appareil est nécessaire | Aussitôt |
| read_imei | Recherche l’IMEI dans la base de données du fabricant. | imei — l’IMEI à 15 chiffres | De 1 à 5 minutes ; continuez à interroger |
| read_lockcode | Renvoie la fiche de l’appareil derrière un code de verrouillage. | imei — l’IMEI pour la commande | Quelques minutes ; continuez à interroger |
Ces lectures sont réservées aux comptes propriétaires d’outil. read_imei n’apparaît que lorsqu’il est ouvert à l’API ; sinon, il est disponible dans l’outil XST.
Envoyer une lecture
Une lecture s’envoie et se vérifie exactement comme frp et fdl : token.php vous donne un request_id, votre premier appel à status.php la démarre, et vous interrogez jusqu’à ce qu’elle soit terminée. Elle n’utilise aucun serveur et ne facture aucun prix de serveur.
# 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.Ce que renvoie une lecture terminée
Au lieu d’un response_token, une lecture répond avec un objet fields — les informations de l’appareil sous forme de paires étiquette/valeur. Les étiquettes dépendent du service ; un champ laissé vide par la source est omis.
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: 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: 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).
12L’historique de vos tâches
Vos tâches, de la plus récente à la plus ancienne : quand chacune a été envoyée, à quel serveur et comment elle s’est terminée — par date et par service. Les mêmes quatre identifiants ; il ne coûte aucun crédit et compte dans votre quota horaire.
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
| from | string | non | Un jour, 2026-10-08 (ou 2026-Oct-08), en UTC : les tâches envoyées ce jour-là ou après. |
| to | string | non | Un jour : les tâches envoyées ce jour-là ou avant, la journée entière comprise. |
| soc | string | non | frp ou fdl : seulement les tâches de ce service. |
| limit | number | non | Combien de tâches lister, 1–100. Par défaut 1. |
| before | string | non | Un request_id : les tâches plus anciennes que lui. Envoyez le next_before d’une réponse pour la page suivante. |
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 et counts couvrent toute la période, pas seulement cette page : combien de demandes vous avez envoyées, et combien se sont terminées done, error, expired, pending ou waiting ; spent est ce qu’elles vous ont coûté. Chaque tâche sous jobs a son request_id — son WorkID : WT-API-6AF684545B pour une tâche envoyée par cette API, WT-WEB-… pour une tâche lancée dans notre propre outil, comme l’indique door — avec soc, serverid et server_name, state, error et message quand elle n’a pas donné de token, amount (ce qu’elle vous a coûté), le device, et ses dates d’envoi et de dernière modification. Le token lui-même n’est pas listé : status.php le donne par request_id.
13Crédit et utilisation
Combien de crédit il reste, et où est passé le reste : les mêmes chiffres que les cartes en haut de votre panneau. Cet appel accepte les quatre mêmes identifiants qu’une demande de token, ne coûte aucun crédit et répond une fois toutes les 10 secondes.
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 }
]
}
}
}Sous usage : bought est tout le crédit ajouté à votre compte, moins ce que l’administrateur a repris ; spent est ce qu’ont coûté vos tâches, moins les remboursements — réparti par service en spent_frp et spent_fdl, et par canal en spent_api (via cette API) et spent_xst (dans notre propre outil) ; sent_to_users est ce que vous avez donné à vos propres utilisateurs si vous avez été revendeur ; left est ce qui reste, identique à credit. Le compte est juste : bought − sent_to_users − spent = left, et spent_frp + spent_fdl = spent. prices est le prix d’une tâche sur chaque serveur pour vous, par service : son serverid, son name, s’il est online, et le price, dans votre devise.
Interrogé de nouveau en moins de 10 secondes, il répond 429 too_soon avec retry_after : attendez ce nombre de secondes. Rien n’est bloqué.
14Serveurs et prix
Il accepte les quatre mêmes identifiants qu’une demande de token et ne coûte aucun crédit. Chaque appel compte dans votre quota horaire.
Ce que vous pouvez utiliser, et ce que chaque tâche vous coûte.
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":[ … ] }
}
}
}Chaque ligne sous providers est un serveur que vous pouvez utiliser — tous, y compris ceux éteints en ce moment (online false), d’abord ceux qui acceptent des tâches et les moins chers. serverid et name sont ce que vous envoyez et ce que chaque réponse renvoie, price est le prix d’une tâche sur lui pour vous, et online dit si une demande qui le désigne est acceptée maintenant. Choisir un serveur éteint est votre choix : token.php le refuse avec server_offline avant tout request_id — utilisez un autre serveur ou réessayez plus tard. Le price à côté de la liste est le prix d’une demande sans serverid : le serveur le moins cher qui accepte des tâches.
Chaque ligne indique aussi quels pays ce serveur accepte, d’après la region de l’appareil : region_support — les pays qu’il accepte alors que certains de nos serveurs ne les acceptent pas (ou que nous indiquons comme acceptés là), region_not_support — les pays qu’il n’accepte pas. Les deux vides : tous les pays. Montrez-les à votre client comme rappel, p. ex. « Server 6.3 — Chine : oui, Inde : non ». Seul le pays est publié : nos autres conditions (un modèle, une version, un CPU ID…) ne sont pas listées et restent chez nous. La décision est toujours prise ici, avant tout envoi — token.php refuse un appareil qu’un serveur n’accepte pas avec unsupported (422), et check.php répond pour un appareil.
La réponse de servers peut être montrée sans risque à vos propres clients : elle contient nos noms de serveurs publics et votre prix, jamais notre coût ni le fournisseur en amont.
15Quels serveurs acceptent un appareil
Une vérification à blanc : les mêmes identifiants et le même corps que token.php (soc, configblob), les mêmes contrôles du configblob et les mêmes erreurs — puis rien : aucun request_id, aucune tâche, rien de réservé ni de facturé, rien d’envoyé à aucun serveur. Certains serveurs n’acceptent pas certains appareils (une version, un pays…) ; cet appel vous dit, pour cet appareil, lesquels de vos serveurs l’acceptent. Il a sa propre limite, 60 appels par minute, et ne compte pas dans votre quota horaire.
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."
}
}Les lignes sous servers sont celles que liste servers.php, dans son ordre, y compris celles qui sont éteintes. takes indique si ce serveur accepte cet appareil ; sinon, reason dit pourquoi. Un serveur peut être éteint (online false) et accepter quand même l’appareil : dites à votre client de réessayer plus tard. suggested est le premier serveur qui accepte l’appareil et qui est allumé (le moins cher d’abord), ou null ; refused_everywhere vaut true quand aucun de vos serveurs ne l’accepte. device est ce que nous avons lu dans le configblob. Quand token.php refuse un appareil avec unsupported (422), sa réponse porte takes_on : les serverid qui l’acceptent.
La réponse lit aussi la région propre de l’appareil avec la même vérification préalable des informations de l’appareil que token.php exécute — un téléphone peut annoncer une région qui n’est pas la sienne. region est la région sur laquelle les serveurs ont été jugés : ce que la vérification préalable a lu quand region_checked vaut true, sinon ce que disait le configblob. flash_status est l’état de flash lu (vide s’il est inconnu). premium liste vos serveurs qui acceptent cet appareil et sont premium pour sa région (ils sont faits pour les appareils de ce pays, leur taux de réussite y est donc très élevé) ; suggested est alors l’un d’eux, et message une phrase que vous pouvez montrer à votre client, p. ex. “La région de votre appareil est CN. Cet appareil doit être traité avec Server 6.3 — un serveur premium …” (vide s’il n’y en a pas). Présélectionnez ce serveur pour votre client, dites pourquoi, et laissez-le changer de serveur, annuler ou continuer. La réponse de la vérification préalable est gardée 15 minutes : le token.php qui suit ne redemande pas.
Posez-la après avoir lu un appareil, pour griser les serveurs qui le refuseraient, ou après que token.php a répondu unsupported pour le serveur choisi par votre client, pour proposer celui qui l’accepte. Une demande sans serverid est envoyée d’elle-même au serveur le moins cher qui accepte l’appareil.
16Codes d’erreur
Chaque échec a la même forme : un code error sur lequel baser votre logique et un message destiné à un humain. Basez-vous sur le code, jamais sur le texte du message — la formulation change, les codes non. Les échecs de token.php survenant après l’authentification contiennent aussi les blocs de temps, de sorte qu’un échec lent est aussi mesurable qu’un succès lent. Certains ajoutent leurs propres champs : incomplete_configblob nomme les champs dans missing et invalid, avec mode ; rate_limited contient limit et used. Quel que soit le code, une tâche qui n’a livré aucun token ne coûte rien — les refus interviennent avant toute réservation, et une tâche qui échoue après l’envoi voit sa réservation intégralement remboursée.
| Code | HTTP | Signification et marche à suivre |
|---|---|---|
| unauthorized | 401 | L’un des quatre identifiants est erroné, ou le compte est désactivé. Nous ne précisons volontairement pas lequel. |
| ip_not_allowed | 403 | Les quatre identifiants sont corrects, mais la requête provient d’une adresse qui ne figure pas dans la liste autorisée de votre compte. La réponse l’indique dans ip. Ajoutez-la dans votre panneau (Compte → IP autorisées), ou envoyez depuis une adresse autorisée. Rien n’a été exécuté ni débité. |
| too_many_failures | 429 | Trop de tentatives échouées depuis votre adresse. Patientez, puis corrigez l’identifiant avant de réessayer. |
| rate_limited | 429 | Vous avez dépassé une limite : votre quota horaire (token.php, history.php, balance.php, servers.php) ou 120 vérifications d’état par minute. La réponse contient limit, used et per. |
| too_soon | 429 | balance.php a été interrogé de nouveau en moins de 10 secondes. La réponse indique combien de secondes attendre (retry_after et l’en-tête Retry-After) ; rien n’est bloqué. |
| method_not_allowed | 405 | Vous avez utilisé GET. Tous les points de terminaison sont en POST. |
| bad_json | 400 | Le corps n’était pas un objet JSON. Définissez Content-Type: application/json. |
| body_too_large | 413 | Le corps dépasse 256 Ko. |
| missing_soc | 400 | soc n’a pas été envoyé. Il n’a volontairement aucune valeur par défaut. |
| bad_service | 400 | soc doit valoir exactement frp ou fdl. |
| not_entitled | 403 | Ce service n’est pas vendu à votre compte. Demandez-nous de l’activer. |
| disabled | 503 | Nous avons désactivé ce service pour le moment. C’est temporaire. |
| missing_configblob | 400 | configblob n’a pas été envoyé. |
| bad_configblob | 400 | configblob n’est pas un objet JSON valide encodé en base64. |
| missing_token | 400 | Le token de l’appareil est absent du blob. |
| incomplete_configblob | 400 | Un champ dont les serveurs ont besoin est omis (missing), envoyé à null (nulls) ou contient une lecture échouée (invalid) ; mode est le mode dans lequel le téléphone doit se trouver. Relisez le téléphone et envoyez les informations complètes ; rien n’a été débité. |
| use_serverid | 400 | Vous avez envoyé services ou server_id, l’ancienne façon de choisir un serveur par sa position. Ce n’est plus accepté : envoyez serverid (depuis servers.php), ou omettez-le et laissez-nous choisir. |
| use_status | 400 | Vous avez envoyé un request_id à token.php, qui ne lance que de nouvelles demandes. Envoyez le request_id à status.php. |
| unknown_serverid | 404 | Aucun serveur n’a ce serverid. Pour obtenir la liste, appelez servers.php ou consultez Serveurs dans votre panneau. |
| server_not_allowed | 403 | Le serveur indiqué n’est pas activé pour votre compte. Nous ne vous redirigeons pas vers un autre : choisissez-en un dans votre liste, ou omettez serverid. |
| service_not_allowed | 403 | Vous n’avez indiqué aucun serveur et aucun n’est activé pour votre compte. Demandez-nous d’en activer un. |
| server_offline | 503 | Le serveur indiqué ne peut pas assurer ce service en ce moment : éteint, hors ligne ou pas prêt de notre côté. token.php le vérifie avant de donner un request_id, et la tâche le vérifie de nouveau à son démarrage ; s’il s’est arrêté entre-temps, ou pendant la tâche, la réservation est remboursée. Rien n’a été débité. Utilisez un autre serveur de servers.php, ou réessayez plus tard. |
| service_offline | 503 | Vous n’avez indiqué aucun serveur et aucun n’accepte de tâches pour ce service en ce moment. Rien n’a été débité. Réessayez dans un instant. |
| no_credit | 402 | Crédit insuffisant pour une tâche sur ce serveur — refusé par token.php avant tout request_id, ou, si votre crédit a baissé entre-temps, au démarrage de la tâche. Rien n’a été débité. Rechargez, ou choisissez un serveur moins cher. |
| bad_imei | 400 | imei était manquant ou invalide : read_imei a besoin de l’IMEI à 15 chiffres ; read_lockcode a besoin de l’IMEI utilisé pour la commande. |
| read_failed | 502 | Une lecture (read_sideload, read_fastboot, read_imei ou read_lockcode) n’a pas pu être effectuée. Rien n’a été débité. Renvoyez-la ou réessayez plus tard. |
| missing_request_id | 400 | status.php a été appelé sans request_id. |
| not_found | 404 | Aucune demande avec ce request_id n’appartient à votre compte. |
| request_expired | 410 | Le request_id n’a pas été confirmé avec status.php sous 30 secondes, il a donc été annulé. Rien n’a été envoyé ni débité. Renvoyez la demande à token.php pour un nouveau request_id. |
| unsupported | 422 | Cet appareil ne peut pas être servi. S’il figure sur notre liste des appareils que nous ne servons pas, token.php le refuse : pas de request_id, rien de débité. Si le serveur choisi dit qu’il ne prend pas l’appareil en charge, la tâche est débitée et n’est pas remboursée — demandez-nous quels builds et pays sont pris en charge avant d’envoyer (voir Appareils pris en charge). Ne réessayez pas sur le même serveur. |
| account_restricted | 403 | Votre compte est restreint pour quelques minutes : un appareil a été refusé comme non pris en charge plus de 3 fois en 5 minutes et renvoyé après l’avertissement. Jusque-là, chaque nouvelle requête est refusée ; status.php répond toujours pour les travaux déjà lancés. La réponse porte reason (unsupported_device_repeated), retry_after (secondes), restricted_until et note, une phrase pour votre panneau. Contactez-nous si c’était une erreur. |
| device_reboot | 409 | Le serveur indique que ce jeton d’appareil a déjà été utilisé. Rien n’a été facturé. Redémarrez le téléphone, remettez-le en mode sideload pour qu’il donne un nouveau jeton, puis envoyez une nouvelle requête. |
| server_unavailable | 503 | Le serveur n’a pas répondu à temps, ou — si vous n’en avez indiqué aucun — aucun serveur n’était disponible. Rien n’a été débité. Renvoyez la même demande. |
| failed | 400 | Le serveur choisi a exécuté la tâche mais n’a donné aucun token pour cet appareil — un échec tardif. Rien n’a été débité : la réservation est intégralement remboursée. Vérifiez la lecture du téléphone, ou essayez un autre serveur. |
| bad_before | 400 | before dans history.php n’est pas un request_id. |
| bad_date | 400 | from ou to dans history.php n’est pas un jour comme 2026-10-08, ou from est après to. |
17Sécurité
Votre mot de passe et votre clé figurent dans le corps de chaque requête. En HTTP simple, n’importe qui sur le trajet peut les lire et les réutiliser.
Il s’agit de votre token de compte — la valeur rst_… de l’en-tête Authorization — et non des tokens de déverrouillage. Nous n’en gardons qu’un hash, nous ne pouvons donc pas vous l’envoyer par e-mail plus tard. Rangez-le avec vos autres secrets. S’il fuit, générez-en un nouveau dans votre panneau — l’ancien cesse aussitôt de fonctionner. Les tokens de déverrouillage de vos tâches sont autre chose : chacun est conservé avec sa tâche, et status.php vous le redonne par request_id.
Appelez-nous depuis votre propre serveur, pas depuis un outil que vous remettez aux utilisateurs finaux. Tout ce que vous distribuez peut être lu.
Dans votre panneau (Compte → IP autorisées), vous pouvez restreindre l’API aux adresses depuis lesquelles votre serveur appelle, de sorte qu’un identifiant volé soit inutilisable ailleurs. Une requête provenant de toute autre adresse est refusée avec ip_not_allowed. Nous pouvons aussi la configurer pour vous.
Il est retiré avant que la requête ne soit stockée avec la tâche ; il n’apparaît donc ni dans notre base de données, ni dans notre journal des tâches, ni dans nos écrans d’administration.
Seul votre compte peut lire un request_id ; tout autre compte reçoit not_found. Une tâche ne démarre que lorsque vous confirmez son request_id sous 30 secondes : une réponse perdue en route ne laisse donc jamais tourner une tâche payante dont vous ignorez tout.
Votre quota horaire couvre token.php, history.php, balance.php et servers.php ; status.php accepte à lui seul 120 appels par minute ; balance.php répond une fois toutes les 10 secondes ; et après plus de 40 échecs d’authentification depuis une même adresse en 15 minutes, cette adresse est refusée un moment avec too_many_failures.
18Exemples de code
Un programme complet pour chaque tâche, en quatre langages — envoyer une demande, la suivre par son request_id, votre historique, votre crédit, les serveurs. Mettez vos propres identifiants et lancez-le ; choisir un langage l’affiche dans tous les blocs.
1Envoyer une demande de token
Envoie la lecture du téléphone à token.php et affiche le request_id. Rien ne tourne encore : donnez ce request_id au programme suivant sous 30 secondes.
#!/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<?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";"""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}")// 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}");2Suivre une demande et obtenir le token
Prend le request_id du premier programme. Son premier appel à status.php lance la tâche ; ensuite il interroge toutes les 3 secondes jusqu’à l’arrivée du token — ou de la raison de son absence.
#!/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).<?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";
}"""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"))// 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"]}");
}3L’historique de vos tâches
Liste vos tâches entre deux jours, pour un service : combien vous en avez envoyé, comment chacune s’est terminée, ce qu’elles ont coûté.
#!/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<?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'] ?? '');
}"""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 ''}")// 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"]}");4Votre crédit
Affiche votre crédit, ce que vous avez dépensé en FRP et en FDL, et votre prix sur chaque serveur.
#!/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<?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']);
}
}"""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']}")// 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"]}");5Les serveurs
Liste les serveurs que vous pouvez utiliser, votre prix sur chacun, et s’il accepte des tâches maintenant.
#!/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<?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']);
}
}"""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']}")// 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"]}");
}Quelque chose ne fonctionne pas ?
Envoyez-nous le request_id et le code error. Avec ces deux éléments, nous voyons exactement ce qui est arrivé à cette tâche ; sans eux, nous en sommes réduits aux suppositions.