01Cómo funciona
Usted lee el teléfono con su propia herramienta y envía lo que leyó. Recibe un request_id al instante. Lo devuelve a status.php, y eso inicia el trabajo en el servidor que eligió; esa misma llamada le dice después cómo va y le entrega el token de desbloqueo para escribirlo en el teléfono. Nosotros nunca tocamos el teléfono y usted nunca trata con el servidor.
token.php lo comprueba todo y responde al instante con un request_id, como WT-API-6AF684545B. Todavía no se envía nada a ningún servidor ni se cobra nada.
Envíe el request_id a status.php. Esa primera llamada es su recibo: solo entonces empieza el trabajo. Un request_id que nadie confirma en 30 segundos se cancela.
Pregunte de nuevo a status.php cada pocos segundos hasta que state sea done — con el token — o error, con el motivo y su crédito devuelto.
Los endpoints
| POST token.php | Enviar una solicitud de token: el request_id vuelve al instante. |
| POST status.php | Confirmar un request_id (esto inicia el trabajo) y seguirlo hasta el token. |
| POST history.php | Sus trabajos, por fecha y servicio, y cómo terminó cada uno. |
| POST balance.php | Su crédito y en qué se gastó. |
| POST servers.php | Los servidores que puede usar y su precio en cada uno. |
https://xiaomi.source.af/api/reseller/v1/…
02Credenciales
Cuatro datos le identifican, y los cuatro deben corresponder a la misma cuenta. El token viaja en el encabezado; los otros tres, en el cuerpo. Si cualquiera de ellos es incorrecto, se obtiene el mismo unauthorized — nunca decimos cuál, porque eso le indicaría a un atacante qué conservar.
| Campo | Qué es |
|---|---|
| Authorization | Token de cuenta — Solo suyo. Genérelo en su panel, en «Sus credenciales», o pídanoslo. Solo guardamos un hash, así que se muestra una sola vez y no se puede recuperar — al generar uno nuevo, el anterior deja de funcionar al instante. |
| api_key | Clave API — En el cuerpo, en cada solicitud. |
| Correo electrónico — La dirección de su cuenta. Si cambia, avísenos, o las llamadas dejarán de funcionar. | |
| password | Contraseña de la API — La contraseña de la API de su cuenta. De forma predeterminada, es su contraseña de inicio de sesión. Si quiere una aparte, establézcala en su panel, en Cuenta → Contraseña de la API; Usar mi contraseña de inicio de sesión lo revierte, y la contraseña aparte deja de funcionar al instante. También podemos establecerla nosotros por usted. En cualquier caso, nunca se escribe en nuestros registros ni se guarda con el trabajo. |
Todos los endpoints son POST sobre HTTPS. Un GET se rechaza con method_not_allowed; un cuerpo que no sea JSON se rechaza con bad_json.
03La solicitud
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"
}Todos los campos siguientes van en el cuerpo JSON.
| Campo | Tipo | Obligatorio | Qué es |
|---|---|---|---|
| api_key | string | sí | Su clave API. |
| string | sí | El correo electrónico de su cuenta. | |
| password | string | sí | Su contraseña de la API — de forma predeterminada, la misma que su contraseña de inicio de sesión, salvo que haya establecido una aparte en su panel, en Cuenta → Contraseña de la API. Consulte Credenciales. |
| soc | string | sí | Qué servicio. frp = eliminación de FRP, fdl = Fastboot a EDL. No hay valor predeterminado — omitirlo es un error, en lugar de una suposición que podría cobrarle por el servicio equivocado. |
| configblob | string | sí | La lectura completa del teléfono, como base64 de un objeto JSON. Lo que debe contener depende de soc — consulte El blob del dispositivo. |
| serverid | string | no | El id público del servidor que debe ejecutar el trabajo, p. ej. server1 — de servers.php, o de su panel en Servidores. Estricto: ese servidor lo ejecuta, o la llamada se rechaza indicando el motivo. Si lo omite, elegimos el servidor más barato que esté aceptando trabajos. Propietarios de herramientas: envíenlo siempre. Vea Elegir un servidor. |
| request_date | string | no | Opcional. Cuándo envió su servidor esta solicitud, en ISO-8601 con Z o un desfase horario. Se devuelve intacto como client_sent y nunca se usa en nuestros cálculos. Si lo omite, no cambia nada, salvo que client_sent y client_clock_offset_seconds se devuelven como null. |
| request_language | string | no | 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. |
04El blob del dispositivo
El blob es el base64 de un objeto JSON que contiene la lectura completa del teléfono — todo lo que su herramienta lee en ese modo, no solo el token. Lo que es obligatorio depende de soc: para frp, el teléfono está en modo Mi Assistant (sideload / recovery); para fdl, en modo Fastboot. Envíe cada valor exactamente como lo devolvió el teléfono — sin acortarlo ni cambiarle el formato.
frp — el teléfono en modo Mi Assistant (sideload / recovery)
| Campo | Se lee con | Obligatorio | Qué es | Ejemplo |
|---|---|---|---|---|
| token | getmitoken: | sí — null se rechaza | El desafío FRP, exactamente como se leyó | 8f21c0b4e9d7… |
| deviceName | getdevice: | sí — null se rechaza | El nombre en clave con su sufijo de región | fleur_global |
| version | getversion: | sí — null se rechaza | La compilación de la ROM | OS2.0.207.0.VMTMIXM |
| serial | getsn: | sí — null se rechaza | El número de serie | fb4d921c |
| codebase | getcodebase: | sí — null si no se lee | La versión de Android | 15 |
| region | getregion: | sí — null si no se lee | La región de la ROM | IR |
| cpuid | getcpuid: | sí — null si no se lee | El CPU ID, si el teléfono lo devuelve | 0x000460E1 |
| recovery | getrecoveryversion: | sí — null si no se lee | La versión de recovery — solo la usa nuestra lista de dispositivos no admitidos | 2 |
| branch | getbranch: | no — null está bien | La rama de la ROM | F |
| language | getlanguage: | no — null está bien | El idioma del dispositivo — solo lo usa nuestra lista de dispositivos no admitidos | fa |
| romzone | getromzone: | no — null está bien | La ROM zone — solo la usa nuestra lista de dispositivos no admitidos | 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"
}Para frp, no envíe productName. Se ignora: deducimos el producto de deviceName (fleur_global → fleur), exactamente como lo hace nuestra propia herramienta. La compilación de la ROM va en version. Una versión anterior de esta página indicaba que la compilación de la ROM se pusiera en productName — si su cliente lo hace, muévala a version.
fdl — el teléfono en modo Fastboot
| Campo | Se lee con | Obligatorio | Qué es | Ejemplo |
|---|---|---|---|---|
| token | oem get_token (getvar:token) | sí — null se rechaza | El token de fastboot, exactamente como se leyó | VQHzN2Ib… |
| product | getvar:product | sí — null se rechaza | El nombre en clave, sin región | fleur |
| serial | getvar:serialno | sí — null se rechaza | El número de serie | fb4d921c |
| cpuid | getvar:cpuid | sí — null si no se lee | El CPU ID, si el teléfono lo devuelve | 0x000460E1 |
{
"token": "VQHzN2Ib…",
"product": "fleur",
"serial": "fb4d921c",
"cpuid": "0x000460E1"
}Cualquier campo puede enviarse como null (null de JSON o el texto "null") cuando el teléfono no pudo leerlo — nunca unknown, N/A ni un texto vacío. Lo que ocurre después depende del campo: si está marcado sí — null se rechaza, los servidores construyen el trabajo con él, así que la solicitud se rechaza con incomplete_configblob, el campo se indica en nulls y no se cobra nada — lea el teléfono de nuevo y envíe la información completa; si está marcado sí — null si no se lee, debe estar siempre en el blob, y un null ejecuta el trabajo como siempre (si se omite por completo, se rechaza); si está marcado no — null está bien, envíelo como null u omítalo — el trabajo se ejecuta. Un trabajo enviado con campos null se ejecuta con lo que usted envió: si luego su token no funciona en el teléfono porque un campo no se leyó, no se puede reembolsar. Envíe todos los campos que el teléfono devuelva.
Algunos servidores necesitan cpuid para comprobar la región del dispositivo. Envíelo siempre que el teléfono lo dé (en modo Mi Assistant el número de serie 0x… es el CPU ID). Si se omite, se toma como null y el trabajo continúa.
{
"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"
}Otros nombres que aceptamos para los mismos campos: authKey o auth_key para token; devicename o device_name para deviceName; osVersion u os_version para version; serial_number o serialNumber para serial (para fdl, también serialno); productName o productname para product; cpu_id para cpuid.
Se comprueba antes de cobrar nada
Todo se comprueba antes de reservar o cobrar nada. Un campo obligatorio (sí — null se rechaza) que se omite se indica en missing; uno enviado como null, en nulls; uno que contiene una lectura fallida — N/A, unknown, none, un texto de error —, en invalid. Cada uno se rechaza con HTTP 400 incomplete_configblob, junto con el modo en que debe estar el teléfono en mode: Mi Assistant o Fastboot. Envíe los campos opcionales siempre que el teléfono los devuelva; uno que falte, sea null o contenga una lectura fallida simplemente se omite y nunca se transmite — no detiene el trabajo. Un blob cuyo token falte o sea null recibe como respuesta 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.Nosotros mismos construimos, a partir de estos campos, el formato propio de cada servidor de origen, así que usted nunca envía una forma que pertenezca a un servidor concreto — y un serverid distinto nunca cambia lo que envía.
Lo que envía y lo que recibe:
| Usted envía | Devolvemos | Significado |
|---|---|---|
| deviceName | device_name | El nombre en clave con su sufijo de región (frp) |
| — | product_model | El nombre en clave, sin región — lo deducimos de deviceName (frp) |
| product | product_model device_name | El nombre en clave, sin región (fdl) |
| version | device_version | La compilación de la ROM (frp) |
| serial | serial_number | El número de serie |
| codebase | codebase | La versión de Android (frp) |
| branch | branch | La rama de la ROM (frp) |
| region | region | La región de la ROM (frp) |
| cpuid | cpu_id | El CPU ID, si el teléfono lo devuelve |
| token | — | No se repite en device |
Un campo que no se aplica al servicio se devuelve como null en lugar de omitirse, así que un solo parser lee ambos servicios.
05Dispositivos compatibles
Algunos números de compilación no son compatibles, y puede que algunos países tampoco lo sean. Para saber cuáles, pregunte al administrador por Telegram o WhatsApp cuando tenga su API de propietario de herramienta.
Un dispositivo de nuestra propia lista de dispositivos no admitidos es rechazado por token.php al instante con unsupported (422): sin request_id, nada enviado a ningún servidor, nada cobrado. La lista puede comprobar cualquier campo del configblob — modelo, firmware, versión de Android, rama, idioma, región, versión de recovery, ROM zone, serie y CPU id —, así que envíe todo lo que leyó su herramienta.
Atención: enviar un modelo no compatible le cuesta su crédito — no se reembolsa — y a veces puede hacer que su cuenta sea bloqueada.
El mismo dispositivo no compatible una y otra vez: cada rechazo de un dispositivo como no compatible (unsupported) se cuenta para su cuenta y ese dispositivo durante 5 minutos. El 4.º rechazo lleva una advertencia en su message y strike.warning en true (strike = {count, max, window_seconds, warning} va en cada rechazo de este tipo): volver a enviar este dispositivo bloqueará su cuenta. Un rechazo más dentro de esos 5 minutos restringe su cuenta durante 5 minutos: cada solicitud nueva se responde con account_restricted (403). Deténgase en la advertencia y ofrezca a su cliente un servidor que acepte el dispositivo.
Cuando un dispositivo se rechaza por su región y uno de sus servidores es premium para esa región, el rechazo unsupported lleva region, suggested, premium (como en check.php) y takes_on, y su message nombra ese servidor: “La región de su dispositivo es CN. Este dispositivo debe hacerse con Server 6.3 — un servidor premium: su tasa de éxito para quitar FRP en este dispositivo es muy alta.” No se cobró nada.
Cuando los servidores premium de la región están todos apagados o sin plazas libres, no se sugiere nada (suggested es null), slots_full es true y message lo dice: “★ Server 6.3 admite la región AE de su dispositivo, pero sus plazas están llenas por ahora. Vuelva a mirar un poco más tarde, o siga en nuestro canal de Telegram …” Muéstrelo y deje que su cliente lo intente más tarde, en lugar de enviar el dispositivo a un servidor que no acepta su región. Un servidor premium siempre se nombra con su estrella (★).
Cuando el servidor que eligió rechaza un dispositivo por sí mismo, la respuesta unsupported de status.php lleva server_message: la frase del propio servidor, sin ningún nombre de proveedor, p. ej. “This device is not supported (China Device Not Supported) · CPUID: 0xe4f01f4c · Product: star · IMEI: 867869053687315”; el message también termina con ella. data.amount_charged indica si el trabajo se cobró: un servidor que rechaza un dispositivo antes de ejecutar el trabajo no cobra nada, y entonces nosotros tampoco.
Pregunte al administrador: Telegram: @RaminGSM WhatsApp: +93799691689
06Elegir un servidor
Cada servidor tiene un ID público que le asignamos nosotros — server1, server2… — y un nombre, como Server 1. Ambos aparecen en servers.php, como serverid y name, y en su panel, en Servidores. Para ejecutar un trabajo en uno de ellos, envíe su ID como 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", … } }Si omite serverid, elegimos por usted: el servidor más barato que esté aceptando trabajos y que su cuenta pueda usar. Si indica uno, es estricto: ese servidor ejecuta el trabajo o la llamada se rechaza — unknown_serverid (404) si ningún servidor tiene ese ID, server_not_allowed (403) si no está habilitado para su cuenta, server_offline (503) si en este momento no puede aceptar este servicio. Nunca le cambiamos a otro servidor sin que usted lo sepa.
Algunos servidores constan de varias máquinas que se pueden elegir una por una. servers.php lista cada máquina como un servidor propio, con su propio precio — por ejemplo serverid server6-2, name Server 6.2. Envíe ese serverid y el trabajo se ejecuta exactamente en esa máquina, a su precio, o se rechaza con el motivo. Envíe el id del propio servidor (server6) y usamos la más barata de sus máquinas que esté aceptando trabajos.
Comprobamos el servidor antes de reservar o cobrar nada. Si no puede aceptar el trabajo, recibe server_offline y no se gasta nada — elija otro de servers.php y vuelva a enviar, u omita serverid y deje que elijamos nosotros.
{ "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.Propietarios de herramientas: envíen siempre serverid. Lean la lista con servers.php (o en Servidores de su panel), dejen que su cliente elija y envíen ese id. El token llega entonces de ese servidor, o la respuesta dice exactamente por qué no — nunca se obtiene de otro servidor.
Cada respuesta nombra el servidor que ejecuta el trabajo, como serverid y server_name — también cuando deja que elijamos —, así se distingue un servidor lento de un dispositivo lento.
07Antes de recibir un request_id
token.php solo da un request_id cuando todas las comprobaciones de abajo se cumplen, en este orden. La primera que falla es su respuesta — con su código error — y no hay request_id: no se creó, retuvo ni cobró nada.
| # | Qué comprobamos | Si falla |
|---|---|---|
| 1 | La solicitud es un POST con un cuerpo JSON de 256 KB como máximo. | method_not_allowed 405bad_json 400body_too_large 413 |
| 2 | Sus cuatro credenciales corresponden a la misma cuenta activa: el token de cuenta, api_key, email y password. | unauthorized 401too_many_failures 429 |
| 3 | La solicitud llega desde una dirección de la lista permitida de su cuenta, si ha definido una. | ip_not_allowed 403 |
| 4 | Está dentro de su límite de solicitudes por hora. | rate_limited 429 |
| La cuenta no está restringida por enviar un dispositivo no compatible una y otra vez (vea Dispositivos compatibles). | account_restricted 403 | |
| 5 | soc es frp o fdl, ese servicio está vendido a su cuenta y está activado. | missing_soc 400bad_service 400not_entitled 403disabled 503 |
| 6 | configblob es válido y lleva la lectura completa que necesita el modo del teléfono. | missing_configblob 400bad_configblob 400missing_token 400incomplete_configblob 400 |
| 7 | El dispositivo no está en nuestra lista de dispositivos que no atendemos. | unsupported 422 |
| 8 | El servidor que indicó en serverid existe, está habilitado para su cuenta y está aceptando trabajos ahora: activado, con precio, en línea y listo por nuestra parte. | unknown_serverid 404server_not_allowed 403server_offline 503 |
| No indicó ningún servidor: tomamos el servidor más barato que esté aceptando trabajos y que su cuenta pueda usar, y la respuesta lo nombra. | service_not_allowed 403service_offline 503 | |
Una comprobación previa de información del dispositivo en el servidor elegido: se lee la región propia del dispositivo y se compara con nuestra lista; una región que el servidor elegido no admite se rechaza con unsupported (la respuesta lleva region del dispositivo, takes_on, suggested y premium). También se lee el estado de flash, pero no decide nada. Esto ocurre antes de dar un request_id, así que no se cobra nada. Si la comprobación previa no responde, se omite y la lista de dispositivos de arriba sigue aplicándose. | unsupported 422 | |
| 9 | El mismo dispositivo — el mismo token de dispositivo, para el mismo servicio y el mismo servidor — no se envió en los últimos 30 minutos. Si se envió y ese trabajo ya empezó, recibe ese mismo trabajo: su request_id y su estado, o su token — nunca un segundo trabajo ni un segundo cobro. Un trabajo que falló, o cuyo request_id nunca se confirmó, se sustituye por uno nuevo. | — |
| 10 | Su crédito cubre el precio de un trabajo en ese servidor — el price que servers.php muestra para él. Se comprueba aquí; el precio solo se retiene cuando empieza el trabajo. | no_credit 402 |
Todas las comprobaciones se cumplieron: recibe el request_id al instante, con state waiting. No se ha enviado nada a ningún servidor y no se retiene nada hasta que lo confirme con status.php. | ||
08Su request_id
La respuesta llega al instante: HTTP 202 con state waiting. El request_id es el WorkID del trabajo — ve el mismo WT-API-6AF684545B en su panel y puede buscarlo allí. Guárdelo: confirmar el trabajo, seguirlo, recoger el token y consultarnos sobre él se hace con este request_id. Un request_id dado antes del 2026-10-17, como Api123, sigue funcionando en todas partes y se responde en esa forma.
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.Confírmelo en 30 segundos. Envíelo a status.php: esa llamada es su recibo e inicia el trabajo. Un request_id que nadie confirma en 30 segundos se cancela — no se envió nada a ningún servidor ni se cobró nada — y una llamada posterior con él responde request_expired: envíe la solicitud de nuevo para obtener un request_id nuevo. Así, si una respuesta nunca le llegó, no hay nada en marcha que usted no pueda seguir.
| Campo | Qué es |
|---|---|
| request_id | Nuestro id para este trabajo — su WorkID, como WT-API-6AF684545B. Con él confirma, sigue y recoge el trabajo, y es lo que nos debe indicar en cualquier consulta sobre él. |
| state | waiting |
| confirm_within | Segundos que quedan para confirmarlo con status.php. |
| price | Lo que le costará el trabajo en ese servidor — se retiene al empezar y solo se cobra si llega el token. |
| serverid | El ID público del servidor que ejecutó el trabajo — el mismo serverid que usted puede enviar. |
| server_name | El nombre público de ese servidor, p. ej. Server 1. |
El mismo dispositivo — el mismo token de dispositivo, para el mismo servicio y el mismo servidor — no se envió en los últimos 30 minutos. Si se envió y ese trabajo ya empezó, recibe ese mismo trabajo: su request_id y su estado, o su token — nunca un segundo trabajo ni un segundo cobro. Un trabajo que falló, o cuyo request_id nunca se confirmó, se sustituye por uno nuevo.
09Confirmar y seguir
Envíe el request_id a status.php con las mismas cuatro credenciales. La primera llamada dentro de los 30 segundos inicia el trabajo y responde pending al instante; después, pregunte de nuevo cada retry_after segundos hasta que state deje de ser pending. Un trabajo suele tardar 15–25 segundos.
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.Lo que dice state
| Estado | HTTP | Qué significa y qué hacer |
|---|---|---|
pending | 202 | El trabajo está en el servidor. Pregunte de nuevo tras retry_after segundos. |
done | 200 | El token está en response_token: escríbalo en el teléfono. El trabajo se cobra; la respuesta indica cuánto costó y su crédito después. |
error | 4xx · 5xx | Sin token. error dice por qué — server_offline, unsupported, server_unavailable, failed o no_credit. El precio vuelve íntegro a su crédito, salvo cuando el servidor dice que no admite el dispositivo (unsupported): ese trabajo se cobra, como muestra amount_charged. |
expired | 410 | No se confirmó en 30 segundos: cancelado, nada enviado, nada cobrado. Envíe la solicitud de nuevo para un request_id nuevo. |
not_found | 404 | Ninguna solicitud con este request_id pertenece a su cuenta. |
El token se guarda con el trabajo, en nuestro servidor. Si una respuesta nunca le llegó, pregunte de nuevo a status.php con el mismo request_id: mientras el trabajo corre recibe pending; cuando termina, el token — tantas veces como pregunte, sin coste extra. Un trabajo que falló dice por qué.
# 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 tiene su propio límite — 120 llamadas por minuto por cuenta — y no cuenta para su cuota por hora, así que seguir un trabajo nunca consume sus solicitudes de token.
10La respuesta
Una sola forma para toda respuesta sobre un trabajo, venga de token.php, status.php o history.php. state es waiting, pending, done, error o expired; el estado HTTP dice lo mismo, así que puede bifurcar antes de analizar.
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
}
}
}Todos los campos de la respuesta:
| Campo | Qué es |
|---|---|
| request_id | Nuestro id para este trabajo — su WorkID, como WT-API-6AF684545B. Con él confirma, sigue y recoge el trabajo, y es lo que nos debe indicar en cualquier consulta sobre él. |
| soc | El servicio que se ejecutó. |
| serverid | El ID público del servidor que ejecutó el trabajo — el mismo serverid que usted puede enviar. |
| server_name | El nombre público de ese servidor, p. ej. Server 1. |
| configblob | El configblob que envió, byte a byte. |
| device | El dispositivo, desempaquetado. Siempre presente. |
| state | waiting, pending, done, error o expired. |
| response_token | El token de desbloqueo. Es lo que usted escribe en el teléfono. |
| provider_ref | La referencia de trabajo del propio servidor, para consultas de soporte. |
| amount_charged | Lo que le costó este trabajo. |
| credit | Su crédito después del cobro. |
| currency | La moneda de ambas cifras. |
Un fallo
# 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.Los tres bloques de tiempo
Tres bloques, y cuadran: start_date.elapsed_seconds más end_date.elapsed_seconds es igual a response_date.total_seconds, exactamente, porque los tres se calculan a partir de los mismos instantes.
En el ejemplo práctico de abajo, el trabajo tardó 5 segundos en obtenerse y 3 segundos en devolverse, así que 8 segundos en total.
| Campo | Qué es |
|---|---|
| start_date — la obtención del token | |
| client_sent | cuándo envió su servidor la solicitud — exactamente lo que usted envió |
| server_received | cuándo llegó la solicitud a nuestro servidor |
| upstream_sent | cuándo la enviamos al servidor de tokens |
| upstream_received | cuándo nos llegó la respuesta del servidor de tokens |
| elapsed_seconds | desde la llegada hasta tener el token — 5 segundos en el ejemplo |
| end_date — la devolución | |
| upstream_answered | cuándo nos respondió el servidor de tokens |
| server_answered | cuándo le respondió nuestro servidor |
| elapsed_seconds | solo el tramo de entrega — 3 segundos en el ejemplo |
| response_date — el total | |
| total_seconds | 5 + 3 = 8 segundos |
| client_clock_offset_seconds | la diferencia entre su reloj y el nuestro |
Los bloques de tiempo se miden desde que token.php recibió la solicitud. end_date termina cuando salió de nuestro lado la respuesta con el token, así que incluye lo que usted esperó entre sus llamadas a status.php.
Por qué no se usa su propia marca de tiempo en las sumas
Se devuelve en client_sent y es suyo, no nuestro — si el reloj de su servidor va un minuto adelantado, los cálculos basados en él darían una duración negativa. Todas las cifras de tiempo transcurrido proceden de nuestro reloj, y la diferencia entre ambos se informa por separado como client_clock_offset_seconds, para que pueda verla en lugar de que se incorpore en silencio a una cifra sobre la que usted factura.
11Lecturas de información del dispositivo (soc)
Además de frp y fdl, las cuentas de propietario de herramienta también pueden leer la información de un dispositivo. Estos servicios funcionan por el mismo flujo token.php y luego status.php, pero no usan ningún servidor y, en lugar de un token, devuelven la información del dispositivo como campos (fields). Son gratuitos por ahora (más adelante puede fijarse un precio).
Los valores de soc
| soc | Qué hace | Entrada | Respuesta en |
|---|---|---|---|
| frp | Desbloqueo FRP; devuelve un token de respuesta. | configblob — la lectura completa del dispositivo | Al instante |
| fdl | Autorización FDL; devuelve un token de respuesta. | configblob — la lectura completa del dispositivo | Al instante |
| read_sideload | Lee la información del dispositivo en modo Mi Assistant. | configblob — solo se necesita el token del dispositivo | Al instante |
| read_fastboot | Lee la información del dispositivo en modo fastboot. | configblob — solo se necesita el token del dispositivo | Al instante |
| read_imei | Busca el IMEI en la base de datos del fabricante. | imei — el IMEI de 15 dígitos | De 1 a 5 minutos; sigue consultando |
| read_lockcode | Devuelve el registro del dispositivo detrás de un código de bloqueo. | imei — el IMEI para el pedido | Unos minutos; sigue consultando |
Estas lecturas son para cuentas de propietario de herramienta. read_imei solo aparece mientras está abierto a la API; si no, está disponible dentro de la herramienta XST.
Enviar una lectura
Una lectura se envía y se comprueba igual que frp y fdl: token.php te da un request_id, tu primera llamada a status.php la inicia y consultas hasta que termina. No usa ningún servidor ni cobra precio de servidor.
# 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.Lo que devuelve una lectura terminada
En lugar de un response_token, una lectura responde con un objeto fields — la información del dispositivo como pares etiqueta/valor. Las etiquetas dependen del servicio; un campo que la fuente dejó vacío se omite.
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).
12Su historial de trabajos
Sus trabajos, del más reciente al más antiguo: cuándo se envió cada uno, a qué servidor y cómo terminó — por fecha y por servicio. Las mismas cuatro credenciales; no consume crédito y cuenta para su cuota por hora.
| Campo | Tipo | Obligatorio | Qué es |
|---|---|---|---|
| from | string | no | Un día, 2026-10-08 (o 2026-Oct-08), en UTC: los trabajos enviados ese día o después. |
| to | string | no | Un día: los trabajos enviados ese día o antes, incluido el día entero. |
| soc | string | no | frp o fdl: solo los trabajos de ese servicio. |
| limit | number | no | Cuántos trabajos listar, 1–100. Por defecto 1. |
| before | string | no | Un request_id: los trabajos anteriores a él. Envíe el next_before de una respuesta para obtener la página siguiente. |
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 y counts cubren todo el rango, no solo esta página: cuántas solicitudes envió y cuántas terminaron done, error, expired, pending o waiting; spent es lo que le costaron. Cada trabajo en jobs tiene su request_id — su WorkID: WT-API-6AF684545B para uno enviado por esta API, WT-WEB-… para uno hecho en nuestra propia herramienta, según indica door — con soc, serverid y server_name, state, error y message cuando no dio token, amount (lo que le costó), el device, y cuándo se envió y cambió por última vez. El token en sí no aparece: status.php lo da por request_id.
13Crédito y uso
Cuánto crédito queda y adónde fue el resto: las mismas cifras que las tarjetas de la parte superior de su panel. Usa las mismas cuatro credenciales que una solicitud de token, no consume crédito y responde una vez cada 10 segundos.
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 }
]
}
}
}En usage: bought es todo el crédito añadido a su cuenta, menos lo que el administrador retiró; spent es lo que costaron sus trabajos, menos reembolsos — dividido por servicio en spent_frp y spent_fdl, y por vía en spent_api (por esta API) y spent_xst (en nuestra propia herramienta); sent_to_users es lo que dio a sus propios usuarios si alguna vez fue revendedor; left es lo que queda, igual que credit. Cuadran: bought − sent_to_users − spent = left, y spent_frp + spent_fdl = spent. prices es lo que le cuesta un trabajo en cada servidor, por servicio: su serverid, name, si está online y el price, en su moneda.
Si se vuelve a consultar antes de 10 segundos, responde 429 too_soon con retry_after: espere esa cantidad de segundos. No se bloquea nada.
14Servidores y precios
Usa las mismas cuatro credenciales que una solicitud de token y no consume crédito. Cada llamada cuenta para su cuota por hora.
Lo que puede usar y lo que le cuesta cada trabajo.
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":[ … ] }
}
}
}Cada fila de providers es un servidor que puede usar — todos, también los que están apagados ahora (online false), primero los que aceptan trabajos y los más baratos. serverid y name son lo que envía y lo que cada respuesta devuelve, price es lo que le cuesta un trabajo en él, y online dice si ahora se acepta una solicitud dirigida a él. Elegir uno apagado es decisión suya: token.php lo rechaza con server_offline antes de cualquier request_id — use otro servidor o inténtelo más tarde. El price junto a la lista es lo que cuesta una solicitud sin serverid: el servidor más barato que acepta trabajos.
Cada fila dice también qué países acepta ese servidor, según la region del dispositivo: region_support — países que acepta aunque algunos de nuestros servidores no (o que indicamos como aceptados allí), region_not_support — países que no acepta. Ambas vacías: todos los países. Muéstrelas a su cliente como recordatorio, p. ej. “Server 6.3 — China: sí, India: no”. Solo se publica el país: nuestras demás condiciones (un modelo, una compilación, un CPU ID…) no se listan y quedan con nosotros. La decisión se toma siempre aquí, antes de enviar nada — token.php rechaza un dispositivo que un servidor no acepta con unsupported (422), y check.php lo responde para un dispositivo.
La respuesta de servers puede mostrarse con seguridad a sus propios clientes: incluye nuestros nombres públicos de servidores y su precio, nunca nuestro coste ni el proveedor de origen.
15Qué servidores aceptan un dispositivo
Una comprobación en seco: las mismas credenciales y el mismo cuerpo que token.php (soc, configblob), las mismas comprobaciones del configblob y los mismos errores — y después nada: ningún request_id, ningún trabajo, nada reservado ni cobrado, nada enviado a ningún servidor. Algunos servidores no aceptan algunos dispositivos (una compilación, un país…); esta llamada le dice, para este dispositivo, cuáles de sus servidores lo aceptan. Tiene su propio límite, 60 llamadas por minuto, y no cuenta para su cuota por hora.
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."
}
}Las filas bajo servers son las que lista servers.php, en su orden, también las apagadas. takes indica si ese servidor acepta este dispositivo; si no, reason dice por qué. Un servidor puede estar apagado (online false) y aun así aceptar el dispositivo: dígale a su cliente que lo intente más tarde. suggested es el primer servidor que acepta el dispositivo y está encendido (el más barato primero), o null; refused_everywhere es true cuando ninguno de sus servidores lo acepta. device es lo que leímos del configblob. Cuando token.php rechaza un dispositivo con unsupported (422), su respuesta lleva takes_on: los serverid que sí lo aceptan.
La respuesta también lee la región propia del dispositivo con la misma comprobación previa de información del dispositivo que ejecuta token.php — un teléfono puede informar una región que no es la suya. region es la región con la que se juzgaron los servidores: lo que leyó la comprobación previa cuando region_checked es true; si no, lo que decía el configblob. flash_status es el estado de flash que leyó (vacío si se desconoce). premium lista sus servidores que aceptan este dispositivo y son premium para su región (están hechos para dispositivos de ese país, así que su tasa de éxito con él es muy alta); entonces suggested es uno de ellos y message es una frase que puede mostrar a su cliente, p. ej. “La región de su dispositivo es CN. Este dispositivo debe hacerse con Server 6.3 — un servidor premium …” (vacío si no hay ninguno). Preseleccione ese servidor para su cliente, explique por qué y déjele cambiar de servidor, cancelar o continuar. La respuesta de la comprobación previa se guarda 15 minutos, así que el token.php siguiente no vuelve a preguntar.
Pregúntelo después de leer un dispositivo, para atenuar los servidores que lo rechazarían, o después de que token.php respondiera unsupported para el servidor que eligió su cliente, para ofrecer el que lo acepta. Una solicitud sin serverid se envía por sí sola al servidor más barato que acepta el dispositivo.
16Códigos de error
Todos los fallos tienen la misma forma: un código error sobre el que bifurcar la lógica y un message para las personas. Bifurque según el código, nunca según el texto del mensaje — el texto cambia, los códigos no. Los fallos de token.php que se producen después de las comprobaciones de inicio de sesión también incluyen los bloques de tiempos, así que un fallo lento es tan medible como un éxito lento. Algunos añaden campos propios: incomplete_configblob indica los campos en missing e invalid, junto con mode; rate_limited incluye limit y used. Sea cual sea el código, un trabajo que no entregó ningún token no cuesta nada — los rechazos se producen antes de reservar nada, y a un trabajo que falla después de enviarse se le reembolsa la reserva por completo.
| Código | HTTP | Qué significa y qué hacer |
|---|---|---|
| unauthorized | 401 | Una de las cuatro credenciales es incorrecta o la cuenta está desactivada. A propósito, no decimos cuál. |
| ip_not_allowed | 403 | Las cuatro credenciales son correctas, pero la solicitud llegó desde una dirección que no está en la lista de permitidas de su cuenta. La respuesta la indica en ip. Añádala en su panel (Cuenta → IP permitidas) o envíe desde una dirección permitida. No se ejecutó ni se cobró nada. |
| too_many_failures | 429 | Demasiados intentos fallidos desde su dirección. Espere y corrija la credencial antes de volver a intentarlo. |
| rate_limited | 429 | Superó un límite: su cuota por hora (token.php, history.php, balance.php, servers.php) o 120 consultas de estado por minuto. La respuesta trae limit, used y per. |
| too_soon | 429 | Se volvió a consultar balance.php antes de que pasaran 10 segundos. La respuesta indica cuántos segundos debe esperar (retry_after y el encabezado Retry-After); no se bloquea nada. |
| method_not_allowed | 405 | Usó GET. Todos los endpoints son POST. |
| bad_json | 400 | El cuerpo no era un objeto JSON. Establezca Content-Type: application/json. |
| body_too_large | 413 | El cuerpo supera los 256 KB. |
| missing_soc | 400 | soc no se envió. A propósito, no tiene valor predeterminado. |
| bad_service | 400 | soc debe ser exactamente frp o fdl. |
| not_entitled | 403 | Ese servicio no se vende a su cuenta. Pídanos que lo habilitemos. |
| disabled | 503 | Tenemos ese servicio desactivado en este momento. Es temporal. |
| missing_configblob | 400 | configblob no se envió. |
| bad_configblob | 400 | configblob no es un base64 válido de un objeto JSON. |
| missing_token | 400 | Falta el token del dispositivo dentro del blob. |
| incomplete_configblob | 400 | Un campo que los servidores necesitan se omitió (missing), se envió como null (nulls) o contiene una lectura fallida (invalid); mode es el modo en que debe estar el teléfono. Lea el teléfono de nuevo y envíe la información completa; no se cobró nada. |
| use_serverid | 400 | Envió services o server_id, la forma antigua de elegir un servidor por su posición. Ya no se acepta: envíe serverid (de servers.php), u omítalo y deje que elijamos. |
| use_status | 400 | Envió un request_id a token.php, que solo inicia solicitudes nuevas. Envíe el request_id a status.php. |
| unknown_serverid | 404 | Ningún servidor tiene ese serverid. Para obtener la lista, llame a servers.php o consulte Servidores en su panel. |
| server_not_allowed | 403 | El servidor que indicó no está habilitado para su cuenta. No le cambiamos a otro: elija uno de su lista u omita serverid. |
| service_not_allowed | 403 | No indicó ningún servidor y ninguno está habilitado para su cuenta. Pídanos que habilitemos uno. |
| server_offline | 503 | El servidor que indicó no puede aceptar este servicio en este momento: apagado, sin conexión o no listo por nuestra parte. token.php lo comprueba antes de dar un request_id y el trabajo lo comprueba de nuevo al empezar; si se apagó entretanto, o mientras corría el trabajo, la reserva se reembolsa. No se cobró nada. Use otro servidor de servers.php o vuelva a intentarlo más tarde. |
| service_offline | 503 | No indicó ningún servidor y ninguno está aceptando trabajos para este servicio en este momento. No se cobró nada. Vuelva a intentarlo en breve. |
| no_credit | 402 | Crédito insuficiente para un trabajo en ese servidor — rechazado por token.php antes de cualquier request_id, o, si su crédito bajó entretanto, al ir a empezar el trabajo. No se cobró nada. Recargue o elija un servidor más barato. |
| bad_imei | 400 | Faltaba imei o no era válido: read_imei necesita el IMEI de 15 dígitos; read_lockcode necesita el IMEI usado para el pedido. |
| read_failed | 502 | No se pudo completar una lectura (read_sideload, read_fastboot, read_imei o read_lockcode). No se cobró nada. Vuelve a enviarla o inténtalo más tarde. |
| missing_request_id | 400 | Se llamó a status.php sin request_id. |
| not_found | 404 | Ninguna solicitud con este request_id pertenece a su cuenta. |
| request_expired | 410 | El request_id no se confirmó con status.php en 30 segundos, así que se canceló. No se envió ni se cobró nada. Envíe la solicitud de nuevo a token.php para un request_id nuevo. |
| unsupported | 422 | Este dispositivo no se puede atender. Si está en nuestra lista de dispositivos que no atendemos, token.php lo rechaza: sin request_id y sin cobro. Si el servidor que eligió dice que no admite el dispositivo, el trabajo se cobra y no se reembolsa — pregúntenos qué compilaciones y países son compatibles antes de enviar (vea Dispositivos compatibles). No lo reintente en el mismo servidor. |
| account_restricted | 403 | Su cuenta está restringida durante unos minutos: un dispositivo fue rechazado como no compatible más de 3 veces en 5 minutos y se volvió a enviar tras la advertencia. Hasta entonces se rechaza cada solicitud nueva; status.php sigue respondiendo para los trabajos ya iniciados. La respuesta lleva reason (unsupported_device_repeated), retry_after (segundos), restricted_until y note, una frase para su panel. Contáctenos si fue un error. |
| device_reboot | 409 | El servidor indica que este token del dispositivo ya se usó. No se cobró nada. Reinicie el teléfono, vuelva a ponerlo en modo sideload para que dé un token nuevo y envíe una solicitud nueva. |
| server_unavailable | 503 | El servidor no respondió a tiempo o — si no indicó ninguno — no había ningún servidor disponible. No se cobró nada. Envíe de nuevo la misma solicitud. |
| failed | 400 | El servidor que eligió ejecutó el trabajo pero no dio ningún token para este dispositivo — un fallo tardío. No se cobró nada: la reserva se reembolsa por completo. Revise la lectura del teléfono o pruebe otro servidor. |
| bad_before | 400 | before en history.php no es un request_id. |
| bad_date | 400 | from o to en history.php no es un día como 2026-10-08, o from es posterior a to. |
17Seguridad
Su contraseña y su clave van en el cuerpo de cada solicitud. Con HTTP sin cifrar, cualquiera en el trayecto puede leerlas y reutilizarlas.
Este es su token de cuenta — el valor rst_… de la cabecera Authorization —, no los tokens de desbloqueo. Solo guardamos un hash, así que no podemos enviárselo por correo más tarde. Guárdelo donde guarda sus otros secretos. Si se filtra, genere uno nuevo en su panel — el anterior deja de funcionar al instante. Los tokens de desbloqueo de sus trabajos son otra cosa: cada uno se guarda con su trabajo, y status.php se lo vuelve a dar por request_id.
Llámenos desde su propio servidor, no desde una herramienta que entregue a usuarios finales. Todo lo que distribuya puede leerse.
En su panel (Cuenta → IP permitidas) puede restringir la API a las direcciones desde las que llama su servidor, de modo que una credencial robada no sirva de nada en ningún otro lugar. Una solicitud desde cualquier otra dirección se rechaza con ip_not_allowed. También podemos configurarlo nosotros por usted.
Se elimina antes de que la solicitud se guarde con el trabajo, así que no está en nuestra base de datos, ni en nuestro registro de trabajos, ni en nuestras pantallas de administración.
Solo su cuenta puede leer un request_id; cualquier otra cuenta recibe not_found. Un trabajo solo empieza cuando usted confirma su request_id en 30 segundos, así que una respuesta perdida por el camino nunca deja en marcha un trabajo de pago que usted desconoce.
Su cuota por hora cubre token.php, history.php, balance.php y servers.php; status.php admite por su cuenta 120 llamadas por minuto; balance.php responde una vez cada 10 segundos; y tras más de 40 inicios de sesión fallidos desde una dirección en 15 minutos, esa dirección se rechaza un tiempo con too_many_failures.
18Ejemplos de código
Un programa completo para cada tarea, en cuatro lenguajes — enviar una solicitud, consultarla por su request_id, su historial, su crédito, los servidores. Ponga sus propias credenciales y ejecútelo; al elegir un lenguaje se muestra en todos los bloques.
1Enviar una solicitud de token
Envía la lectura del teléfono a token.php e imprime el request_id. Aún no se ejecuta nada: pase ese request_id al siguiente programa en 30 segundos.
#!/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}");2Consultar una solicitud y obtener el token
Toma el request_id del primer programa. Su primera llamada a status.php inicia el trabajo; luego pregunta cada 3 segundos hasta que llega el token — o el motivo por el que no llegó.
#!/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"]}");
}3Su historial de trabajos
Lista sus trabajos entre dos días, para un servicio: cuántos envió, cómo terminó cada uno y cuánto costaron.
#!/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"]}");4Su crédito
Imprime su crédito, lo que gastó en FRP y FDL, y su precio en cada servidor.
#!/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"]}");5Los servidores
Lista los servidores que puede usar, su precio en cada uno y si cada uno acepta trabajos ahora.
#!/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"]}");
}¿Algo no funciona?
Envíenos el request_id y el código error. Con esos dos datos podemos ver exactamente qué le pasó a ese trabajo; sin ellos, solo podemos suponer.