API Pro: saldo, limites e personagens treinados
Duas rotas de leitura, sempre permitidas independentemente das permissões da chave, mais o registo de atividade guardado na app.
Saldo e limites
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 quando o saldo é desconhecido (ambiente de teste). |
licenseId | A licença à qual esta chave pertence. |
key | O identificador público da chave, seguro para registar em logs. Nunca o segredo. |
limits.concurrent | Renders a correr ao mesmo tempo, 2 por defeito. |
limits.perMinute | Submissões por minuto, 60 por defeito. |
limits.perDay | Limite diário de créditos que definiste para a chave. 0 significa sem limite. |
running | Renders desta chave ainda à espera de serem recolhidos. |
buy_url | A página de compra de créditos da tua licença, por cartão ou cripto. |
Esta é a chamada mais barata da API e a ideal para verificar se uma chave funciona.
Comprar créditos com o teu cartão guardado
POST /credits/purchase
{ "pack": "m" }pack é um id de pack (s, m, l, xl, xxl ou xxxl), ou "c25000" para uma quantidade livre acima do maior pack. O pagamento é feito com o cartão guardado no carregamento automático da tua página de conta, apenas se aí autorizaste o teu agente a comprar, dentro do limite mensal que fixaste (carregamentos automáticos incluídos). Um e-mail confirma cada compra. Uma compra de cada vez.
A resposta é 200 { credits, eur, balance }. Cada recusa inclui buy_url, o link de compra normal, para usar em alternativa: 402 confirmation_required (o teu banco pede a tua confirmação) ou payment_declined; 409 agent_not_allowed, no_saved_card, monthly_cap_reached, topup_in_progress ou purchase_in_progress; 400 unknown_pack. Com 503 payment_unavailable não sabemos se o débito foi feito: não tentes de novo de imediato, um débito feito é creditado.
Personagens treinados
Estes são os personagens treinados como LoRA, para imagens e vídeos normais. Os personagens do Multi-Ref Vídeo, sete vistas feitas a partir de uma foto, são outra coisa: consulta Characters.
GET /loras
{ "image": [ { "name": "megan_krea2", "trigger": "megan", "base": "krea2", "size": 0 } ],
"video": [ { "name": "megan_ltx", "trigger": "megan", "size": 0 } ] }Passa name como lora, retirado da lista image para imagens e da lista video para
vídeos, e coloca o seu trigger no prompt, caso contrário o personagem não aparecerá. Um
personagem que não pertence à conta responde com 403 unknown_lora.
Carrega o teu próprio LoRA
POST /loras/upload
Um ficheiro .safetensors que já tens, treinado noutro lugar ou alojado no teu próprio servidor,
é adicionado ao espaço da conta e fica disponível através de lora, exatamente como um personagem
treinado na app. Há duas formas de o fazer: de uma vez até 90 MB, ou em partes acima disso.
Um LoRA pesa normalmente entre 200 e 500 MB, por isso a segunda opção é o caso habitual. O limite
máximo é 800 MB em ambos os casos.
De uma vez: corpo bruto (application/octet-stream, Content-Length obrigatório), parâmetros no
URL.
Acima de 100 MB o pedido nunca chega à API. A infraestrutura à frente rejeita-o, por isso o
que se recebe é um simples 413 sem corpo JSON: um cliente que tente analisar todos os erros
como JSON vai falhar em vez de reportar o problema. Esse tamanho deve ser enviado em partes.
| Parâmetro | Função |
|---|---|
name | Nome do ficheiro, .safetensors, letras, dígitos, . _ -, no máximo 80 caracteres. Um nome já existente é substituído. |
kind | image (valor por defeito) ou video. Requer a permissão images ou videos respetivamente. |
trigger | A palavra de ativação a colocar no prompt, no máximo 60 caracteres. |
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.safetensorsA resposta é { "ok": true, "name": "my_character.safetensors", "kind": "image", "size": 314572800, "trigger": "ohwx", "architecture": "krea2", "storage": { ... } }.
A compatibilidade é verificada no momento do carregamento. As imagens são geradas pelo Krea 2, que tem uma arquitetura própria: só um LoRA Krea 2 é aplicado, quer tenha sido treinado no tendre.AI ou para Krea 2 noutro lugar. Nota que um LoRA FLUX.1 é recusado, o que nem toda a gente adivinhou à primeira. Os vídeos são gerados pelo LTX 2.5, por isso um LoRA LTX 2.5 ou 2.3.
A API lê o cabeçalho do ficheiro antes de guardar qualquer coisa. Um LoRA reconhecido como SDXL,
SD 1.5, SD3, FLUX.1, Wan ou HunyuanVideo é recusado com 415 incompatible_lora, que indica a
architecture encontrada, e um ficheiro que não seja safetensors recebe 415 invalid_safetensors.
Nada é guardado e nenhuma quota é consumida em nenhum dos casos.
Há uma situação que ainda passa: uma arquitetura que o cabeçalho não identifica é aceite, com
architecture: "inconnue", e só é avaliada no momento da geração. Esse é o único erro que te custa
um render para descobrires.
Envia um LoRA grande em partes
Três chamadas, mais uma para desistir.
1. Abre a sessão. POST /loras/upload/start com { "name", "kind", "size", "trigger" },
onde size é o ficheiro completo em bytes. Verifica o nome, o tamanho e a quota, e responde:
{ "uploadId": "...", "part_size": 20971520, "max_part_size": 104857600, "parts_expected": 15 }2. Envia cada parte. POST /loras/upload/part?uploadId=...&part=N, corpo bruto, partes numeradas
a partir de 1, part_size bytes cada uma exceto a última, nunca mais de max_part_size. Cada resposta
inclui um etag, e precisas de todos eles para o passo seguinte, por isso guarda-os à medida que avança.
A parte 1 é especial: contém o cabeçalho do ficheiro, por isso é aí que a arquitetura é avaliada. Um
LoRA incompatível é detetado nesse ponto com 415 e a sessão é cancelada. Ficas a saber ao fim de
20 MB em vez de 400.
3. Fecha a sessão. POST /loras/upload/finish com { "uploadId", "parts": [ { "part": 1, "etag": "..." }, ... ] }.
Monta o ficheiro e regista-o, respondendo exatamente como o carregamento de uma só vez.
Para desistir, POST /loras/upload/abort com { "uploadId" }. Uma sessão deixada sem finish é
eliminada ao fim de seis horas. Nada aparece em GET /loras e nada conta para a quota até que
finish seja concluído com sucesso, por isso um carregamento interrompido não te custa armazenamento.
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"])Um script PowerShell já pronto é incluído com a app, em scripts/api-pro-lora.ps1.
Elimina um LoRA
DELETE /loras/{name}?kind=image|video remove o ficheiro e liberta a quota. Responde com
{ "ok": true, "name": ..., "storage": { ... } }, ou 404 unknown_lora quando o nome não
existe na conta.
Quota de armazenamento
Cada licença tem no máximo 20 ficheiros e 10 GB, imagens e vídeos juntos, e os personagens
treinados na app contam para esse total. GET /loras reporta o estado em storage:
{ "count": 3, "max_count": 20, "bytes": 943718400, "max_bytes": 10737418240 }Quando qualquer um dos limites é atingido, um carregamento responde com 413 lora_quota indicando
o que resta. Elimina um ficheiro para libertar espaço.
Treinar um personagem ainda não está exposto pela API. Acontece na app e o resultado aparece nesta lista.
Atividade
A app mantém o registo de cada render feito pelas tuas chaves (data, chave, tipo, custo, estado) e o consumo por chave, em Configurações > API > Atividade.
É também aí que revogás uma chave que tenha sido comprometida, sem afetar as outras.