API de tokens

Envíe el dispositivo, reciba un request_id al instante y sígalo hasta el token de desbloqueo.

Español
Para empezar

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.

1Enviar

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.

2Confirme en 30 segundos

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.

3Recoja el token

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.phpEnviar una solicitud de token: el request_id vuelve al instante.
POST status.phpConfirmar un request_id (esto inicia el trabajo) y seguirlo hasta el token.
POST history.phpSus trabajos, por fecha y servicio, y cómo terminó cada uno.
POST balance.phpSu crédito y en qué se gastó.
POST servers.phpLos 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.

CampoQué es
AuthorizationToken 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_keyClave API — En el cuerpo, en cada solicitud.
emailCorreo electrónico — La dirección de su cuenta. Si cambia, avísenos, o las llamadas dejarán de funcionar.
passwordContraseñ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.

Parte 1 · Enviar una solicitud de token

03La solicitud

POSThttps://xiaomi.source.af/api/reseller/v1/token.php
la solicitud completa
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.

CampoTipoObligatorioQué es
api_keystringsíSu clave API.
emailstringsíEl correo electrónico de su cuenta.
passwordstringsí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.
socstringsí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.
configblobstringsíLa lectura completa del teléfono, como base64 de un objeto JSON. Lo que debe contener depende de soc — consulte El blob del dispositivo.
serveridstringnoEl 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_datestringnoOpcional. 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_languagestringnoOptional. The language for the customer-facing message of a refusal: one of English, Persian, Spanish, French, Turkish, Arabic (or a 2-letter code like en/fa). Omitted or unrecognised → the reply is in English. Send it on every call (token.php, status.php and the reads). The structured fields (such as flash_status, region, takes_on) come back regardless, so you can also build your own sentence in any language.

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)

CampoSe lee conObligatorioQué esEjemplo
tokengetmitoken:sí — null se rechazaEl desafío FRP, exactamente como se leyó8f21c0b4e9d7…
deviceNamegetdevice:sí — null se rechazaEl nombre en clave con su sufijo de regiónfleur_global
versiongetversion:sí — null se rechazaLa compilación de la ROMOS2.0.207.0.VMTMIXM
serialgetsn:sí — null se rechazaEl número de seriefb4d921c
codebasegetcodebase:sí — null si no se leeLa versión de Android15
regiongetregion:sí — null si no se leeLa región de la ROMIR
cpuidgetcpuid:sí — null si no se leeEl CPU ID, si el teléfono lo devuelve0x000460E1
recoverygetrecoveryversion:sí — null si no se leeLa versión de recovery — solo la usa nuestra lista de dispositivos no admitidos2
branchgetbranch:no — null está bienLa rama de la ROMF
languagegetlanguage:no — null está bienEl idioma del dispositivo — solo lo usa nuestra lista de dispositivos no admitidosfa
romzonegetromzone:no — null está bienLa ROM zone — solo la usa nuestra lista de dispositivos no admitidos2
frp · antes de base64
{
  "token":      "8f21c0b4e9d7…",
  "deviceName": "fleur_global",
  "version":    "OS2.0.207.0.VMTMIXM",
  "serial":     "fb4d921c",
  "codebase":   "15",
  "region":     "IR",
  "cpuid":      "0x000460E1",
  "recovery":   "2",
  "branch":     "F",
  "language":   "fa"
}

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

CampoSe lee conObligatorioQué esEjemplo
tokenoem get_token
(getvar:token)
sí — null se rechazaEl token de fastboot, exactamente como se leyóVQHzN2Ib…
productgetvar:productsí — null se rechazaEl nombre en clave, sin regiónfleur
serialgetvar:serialnosí — null se rechazaEl número de seriefb4d921c
cpuidgetvar:cpuidsí — null si no se leeEl CPU ID, si el teléfono lo devuelve0x000460E1
fdl · antes de base64
{
  "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.

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

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.

incomplete_configblob
HTTP/1.1 400 Bad Request

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

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

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íaDevolvemosSignificado
deviceNamedevice_nameEl nombre en clave con su sufijo de región (frp)
—product_modelEl nombre en clave, sin región — lo deducimos de deviceName (frp)
productproduct_model
device_name
El nombre en clave, sin región (fdl)
versiondevice_versionLa compilación de la ROM (frp)
serialserial_numberEl número de serie
codebasecodebaseLa versión de Android (frp)
branchbranchLa rama de la ROM (frp)
regionregionLa región de la ROM (frp)
cpuidcpu_idEl 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.

los dos casos
# 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.

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

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

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

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é comprobamosSi falla
1La solicitud es un POST con un cuerpo JSON de 256 KB como máximo.method_not_allowed 405
bad_json 400
body_too_large 413
2Sus cuatro credenciales corresponden a la misma cuenta activa: el token de cuenta, api_key, email y password.unauthorized 401
too_many_failures 429
3La solicitud llega desde una dirección de la lista permitida de su cuenta, si ha definido una.ip_not_allowed 403
4Está 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
5soc es frp o fdl, ese servicio está vendido a su cuenta y está activado.missing_soc 400
bad_service 400
not_entitled 403
disabled 503
6configblob es válido y lleva la lectura completa que necesita el modo del teléfono.missing_configblob 400
bad_configblob 400
missing_token 400
incomplete_configblob 400
7El dispositivo no está en nuestra lista de dispositivos que no atendemos.unsupported 422
8El 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 404
server_not_allowed 403
server_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 403
service_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
9El 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.—
10Su 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.

la respuesta de token.php
HTTP/1.1 202 Accepted

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

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

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.

CampoQué es
request_idNuestro 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.
statewaiting
confirm_withinSegundos que quedan para confirmarlo con status.php.
priceLo que le costará el trabajo en ese servidor — se retiene al empezar y solo se cobra si llega el token.
serveridEl ID público del servidor que ejecutó el trabajo — el mismo serverid que usted puede enviar.
server_nameEl 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.

Parte 2 · Consultar una solicitud

09Confirmar y seguir

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

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.

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

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

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

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

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

Lo que dice state

EstadoHTTPQué significa y qué hacer
pending202El trabajo está en el servidor. Pregunte de nuevo tras retry_after segundos.
done200El 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.
error4xx · 5xxSin 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.
expired410No se confirmó en 30 segundos: cancelado, nada enviado, nada cobrado. Envíe la solicitud de nuevo para un request_id nuevo.
not_found404Ninguna 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é.

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

status.php 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.

Token entregado
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:

CampoQué es
request_idNuestro 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.
socEl servicio que se ejecutó.
serveridEl ID público del servidor que ejecutó el trabajo — el mismo serverid que usted puede enviar.
server_nameEl nombre público de ese servidor, p. ej. Server 1.
configblobEl configblob que envió, byte a byte.
deviceEl dispositivo, desempaquetado. Siempre presente.
statewaiting, pending, done, error o expired.
response_tokenEl token de desbloqueo. Es lo que usted escribe en el teléfono.
provider_refLa referencia de trabajo del propio servidor, para consultas de soporte.
amount_chargedLo que le costó este trabajo.
creditSu crédito después del cobro.
currencyLa moneda de ambas cifras.

Un fallo

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

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

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.

CampoQué es
start_date — la obtención del token
client_sentcuándo envió su servidor la solicitud — exactamente lo que usted envió
server_receivedcuándo llegó la solicitud a nuestro servidor
upstream_sentcuándo la enviamos al servidor de tokens
upstream_receivedcuándo nos llegó la respuesta del servidor de tokens
elapsed_secondsdesde la llegada hasta tener el token — 5 segundos en el ejemplo
end_date — la devolución
upstream_answeredcuándo nos respondió el servidor de tokens
server_answeredcuándo le respondió nuestro servidor
elapsed_secondssolo el tramo de entrega — 3 segundos en el ejemplo
response_date — el total
total_seconds5 + 3 = 8 segundos
client_clock_offset_secondsla 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

socQué haceEntradaRespuesta en
frpDesbloqueo FRP; devuelve un token de respuesta.configblob — la lectura completa del dispositivoAl instante
fdlAutorización FDL; devuelve un token de respuesta.configblob — la lectura completa del dispositivoAl instante
read_sideloadLee la información del dispositivo en modo Mi Assistant.configblob — solo se necesita el token del dispositivoAl instante
read_fastbootLee la información del dispositivo en modo fastboot.configblob — solo se necesita el token del dispositivoAl instante
read_imeiBusca el IMEI en la base de datos del fabricante.imei — el IMEI de 15 dígitosDe 1 a 5 minutos; sigue consultando
read_lockcodeDevuelve el registro del dispositivo detrás de un código de bloqueo.imei — el IMEI para el pedidoUnos 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.

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

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

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

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

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

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

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.

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

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

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

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

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

Parte 3 · Su crédito y precios

12Su historial de trabajos

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

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.

CampoTipoObligatorioQué es
fromstringnoUn día, 2026-10-08 (o 2026-Oct-08), en UTC: los trabajos enviados ese día o después.
tostringnoUn día: los trabajos enviados ese día o antes, incluido el día entero.
socstringnofrp o fdl: solo los trabajos de ese servicio.
limitnumbernoCuántos trabajos listar, 1–100. Por defecto 1.
beforestringnoUn request_id: los trabajos anteriores a él. Envíe el next_before de una respuesta para obtener la página siguiente.
history.php
curl -X POST https://xiaomi.source.af/api/reseller/v1/history.php \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer rst_YOUR_ACCOUNT_TOKEN' \
  -d '{"api_key":"YOUR_API_KEY","email":"you@example.com","password":"YOUR_PASSWORD",
       "from":"2026-10-08","to":"2026-10-09","soc":"frp","limit":2}'

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

total 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

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

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.

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

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

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

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

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.

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

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

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

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

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.

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

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

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.

Parte 4 · Códigos de error

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ódigoHTTPQué significa y qué hacer
unauthorized401Una de las cuatro credenciales es incorrecta o la cuenta está desactivada. A propósito, no decimos cuál.
ip_not_allowed403Las 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_failures429Demasiados intentos fallidos desde su dirección. Espere y corrija la credencial antes de volver a intentarlo.
rate_limited429Superó 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_soon429Se 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_allowed405Usó GET. Todos los endpoints son POST.
bad_json400El cuerpo no era un objeto JSON. Establezca Content-Type: application/json.
body_too_large413El cuerpo supera los 256 KB.
missing_soc400soc no se envió. A propósito, no tiene valor predeterminado.
bad_service400soc debe ser exactamente frp o fdl.
not_entitled403Ese servicio no se vende a su cuenta. Pídanos que lo habilitemos.
disabled503Tenemos ese servicio desactivado en este momento. Es temporal.
missing_configblob400configblob no se envió.
bad_configblob400configblob no es un base64 válido de un objeto JSON.
missing_token400Falta el token del dispositivo dentro del blob.
incomplete_configblob400Un 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_serverid400Envió 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_status400Envió un request_id a token.php, que solo inicia solicitudes nuevas. Envíe el request_id a status.php.
unknown_serverid404Ningún servidor tiene ese serverid. Para obtener la lista, llame a servers.php o consulte Servidores en su panel.
server_not_allowed403El servidor que indicó no está habilitado para su cuenta. No le cambiamos a otro: elija uno de su lista u omita serverid.
service_not_allowed403No indicó ningún servidor y ninguno está habilitado para su cuenta. Pídanos que habilitemos uno.
server_offline503El 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_offline503No 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_credit402Cré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_imei400Faltaba 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_failed502No 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_id400Se llamó a status.php sin request_id.
not_found404Ninguna solicitud con este request_id pertenece a su cuenta.
request_expired410El 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.
unsupported422Este 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_restricted403Su 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_reboot409El 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_unavailable503El 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.
failed400El 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_before400before en history.php no es un request_id.
bad_date400from o to en history.php no es un día como 2026-10-08, o from es posterior a to.
Parte 5 · Seguridad

17Seguridad

Use HTTPS, siempre

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.

Su token de cuenta se muestra una sola vez

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.

No incluya las credenciales en lo que distribuye

Llámenos desde su propio servidor, no desde una herramienta que entregue a usuarios finales. Todo lo que distribuya puede leerse.

Restrinja la API a sus direcciones

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.

Nunca registramos su contraseña

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.

El request_id es su recibo

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.

Límites

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.

Parte 6 · Ejemplos de código

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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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ó.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4Su crédito

Imprime su crédito, lo que gastó en FRP y FDL, y su precio en cada servidor.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5Los servidores

Lista los servidores que puede usar, su precio en cada uno y si cada uno acepta trabajos ahora.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

¿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.

Xiaomi Source Tool · API de tokens · 2026-10-27