API Pro: saldo, límites y personajes entrenados
Dos rutas de lectura, siempre permitidas sea cual sea el permiso de la clave, más el registro de actividad disponible en la app.
Saldo y límites
GET /credits
{ "balance": 1830, "licenseId": "lic_...", "key": "k_1a2b3c4d",
"limits": { "concurrent": 2, "perMinute": 60, "perDay": 0 }, "running": 1,
"buy_url": "https://tendre.ai/credits?license=lic_..." }| Campo | Significado |
|---|---|
balance | Créditos restantes. null cuando el saldo es desconocido (entorno de prueba). |
licenseId | La licencia a la que pertenece esta clave. |
key | El identificador público de la clave, seguro para registrar. Nunca el secreto. |
limits.concurrent | Renders ejecutándose a la vez, 2 por defecto. |
limits.perMinute | Envíos por minuto, 60 por defecto. |
limits.perDay | Límite diario de créditos que has establecido en la clave. 0 significa sin límite. |
running | Renders de esta clave que aún esperan ser recogidos. |
buy_url | La página de compra de créditos de tu licencia, con tarjeta o criptomoneda. |
Esta es la llamada más económica de la API y la indicada para comprobar que una clave funciona.
Comprar créditos con tu tarjeta guardada
POST /credits/purchase
{ "pack": "m" }pack es un id de pack (s, m, l, xl, xxl o xxxl), o "c25000" para una cantidad libre por encima del pack más grande. El pago se hace con la tarjeta guardada en la recarga automática de tu página de cuenta, solo si allí has autorizado a tu agente a comprar, dentro del límite mensual que has fijado (recargas automáticas incluidas). Un correo electrónico confirma cada compra. Una compra a la vez.
La respuesta es 200 { credits, eur, balance }. Cada rechazo incluye buy_url, el enlace de compra normal, para usarlo en su lugar: 402 confirmation_required (tu banco pide tu confirmación) o payment_declined; 409 agent_not_allowed, no_saved_card, monthly_cap_reached, topup_in_progress o purchase_in_progress; 400 unknown_pack. Con 503 payment_unavailable no sabemos si el cargo se ha realizado: no vuelvas a intentarlo enseguida, un cargo realizado se acredita.
Personajes entrenados
Son los personajes entrenados como LoRA, para imágenes y vídeos ordinarios. Los personajes de Multi-Ref Vídeo, siete vistas generadas a partir de una foto, son otra cosa: consulta Characters.
GET /loras
{ "image": [ { "name": "megan_krea2", "trigger": "megan", "base": "krea2", "size": 0 } ],
"video": [ { "name": "megan_ltx", "trigger": "megan", "size": 0 } ] }Pasa name como lora, tomándolo de la lista image para imágenes y de la lista video para
vídeos, y pon su trigger en el prompt, de lo contrario el personaje no aparecerá. Un
personaje que no pertenece a la cuenta devuelve 403 unknown_lora.
Sube tu propio LoRA
POST /loras/upload
Un archivo .safetensors que ya tengas, entrenado en otro lugar o alojado en tu propio servidor,
se incorpora al espacio de la cuenta y queda disponible mediante lora, exactamente igual que un
personaje entrenado en la app. Hay dos formas de hacerlo: de una vez hasta 90 MB, o en
partes a partir de ese tamaño. Un LoRA suele pesar entre 200 y 500 MB, así que la segunda opción
es el caso habitual. El límite máximo es de 800 MB en ambos casos.
De una vez: cuerpo en bruto (application/octet-stream, Content-Length requerido), parámetros
en la URL.
A partir de 100 MB la solicitud nunca llega a la API. La infraestructura que está por delante
la rechaza, por lo que lo que se recibe es un 413 simple sin cuerpo JSON: un cliente que
intente analizar cada error como JSON fallará en lugar de informarlo. Ese tamaño corresponde a
partes.
| Parámetro | Función |
|---|---|
name | Nombre del archivo, .safetensors, letras, dígitos, . _ -, 80 caracteres como máximo. Un nombre ya existente se reemplaza. |
kind | image (el valor por defecto) o video. Requiere el permiso images o videos según corresponda. |
trigger | La palabra de activación que hay que incluir en el prompt, 60 caracteres como máximo. |
curl -s -X POST "$B/loras/upload?name=my_character.safetensors&kind=image&trigger=ohwx" -H "Authorization: Bearer $KEY" -H "Content-Type: application/octet-stream" --data-binary @my_character.safetensorsLa respuesta es { "ok": true, "name": "my_character.safetensors", "kind": "image", "size": 314572800, "trigger": "ohwx", "architecture": "krea2", "storage": { ... } }.
La compatibilidad se comprueba al subir. Las imágenes se renderizan con Krea 2, que tiene su propia arquitectura: solo aplica un LoRA de Krea 2, tanto si fue entrenado en tendre.AI como para Krea 2 en otro sitio. Ten en cuenta que un LoRA de FLUX.1 es rechazado, lo que no es lo primero que uno esperaría. Los vídeos se renderizan con LTX 2.5, por lo que corresponde un LoRA de LTX 2.5 o 2.3.
La API lee la cabecera del archivo antes de almacenar nada. Un LoRA reconocido como SDXL, SD 1.5,
SD3, FLUX.1, Wan o HunyuanVideo es rechazado con 415 incompatible_lora, que indica la
architecture detectada, y un archivo que no sea safetensors recibe 415 invalid_safetensors.
En ninguno de los dos casos se almacena nada ni se consume cuota.
Un caso todavía pasa: una arquitectura que la cabecera no identifica es aceptada, con
architecture: "inconnue", y solo se juzga en el momento del render. Ese es el único error que
te cuesta un render descubrir.
Envía un LoRA grande en partes
Tres llamadas, más una para cancelar.
1. Abre la sesión. POST /loras/upload/start con { "name", "kind", "size", "trigger" },
donde size es el archivo completo en bytes. Comprueba el nombre, el tamaño y la cuota, y
responde:
{ "uploadId": "...", "part_size": 20971520, "max_part_size": 104857600, "parts_expected": 15 }2. Envía cada parte. POST /loras/upload/part?uploadId=...&part=N, cuerpo en bruto, partes
numeradas desde 1, part_size bytes cada una excepto la última, nunca más de max_part_size.
Cada respuesta incluye un etag, y los necesitas todos para el siguiente paso, así que
guárdalos a medida que avances.
La parte 1 es especial: lleva la cabecera del archivo, por lo que ahí es donde se evalúa la
arquitectura. Un LoRA incompatible se detecta allí con 415 y la sesión se cancela. Lo sabes
tras 20 MB en lugar de tras 400.
3. Ciérrala. POST /loras/upload/finish con { "uploadId", "parts": [ { "part": 1, "etag": "..." }, ... ] }.
Ensambla el archivo y lo registra, respondiendo exactamente igual que la subida de una vez.
Para cancelar, POST /loras/upload/abort con { "uploadId" }. Una sesión sin finish se
descarta tras seis horas. Nada aparece en GET /loras y nada cuenta contra la cuota hasta que
finish tiene éxito, así que una subida interrumpida no te cuesta nada de almacenamiento.
import os, requests
B, H = "https://api.tendre.ai/pro/v1", {"Authorization": "Bearer " + KEY}
path = name = "my_character.safetensors"
s = requests.post(f"{B}/loras/upload/start", headers=H,
json={"name": name, "kind": "image", "size": os.path.getsize(path), "trigger": "ohwx"}).json()
parts = []
with open(path, "rb") as f:
n = 1
while chunk := f.read(s["part_size"]):
r = requests.post(f"{B}/loras/upload/part",
headers={**H, "Content-Type": "application/octet-stream"},
params={"uploadId": s["uploadId"], "part": n}, data=chunk)
r.raise_for_status()
parts.append({"part": n, "etag": r.json()["etag"]})
n += 1
done = requests.post(f"{B}/loras/upload/finish", headers=H,
json={"uploadId": s["uploadId"], "parts": parts}).json()
print(done["name"], done["architecture"], done["storage"])Un script de PowerShell listo para usar viene incluido con la app, en scripts/api-pro-lora.ps1.
Elimina un LoRA
DELETE /loras/{name}?kind=image|video elimina el archivo y libera la cuota. Responde con
{ "ok": true, "name": ..., "storage": { ... } }, o 404 unknown_lora cuando el nombre no
existe en la cuenta.
Cuota de almacenamiento
Cada licencia dispone de 20 archivos y 10 GB como máximo, imágenes y vídeos juntos, y los
personajes entrenados en la app cuentan hacia ese límite. GET /loras informa del estado en
storage:
{ "count": 3, "max_count": 20, "bytes": 943718400, "max_bytes": 10737418240 }Si se supera cualquiera de los dos límites, una subida responde con 413 lora_quota junto con
lo que queda. Elimina un archivo para liberar espacio.
Entrenar un personaje todavía no está expuesto por la API. Ocurre en la app, y el resultado aparece en esta lista.
Actividad
La app guarda el registro de cada render que han realizado tus claves (fecha, clave, tipo, coste, estado) y el consumo por clave, en Ajustes > API > Activity.
También es donde revocas una clave que haya quedado expuesta, sin tocar las demás.