Pro API: Guthaben, Limits und trainierte Charaktere
Zwei Leseendpunkte, die unabhängig von den Berechtigungen des Keys immer zugänglich sind, sowie das Aktivitätsprotokoll in der App.
Guthaben und Limits
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_..." }| Feld | Bedeutung |
|---|---|
balance | Verbleibendes Guthaben. null, wenn das Guthaben unbekannt ist (Testumgebung). |
licenseId | Die Lizenz, zu der dieser Key gehört. |
key | Der öffentliche Bezeichner des Keys, sicher zum Loggen. Niemals das Geheimnis selbst. |
limits.concurrent | Gleichzeitig laufende Renders, standardmäßig 2. |
limits.perMinute | Anfragen pro Minute, standardmäßig 60. |
limits.perDay | Tägliches Guthaben-Limit, das du für den Key festgelegt hast. 0 bedeutet kein Limit. |
running | Renders dieses Keys, die noch auf Abholung warten. |
buy_url | Die Seite zum Kauf von Credits für deine Lizenz, per Karte oder Krypto. |
Dies ist der günstigste Aufruf der API und der richtige, um zu prüfen, ob ein Key funktioniert.
Credits mit deiner gespeicherten Karte kaufen
POST /credits/purchase
{ "pack": "m" }pack ist eine Paket-ID (s, m, l, xl, xxl oder xxxl) oder "c25000" für eine freie Menge über dem größten Paket. Die Zahlung geht auf die Karte, die im automatischen Aufladen deiner Kontoseite gespeichert ist, nur wenn du dort deinem Agenten das Kaufen erlaubt hast, innerhalb des monatlichen Limits, das du dort festgelegt hast (automatische Aufladungen eingeschlossen). Jeder Kauf wird per E-Mail bestätigt. Immer nur ein Kauf gleichzeitig.
Die Antwort ist 200 { credits, eur, balance }. Jede Ablehnung enthält buy_url, den normalen Kauflink, den du stattdessen nutzt: 402 confirmation_required (deine Bank verlangt deine Bestätigung) oder payment_declined; 409 agent_not_allowed, no_saved_card, monthly_cap_reached, topup_in_progress oder purchase_in_progress; 400 unknown_pack. Bei 503 payment_unavailable wissen wir nicht, ob die Belastung erfolgt ist: nicht sofort erneut versuchen, eine erfolgte Belastung wird gutgeschrieben.
Trainierte Charaktere
Das sind die Charaktere, die als LoRA trainiert wurden, für Bilder und normale Videos. Die Charaktere von Multi-Ref Video, sieben Ansichten aus einem einzigen Foto, sind etwas anderes: siehe Characters.
GET /loras
{ "image": [ { "name": "megan_krea2", "trigger": "megan", "base": "krea2", "size": 0 } ],
"video": [ { "name": "megan_ltx", "trigger": "megan", "size": 0 } ] }Übergib name als lora, nimm ihn für Bilder aus der image-Liste und für Videos aus der video-Liste, und füge den trigger in den Prompt ein, sonst erscheint der Charakter nicht. Ein Charakter, der nicht zum Account gehört, antwortet mit 403 unknown_lora.
Eigene LoRA hochladen
POST /loras/upload
Eine .safetensors-Datei, die du bereits hast, anderswo trainiert oder auf deinem eigenen Server gespeichert, wird in den Speicherplatz des Accounts geladen und ist über lora nutzbar, genau wie ein in der App trainierter Charakter. Es gibt zwei Wege: auf einmal bis 90 MB, oder in Teilen darüber hinaus. Eine LoRA wiegt üblicherweise 200 bis 500 MB, also ist der zweite Weg der Normalfall. Die Obergrenze liegt in beiden Fällen bei 800 MB.
Auf einmal: ein roher Body (application/octet-stream, Content-Length erforderlich), Parameter in der URL.
Ab 100 MB erreicht die Anfrage die API nicht mehr. Die vorgelagerte Infrastruktur weist sie ab, sodass eine bloße 413 ohne JSON-Body zurückkommt: ein Client, der jeden Fehler als JSON parst, wird daran scheitern, statt ihn zu melden. Diese Größe gehört in Teile aufgeteilt.
| Parameter | Rolle |
|---|---|
name | Dateiname, .safetensors, Buchstaben, Ziffern, . _ -, maximal 80 Zeichen. Ein bereits vorhandener Name wird ersetzt. |
kind | image (Standard) oder video. Benötigt entsprechend die Berechtigung images oder videos. |
trigger | Das Triggerwort, das im Prompt angegeben werden muss, maximal 60 Zeichen. |
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.safetensorsDie Antwort lautet { "ok": true, "name": "my_character.safetensors", "kind": "image", "size": 314572800, "trigger": "ohwx", "architecture": "krea2", "storage": { ... } }.
Die Kompatibilität wird beim Hochladen geprüft. Bilder werden von Krea 2 gerendert, das eine eigene Architektur hat: nur eine Krea 2-LoRA wird angewendet, egal ob in tendre.AI oder anderswo für Krea 2 trainiert. Eine FLUX.1-LoRA wird abgelehnt, was nicht jeder auf Anhieb erwartet. Videos werden von LTX 2.5 gerendert, also eine LTX 2.5 oder 2.3-LoRA.
Die API liest den Datei-Header, bevor sie etwas speichert. Eine LoRA, die als SDXL, SD 1.5, SD3, FLUX.1, Wan oder HunyuanVideo erkannt wird, wird mit 415 incompatible_lora abgewiesen, das die gefundene architecture nennt, und eine Datei, die keine Safetensors-Datei ist, mit 415 invalid_safetensors. In beiden Fällen wird nichts gespeichert und kein Kontingent verbraucht.
Ein Fall kommt trotzdem durch: eine Architektur, die der Header nicht identifiziert, wird akzeptiert, mit architecture: "inconnue", und erst beim Rendern beurteilt. Das ist der einzige Fehler, der dich einen Render kostet, um ihn zu entdecken.
Eine große LoRA in Teilen senden
Drei Aufrufe, plus einen zum Abbrechen.
1. Sitzung öffnen. POST /loras/upload/start mit { "name", "kind", "size", "trigger" }, wobei size die gesamte Dateigröße in Bytes ist. Es prüft Name, Größe und Kontingent und antwortet dann:
{ "uploadId": "...", "part_size": 20971520, "max_part_size": 104857600, "parts_expected": 15 }2. Jeden Teil senden. POST /loras/upload/part?uploadId=...&part=N, roher Body, Teile ab 1 nummeriert, je part_size Bytes außer beim letzten, niemals mehr als max_part_size. Jede Antwort enthält ein etag, und du brauchst alle davon für den nächsten Schritt, also speichere sie unterwegs.
Teil 1 ist besonders: er enthält den Datei-Header, hier wird also die Architektur beurteilt. Eine inkompatible LoRA wird dort mit 415 abgefangen und die Sitzung abgebrochen. Du erfährst es nach 20 MB statt nach 400.
3. Abschließen. POST /loras/upload/finish mit { "uploadId", "parts": [ { "part": 1, "etag": "..." }, ... ] }. Es setzt die Datei zusammen und registriert sie, mit einer Antwort genau wie beim einmaligen Upload.
Zum Abbrechen POST /loras/upload/abort mit { "uploadId" }. Eine Sitzung ohne finish wird nach sechs Stunden verworfen. In GET /loras erscheint nichts und nichts wird auf das Kontingent angerechnet, bis finish erfolgreich ist, sodass ein unterbrochener Upload keinen Speicherplatz kostet.
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"])Ein fertiges PowerShell-Skript wird mit der App geliefert, unter scripts/api-pro-lora.ps1.
Eine LoRA löschen
DELETE /loras/{name}?kind=image|video entfernt die Datei und gibt das Kontingent frei. Die Antwort lautet { "ok": true, "name": ..., "storage": { ... } }, oder 404 unknown_lora, wenn der Name auf dem Account nicht existiert.
Speicherkontingent
Jede Lizenz erhält maximal 20 Dateien und 10 GB, Bilder und Videos zusammen, und die in der App trainierten Charaktere zählen dazu. GET /loras meldet den Stand unter storage:
{ "count": 3, "max_count": 20, "bytes": 943718400, "max_bytes": 10737418240 }Sobald eines der Limits erreicht ist, antwortet ein Upload mit 413 lora_quota und zeigt, was noch verfügbar ist. Lösche eine Datei, um Platz zu schaffen.
Das Training eines Charakters ist über die API noch nicht verfügbar. Es geschieht in der App, und das Ergebnis erscheint in dieser Liste.
Aktivität
Die App führt das Protokoll jedes Renders, den deine Keys durchgeführt haben (Datum, Key, Typ, Kosten, Status), sowie den Verbrauch pro Key, unter Einstellungen > API > Aktivität.
Dort widerrufst du auch einen Key, der kompromittiert wurde, ohne die anderen zu berühren.