spicyapiDokumentation
Mit der API entwickeln
Hauptinhalt

Medien

Vorsignierte Direktuploads für Referenzmedien und kurzlebige Downloadlinks für Ergebnisdateien.

Viele Modelle nehmen Bild, Video oder Audioclip als Eingabe, eine Referenz, und liefern eine Datei, ein Ergebnis, zurück. Diese Seite behandelt beide Richtungen:

  • Referenzeingaben: Öffentliche HTTPS-URL, direkt in die Anfrage eingebettetes kleines Bild oder Uploaddatei. Fordern Sie ein Uploadticket an, senden die Datei per PUT direkt in den Objektspeicher und bestätigen den Upload. Die Bestätigung liefert eine spicy://-URI für die Taskeingabe.
  • Ergebnisdownloads: Rufen Sie die signierte output.assets[].url aus dem Ergebnis ab. Nicht jedes Modell erzeugt Dateien: Textendpunkte antworten mit output.text ohne assets, ohne Download und ohne ablaufenden Link. Siehe Ausgabestruktur.

Uploadtickets und Ergebnisdownloads liegen unter /api/v1/common/, Uploadbestätigungen unter /api/v1/files/{fileId}/commit. Uploads sind kostenlos; Sie bezahlen nur den Task, der die Datei verwendet.

Vorhandene HTTPS-Medien

Geben Sie eine öffentlich erreichbare HTTPS-Bild-, Video- oder Audio-URL direkt im unterstützten Modellfeld wie image_url an. Plattformupload oder Umwandlung zu spicy:// ist nicht erforderlich. Die URL muss während des Tasks ohne eigene Autorisierungsheader erreichbar bleiben. Für größere lokale Dateien verwenden Sie den Uploadablauf unten.

Die URL muss die Datei selbst liefern. Eine Freigabeseite von Cloudlaufwerk, Fotoalbum oder Social-Beitrag liefert eine Webseite statt des Bildes; der Task scheitert mit invalid_asset. Laden Sie dann die Datei herunter und hoch.

Base64 und lokale Dateien

Kleine Bilder können direkt in schemadefinierten Bildfeldern stehen, etwa image_url: "data:image/png;base64,...". Nennen Sie den MIME-Typ und verwenden Standard-Base64. Reines Base64, URL-sichere Kodierung, Leerraum, SVG und eingebettetes Audio oder Video werden hier nicht unterstützt.

  • JPEG, PNG, WebP, GIF: höchstens 1 MiB dekodiert je Bild, 8192 Pixel je Seite und 8388608 Pixel je Bild.
  • Höchstens 16 Bilder und 16777216 Gesamtpixel je Anfrage. Der gesamte JSON-Inhalt einschließlich ungefähr einem Drittel Base64-Größenaufschlag muss in 2 MiB passen.
  • Modellschema und Mediengrenzen gelten weiterhin. Preisabfragen validieren ohne Upload oder Reservierung. Erstellung speichert Bilder als kontogebundene Uploads mit einem Tag Lebensdauer; Taskeingabe enthält Datei-URIs statt zurückgegebener Base64-Bytes.

Für größere Dateien übernimmt das SDK Ticket, PUT und Bestätigung:

import { SpicyClient } from '@spicyapi/sdk';

const client = new SpicyClient();
const file = await client.uploadFile('./reference.png');
// With existing Base64, use uploadBase64(dataUri),
// or uploadBase64(rawBase64, { contentType: 'image/png' }).
console.log(file.uri);

uploadBase64 ist eine Datei-Uploadhilfe über dieselbe Upload-API und ihre Grenzen unten. Das 1-MiB-Limit kleiner eingebetteter Bilder gilt dafür nicht; Modelle müssen den Medientyp weiterhin unterstützen. Vorhandene öffentliche HTTPS-Medien benötigen keine Uploadhilfe.

1. Direkter Referenzupload

Eine lokale Referenz ohne öffentliche HTTPS-URL benötigt vier Schritte. Führen Sie die curl-Befehle nacheinander im selben Terminalfenster auf macOS oder Linux mit installiertem jq aus. Jeder Schritt bewahrt nötige Werte für den nächsten in Shellvariablen. Unter Windows verwenden Sie SDK oder Python-Beispiel am Abschnittsende.

Ticket anfordern

POST /api/v1/common/upload-url

Eigenschaft

Typ

Ein Dateinamenfeld fehlt bewusst

Objektschlüssel werden stets serverseitig erzeugt. Freie Pfadwahl könnte Upload-URLs für fremde Objekte und deren Überschreibung ermöglichen.

Uploadgrenzen

contentTypeEndungMaximale DateiMaximale Dauer
image/jpeg.jpg10 MiB–
image/png.png10 MiB–
image/webp.webp10 MiB–
image/gif.gif10 MiB–
audio/wav.wav90 MiB600 Sekunden
audio/mpeg.mp390 MiB600 Sekunden
video/webm.webm90 MiB600 Sekunden
video/mp4.mp490 MiB600 Sekunden
video/quicktime.mov90 MiB600 Sekunden
application/octet-stream.safetensors (LoRA-Gewichte)2 GiB–

.mov, das Standardformat einer iPhone-Kamera, wird akzeptiert und beim Eingang zu MP4 konvertiert; das Modell erhält stets MP4. Webuploader und Upload-API unterstützen es. CLI, SDK und MCP-Server leiten den Typ aus der Endung ab und führen es noch nicht auf; übergeben Sie dort ausdrücklich contentType: video/quicktime.

LoRA-Gewichte werden als application/octet-stream hochgeladen: eine .safetensors-Datei bis 2 GiB (der Webuploader nimmt bis zu 90 MiB). Das Ticket bleibt zwei Stunden statt 20 Minuten gültig, und beim Commit wird geprüft, ob es sich um eine vollständige Safetensors-Datei handelt. Tragen Sie die zurückgegebene URI in loras[].path ein; eine Gewichtsdatei lässt sich in keinem anderen Feld verwenden. Eine Datei, die für den Download eine Anmeldung erfordert, etwa in einem privaten Hugging-Face-Repository, können Sie stattdessen über ihren Link importieren: siehe Adaptergewichte.

Ein Modell kann weniger akzeptieren. Beachten Sie x-ui.max_size_mb seines Schemas und die maßgebliche Uploadobergrenze maxBytes des Tickets.

curl
FILE=reference.png            # the file to upload
CONTENT_TYPE=image/png        # must match the file type in the table above
BYTES=$(wc -c < "$FILE" | tr -d ' ')

TICKET=$(curl -s -X POST https://api.spicyapi.ai/api/v1/common/upload-url \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  --data "$(jq -n --arg type "$CONTENT_TYPE" --argjson bytes "$BYTES" '{contentType: $type, bytes: $bytes}')")
echo "$TICKET" | jq .

UPLOAD_URL=$(echo "$TICKET" | jq -r .data.uploadUrl)
FILE_ID=$(echo "$TICKET" | jq -r .data.fileId)

Eine Antwort sieht etwa so aus; UPLOAD_URL und FILE_ID enthalten danach uploadUrl und fileId:

{
  "code": 200,
  "msg": "success",
  "data": {
    "fileId": "fil_01k3m8x9q2z4v7n5p6r8s0t1w3",
    "key": "spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3",
    "uploadUrl": "https://<account-id>.r2.cloudflarestorage.com/spicy-production-inputs/tmp/…?X-Amz-Signature=…",
    "method": "PUT",
    "headers": {
      "Content-Type": "image/png",
      "Content-Length": "402118"
    },
    "expiresAt": "2026-08-28T09:32:11Z",
    "maxBytes": 10485760
  }
}

expiresAt liegt ungefähr 20 Minuten später. PUT und Bestätigung müssen vorher fertig sein; danach liefert commit 409 und benötigt ein neues Ticket. Fordern Sie es unmittelbar vor Upload an, nicht im Voraus.

Datei per PUT senden

Senden Sie die Bytes per PUT an uploadUrl. Nicht-Browser-Clients wie curl, Node.js und Python müssen alle Header aus headers exakt übernehmen. Browser behandeln Content-Length wie unten beschrieben.

Ein fehlender Header macht die Signatur ungültig

Content-Type und Content-Length sind signiert. Schon eine Byteänderung führt zur Uploadablehnung im Objektspeicher mit dortigem 403, nicht von SpicyAPI, was zunächst verwirrend wirken kann.

Browser verbieten das manuelle Setzen von Content-Length über fetch oder XMLHttpRequest. Senden Sie einen File oder Blob mit exakt passender Ticket-bytes-Größe und lassen den Browser Content-Length aus dem Inhalt erzeugen. Setzen Sie nur die übrigen Ticketheader selbst. Die Antwort listet Content-Length weiterhin für andere Clients und zur Größenprüfung vor Browserupload.

Senden Sie hierbei keinen Authorization-Header. Die vorsignierte URL ist die Berechtigung; zusätzliche Authentifizierung kann manche Speichersysteme zur Ablehnung bringen.

curl
# Prints only the HTTP status. Expect 200.
curl -s -o /dev/null -w "%{http_code}\n" -X PUT "$UPLOAD_URL" \
  -H "Content-Type: $CONTENT_TYPE" \
  -H "Content-Length: $BYTES" \
  --data-binary @"$FILE"
Browser
const uploadHeaders = new Headers(ticket.data.headers);
uploadHeaders.delete('Content-Length'); // the browser derives it from File/Blob

if (file.size !== Number(ticket.data.headers['Content-Length'])) {
  throw new Error('file size does not match the upload ticket');
}

await fetch(ticket.data.uploadUrl, {
  method: ticket.data.method,
  headers: uploadHeaders,
  body: file, // File or Blob; do not turn it into text, Base64 or FormData
});

Upload bestätigen

Erfolgreiches PUT bedeutet nur ein geschriebenes temporäres Objekt. Fahren Sie mit fileId aus dem ersten Schritt fort:

curl
curl -s -X POST "https://api.spicyapi.ai/api/v1/files/$FILE_ID/commit" \
  -H "Authorization: Bearer $SPICY_API_KEY" | jq .

Erwarten Sie "status": "ready" und eine mit spicy:// beginnende uri:

{
  "code": 200,
  "msg": "success",
  "data": {
    "fileId": "fil_01k3m8x9q2z4v7n5p6r8s0t1w3",
    "status": "ready",
    "bytes": 402118,
    "contentType": "image/png",
    "sha256": "a91f4c2e5b7d16809e3f2a4c8d0b61e7c4a890e5d2f71b3c609ae8427d15f0c6",
    "uri": "spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3",
    "expiresAt": "2026-08-29T09:12:11Z"
  },
  "request_id": "req_01k3m8x9q2z4v7n5p6r8s0t1w2"
}

Commit vergleicht gespeicherte Größe und Medientyp mit dem Ticket, prüft Mediensignatur, berechnet SHA-256 und kopiert Bytes in ein privates Objekt, das das alte Ticket nicht überschreiben kann. Bei Audio und Video können zusätzlich durationSeconds als Dezimalstring sowie numerische width und height erscheinen. Erfolgreiches Commit wiederholen ist idempotent.

AntwortBedeutungKorrektur
400, code: 40003Gespeicherte Größe, Typ oder Format passen nicht zur AngabeNeues Ticket mit korrektem contentType und exakten bytes anfordern, erneut hochladen
400Datei fehlt; PUT noch nicht fertig oder gescheitertPUT-Antwort 200 prüfen, dann erneut bestätigen
404Keine solche fileId im KontofileId Ihres eigenen Tickets verwenden
409Ticket oder Datei abgelaufen oder frühere Bestätigung lehnte Datei abBei Schritt 1 neu beginnen
409 / 429Datei wird bereits geprüft oder Prüfer ist ausgelastetEine Sekunde warten, erneut bestätigen

Nach PUT muss commit folgen

Eine unbestätigte Datei ist nicht für Tasks nutzbar. Verwenden Sie weder temporären Ticketpfad noch Kompatibilitätsfeld key, bevor commit erfolgreich ist.

spicy://-URI als Eingabe verwenden

Verwenden Sie nur data.uri aus der Bestätigung als Referenzfeld, nicht uploadUrl, temporären Objektschlüssel oder reine fileId.

Das Dateifeld hängt vom Modell ab. Im inputSchema besitzt es x-ui.widget gleich upload für eine Datei oder multi-upload für eine Liste, deren URI ins Array gehört. x-ui.accept nennt Typen und x-ui.max_size_mb die akzeptierte Größe. Siehe Modellkatalog und Schemas.

MODEL_ID_FROM_CATALOG und übrige Felder unten sind Strukturplatzhalter. Wählen Sie exakt model und schemagültige Eingabe desselben authentifizierten Liveeintrags. Die Aufgabe bestimmt model; input enthält kein Endpunktfeld. Nur im inputSchema gelistete Felder sind erlaubt, ein Zusatzfeld liefert 400:

{
  "model": "MODEL_ID_FROM_CATALOG",
  "input": {
    "prompt": "slow dolly in, rain on the window",
    "image_url": "spicy://f/fil_01k3m8x9q2z4v7n5p6r8s0t1w3",
    "duration_seconds": 5
  }
}

Die Plattform löst die bestätigte spicy://-URI bei Taskausführung auf. Uploads bleiben so während der Warteschlange nutzbar, ohne Verwaltung temporärer Download-URLs. Verwenden Sie die Commit-URI unverändert.

Nicht bereite, abgelaufene oder fremden Konten gehörende URIs werden bei Erstellung mit 400 abgelehnt.

Vollständiges Beispiel

JavaScript
import { readFile } from 'node:fs/promises';

const BASE = 'https://api.spicyapi.ai/api/v1';
const AUTH = { Authorization: `Bearer ${process.env.SPICY_API_KEY}` };

async function uploadReference(path, contentType) {
  const file = await readFile(path);

  // 1. Get a ticket
  const ticketRes = await fetch(`${BASE}/common/upload-url`, {
    method: 'POST',
    headers: { ...AUTH, 'Content-Type': 'application/json' },
    body: JSON.stringify({ contentType, bytes: file.byteLength }),
  });
  const ticket = await ticketRes.json();
  if (ticket.code !== 200) throw new Error(`${ticket.code} ${ticket.msg}`);

  // 2. Upload directly. No Authorization here; pass the headers through as-is
  const put = await fetch(ticket.data.uploadUrl, {
    method: ticket.data.method,
    headers: ticket.data.headers,
    body: file,
  });
  if (!put.ok) throw new Error(`upload failed: ${put.status}`);

  // 3. Commit so the server verifies and freezes the uploaded bytes
  const commitRes = await fetch(
    `${BASE}/files/${encodeURIComponent(ticket.data.fileId)}/commit`,
    { method: 'POST', headers: AUTH },
  );
  const committed = await commitRes.json();
  if (committed.code !== 200) throw new Error(`${committed.code} ${committed.msg}`);

  // 4. Task input uses only the spicy:// URI returned by commit
  return committed.data.uri;
}

console.log(await uploadReference('./reference.png', 'image/png'));
Python
import os, requests

BASE = "https://api.spicyapi.ai/api/v1"
AUTH = {"Authorization": f"Bearer {os.environ['SPICY_API_KEY']}"}


def upload_reference(path: str, content_type: str) -> str:
    with open(path, "rb") as f:
        data = f.read()

    # 1. Get a ticket
    ticket = requests.post(
        f"{BASE}/common/upload-url",
        headers={**AUTH, "Content-Type": "application/json"},
        json={"contentType": content_type, "bytes": len(data)},
    ).json()
    if ticket["code"] != 200:
        raise RuntimeError(f'{ticket["code"]} {ticket["msg"]}')

    # 2. Upload directly. No Authorization; pass the headers through as-is
    put = requests.put(ticket["data"]["uploadUrl"], headers=ticket["data"]["headers"], data=data)
    put.raise_for_status()

    # 3. Commit so the server verifies and freezes the uploaded bytes
    committed = requests.post(
        f'{BASE}/files/{ticket["data"]["fileId"]}/commit',
        headers=AUTH,
    ).json()
    if committed["code"] != 200:
        raise RuntimeError(f'{committed["code"]} {committed["msg"]}')

    # 4. Task input uses only the spicy:// URI returned by commit
    return committed["data"]["uri"]


print(upload_reference("reference.png", "image/png"))

Zusätzliches Ratenlimit

Zusätzlich zum allgemeinen offenen API-Kontingent hat upload-url eine eigene übergreifende Grenze von 1000 Anfragen je 10 Sekunden gegen missbräuchliche oder unkontrollierte Objektspeicher-Schreibautorisierung.

Der Aufwand ist asymmetrisch: Ticketsignierung ist für uns rein lokale Berechnung; das Ticket erlaubt danach Schreiben ohne weitere Verarbeitung durch uns. Manuelle Dateiauswahl erreicht diese Rate nicht, und ein Skript mit einigen Dutzend Referenzen bleibt problemlos darunter.

2. Ergebnisse herunterladen

Bereite Ergebnisse enthalten beim Polling und in v2-Callbacks bereits output.assets[].url und expiresAt. Rufen Sie die URL direkt ohne API-Key ab; download-url unten ist optional. Links gelten normalerweise 20 Minuten und nie über die Taskaufbewahrung hinaus: höchstens ungefähr 14 Tage, bei Ihrer kürzeren Einstellung weniger.

Speichern Sie zu behaltende Ergebnisse während gültigem Link in eigenem Speicher, etwa:

curl -L -o result.png "PASTE_THE_OUTPUT_URL_HERE"

Ergänzen Sie keinen Authorization-Header. Bei abgelaufenem Link mit Speicherantwort 403 fragen Sie erneut recordInfo für einen frischen Link ab.

Optionalen Downloadlink erneuern

POST /api/v1/common/download-url

Eigenschaft

Typ

curl
curl -X POST https://api.spicyapi.ai/api/v1/common/download-url \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "taskId": "job_01k3m8x9q2z4v7n5p6r8s0t1w3",
    "key": "tasks/2026/08/28/job_01k3m8x9q2z4v7n5p6r8s0t1w3/9f3c1d0a7b4e2f68c5a1d3e9b0472fa1.png"
  }'
{
  "code": 200,
  "msg": "success",
  "data": {
    "key": "tasks/2026/08/28/job_01k3m8x9q2z4v7n5p6r8s0t1w3/9f3c1d0a7b4e2f68c5a1d3e9b0472fa1.png",
    "url": "https://<account-id>.r2.cloudflarestorage.com/spicy-production-results/tasks/…?X-Amz-Signature=…",
    "expiresAt": "2026-08-28T09:32:11Z"
  }
}

Bei mehreren Ergebnissen rufen Sie einmal je key auf. Wie recordInfo funktioniert das nur mit dem erstellenden API-Key.

Eine signierte URL ist eine selbst legitimierende Zeichenfolge: Der Objektspeicher fragt nicht erneut, wer sie besitzt. Deshalb:

  • Nicht in Ihrer Datenbank speichern und nicht in langfristigem an Browser ausgelieferten Cache halten. Signieren Sie bei Bedarf; die lokale Berechnung ist praktisch kostenlos.
  • Laden Sie zu behaltende Dateien herunter. Bei uns bleiben sie höchstens ungefähr 14 Tage, bei verkürzter Frist weniger. Siehe Aufbewahrung und Vernichtung.
SituationAnzeigeMaßnahme
Kopie in unseren Speicher läuft nochDatei zeigt pending: true in recordInfo; download-url ohne key liefert 409, mit explizitem key bis Kopierende 404Einige Sekunden warten, erneut abfragen
Kopie endgültig gescheitertDatei zeigt unavailable: true ohne keyWarten hilft nicht; neu generieren
Task noch nicht erfolgreich404, „This task has no downloadable output“Auf state: "succeeded" warten
Aufbewahrung abgelaufen oder Inhalt vernichtetHTTP 410 mit code: 404, „This output has expired or was deleted“Endgültig weg; neu generieren

Fremder Task und nicht zum Task gehöriger key liefern beide 404. Wir unterscheiden fehlend und fremd nicht, um Erkundung fremder Task-IDs zu verhindern.

Asset-Bibliothek

Manche Dateien brauchen Sie immer wieder: ein Charakterporträt, eine Stilreferenz, ein Produktfoto, eine Stimmprobe. Die Asset-Bibliothek bewahrt sie auf, bis Sie sie löschen, und gibt jeder Datei eine feste Referenz, spicy://a/<id>. Tragen Sie diese Referenz in ein beliebiges Medienfeld einer beliebigen Task-Eingabe ein, genau wie bei einem spicy://f/-Upload, nur ohne den Ablauf nach einem Tag.

  • Privat innerhalb Ihres Workspaces. Eine spicy://a/-Referenz funktioniert nur in Anfragen mit Keys desselben Workspaces. Ein persönlicher Key erreicht Ihre persönliche Bibliothek; ein Key aus einem Team-Workspace erreicht die gemeinsame Bibliothek dieses Teams. Beide sind nie verbunden (siehe unten).
  • Gespeichert, bis gelöscht wird. Generierte Ergebnisse werden nach etwa 14 Tagen bereinigt, Uploads nach 1 Tag; gespeicherte Assets bleiben, bis Sie sie löschen. Eine persönliche Bibliothek verschwindet mit dem Schließen Ihres Kontos, eine Team-Bibliothek mit ihrem Team.
  • Kein Datei-Hosting. Die API liefert die Referenz spicy://a/<id> und eine nur für Sie signierte previewUrl, höchstens 20 Minuten gültig. Es gibt keinen öffentlichen oder dauerhaften Link, und aus einer Referenz lässt sich keiner machen. Speichern Sie uri, niemals previewUrl.

Persönliche und Team-Bibliotheken

Der Key bestimmt die Bibliothek. Ein Key aus Ihrem persönlichen Workspace erreicht Ihre persönliche Bibliothek. Ein Key aus einem Team-Workspace, von welchem Mitglied auch immer erstellt, erreicht die gemeinsame Bibliothek dieses Teams. Ein Team-Key erreicht nie die persönliche Bibliothek eines Menschen, ein persönlicher Key nie eine Team-Bibliothek, und eine spicy://a/-Referenz aus der jeweils anderen Bibliothek wird abgelehnt, als gäbe es sie nicht.

Was ein Team-Key ändern darf, richtet sich nach der aktuellen Rolle des Mitglieds, das ihn erstellt hat:

RolleSpeichernUmbenennen oder löschenAuflisten, lesen und verwenden
owner, adminJaJedes AssetJa
developerJaNur selbst gespeicherte AssetsJa
billing, readonlyNeinNeinJa

Hat dieses Mitglied das Team verlassen, erhält der Key 403 mit data.reason asset_workspace_forbidden. Assets zeigen nicht, wer sie gespeichert hat; Sie sehen nur, ob Sie eines ändern dürfen.

Datei speichern

POST /api/v1/assets kopiert eine Datei in Ihre Bibliothek. Der Header Idempotency-Key ist Pflicht. Wählen Sie eine von drei Quellen:

sourceFelderHinweise
task_outputtaskId sowie key oder indexEiner Ihrer eigenen erfolgreichen Tasks. index zählt ab 0 in der Reihenfolge von output.assets in recordInfo.
uploadfileIdEin bestätigter Upload: fil_… oder spicy://f/fil_….
urlurlEine öffentliche http- oder https-Adresse auf Port 80 oder 443, ohne Benutzername und Passwort. Die Datei wird einmal heruntergeladen, der Typ wird aus der Datei selbst bestimmt; akzeptiert werden nur Bilder, Videos und Audio.

name ist optional (bis zu 80 Zeichen, Standard Saved asset). Wenn Sie dieselbe Quelle erneut speichern, erhalten Sie das vorhandene Asset zurück, ohne zusätzlichen Speicherplatz zu belegen. Größe, Abmessungen und Dauer messen wir selbst.

curl
curl -X POST https://api.spicyapi.ai/api/v1/assets \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "source": "task_output",
    "taskId": "job_01k3m8x9q2z4v7n5p6r8s0t1w3",
    "index": 0,
    "name": "Hero portrait"
  }'
{
  "code": 200,
  "msg": "success",
  "data": {
    "id": "ast_01k3m9c4w8r2n6t5v1x7y0z3ab",
    "uri": "spicy://a/ast_01k3m9c4w8r2n6t5v1x7y0z3ab",
    "name": "Hero portrait",
    "kind": "image",
    "contentType": "image/png",
    "bytes": 1843201,
    "width": 1536,
    "height": 1024,
    "source": "task_output",
    "sourceId": "job_01k3m8x9q2z4v7n5p6r8s0t1w3",
    "createdAt": "2026-10-11T08:20:11Z",
    "updatedAt": "2026-10-11T08:20:11Z",
    "previewUrl": "https://…",
    "previewUrlExpiresAt": "2026-10-11T08:40:11Z"
  }
}

Im Task verwenden

Tragen Sie data.uri in ein Dateifeld ein, das im inputSchema des Modells x-ui.widget upload oder multi-upload hat:

{
  "model": "MODEL_ID_FROM_CATALOG",
  "input": {
    "prompt": "slow dolly in, rain on the window",
    "image_url": "spicy://a/ast_01k3m9c4w8r2n6t5v1x7y0z3ab",
    "duration_seconds": 5
  }
}

Eine gelöschte Referenz oder eine Referenz aus einer Bibliothek, die dieser Key nicht erreicht (ein anderes Konto oder der andere Workspace), wird beim Anlegen des Tasks abgelehnt, genau wie eine unbekannte ID.

Auflisten, umbenennen und löschen

GET /api/v1/assets liefert Ihre Assets als items plus nextCursor, das auf der letzten Seite fehlt. Filtern Sie mit kind (image, video oder audio), source (kommagetrennt: task_output,upload,url), q (Suche im Namen, ohne Beachtung der Groß-/Kleinschreibung), favorite (true nur für Favoriten, false für alle übrigen) und collectionId (ein Ordner, siehe unten). Sortiert wird mit sort: recent (Standard, neueste zuerst), oldest, name (A bis Z, ohne Beachtung der Groß-/Kleinschreibung) oder size (größte zuerst). limit liegt zwischen 1 und 100, Standard ist 100. Für die nächste Seite übergeben Sie den vorherigen nextCursor als cursor; ein Cursor funktioniert nur mit dem sort, aus dem er stammt, ein Cursor aus einer anderen Sortierung wird mit 400 abgelehnt.

curl
curl "https://api.spicyapi.ai/api/v1/assets?kind=image&q=portrait&sort=name&limit=20" \
  -H "Authorization: Bearer $SPICY_API_KEY"

curl -X PATCH https://api.spicyapi.ai/api/v1/assets/ast_01k3m9c4w8r2n6t5v1x7y0z3ab \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "Hero portrait v2"}'

curl -X DELETE https://api.spicyapi.ai/api/v1/assets/ast_01k3m9c4w8r2n6t5v1x7y0z3ab \
  -H "Authorization: Bearer $SPICY_API_KEY"

GET /api/v1/assets/{id} liefert ein einzelnes Asset mit frischer previewUrl. PATCH ändert den Namen, das Flag favorite oder beides (senden Sie nur, was sich ändern soll); die uri bleibt gleich. Nach DELETE verschwindet das Asset sofort, und seine uri funktioniert in neuen Anfragen nicht mehr. Beides lässt sich gefahrlos wiederholen und braucht keinen Idempotency-Key. Ein unbekanntes Asset und das Asset eines anderen Kontos liefern dasselbe 404.

Favoriten, Ordner und das erzeugende Modell

Jedes Asset hat ein Flag favorite. Sie setzen es mit PATCH und {"favorite": true}. In einer Team-Bibliothek gehört die Markierung zur Bibliothek, alle sehen also dieselben Favoriten. Ordner (in der API „Collections“ genannt) fassen Assets zusammen, ohne sie zu kopieren, und ein Asset kann in mehreren Ordnern liegen. Ordner legen Sie auf der Seite Assets der Web-App an und verwalten sie dort; über die API lassen sie sich weder anlegen noch bearbeiten, aber mit collectionId bei GET /api/v1/assets listen Sie die Assets eines Ordners auf (404, wenn der Ordner nicht in der Bibliothek liegt, auf die Ihr Schlüssel zugreift).

Jedes Asset trägt außerdem model, den öffentlichen Namen des Modells, das es erzeugt hat, und modelName, dessen Anzeigenamen. Bei Uploads und Links sind beide Felder leere Strings.

Figuren

Eine Figur besteht aus einem Namen und einer geordneten Gruppe von Bild-Assets aus derselben Bibliothek: bis zu 100 Bilder und eine optionale Notiz mit bis zu 280 Zeichen. Die Bilder sind die Assets selbst, es wird nichts kopiert. Wenn Sie eine Figur löschen, bleiben ihre Bilder erhalten; löschen Sie ein Bild, verschwindet es aus allen Figuren. Figuren legen und bearbeiten Sie auf der Seite Assets der Web-App oder in Studio. Sie brauchen keinen eigenen Speicher, es zählt nur Ihr Bibliotheksplan.

Die API liest Figuren nur:

  • GET /api/v1/assets/entities listet sie auf, neueste zuerst. Mit assetId bleiben nur Figuren übrig, die dieses Asset enthalten; limit liegt zwischen 1 und 100 (Standard 50), und cursor setzt mit dem vorherigen nextCursor fort.
  • GET /api/v1/assets/entities/{id} liefert eine Figur. Eine unbekannte Figur, die Figur eines anderen Kontos und eine in einer Bibliothek, auf die Ihr Schlüssel nicht zugreift, liefern dasselbe 404.

Jede Figur hat id, kind (immer character), name, note, cover (das Titelbild als Asset oder null, solange sie keine Bilder hat), assets (die Bilder in Reihenfolge, jeweils mit uri und privater previewUrl), count, createdAt und updatedAt. Welche Bibliothek gemeint ist, bestimmt wie bei Assets der Schlüssel, und das Lesen von Figuren teilt sich das Limit von 60 Anfragen pro Minute mit dem Lesen von Assets.

Um eine Figur zu verwenden, tragen Sie ihre Bilder in dieser Reihenfolge als spicy://a/-Verweise in das Referenzbild-Feld des Modells ein und verweisen im Prompt so darauf, wie es die Seite dieses Modells beschreibt. Figuren hießen früher Personas und lagen in einer eigenen Bibliothek; jetzt gehören sie zur Asset-Bibliothek. Bestehende spicy://p/-Verweise funktionieren weiter, allerdings nur in Ihrer persönlichen Bibliothek.

Wenn Sie einer Figur ein selbst hochgeladenes Bild (kein generiertes Ergebnis) hinzufügen, bestätigen Sie, dass Sie die Rechte daran besitzen und, falls eine reale Person zu sehen ist, deren Einwilligung vorliegt.

Speicherpläne

Speicher wird automatisch mit Ihren kumulierten Aufladungen freigeschaltet und kann nur steigen; eine Änderung der Tabelle löscht nie bereits gespeicherte Assets. Maßgeblich für Ihr Konto ist GET /api/v1/assets/usage: Ihr Plan, der belegte Speicher, die Limits, der nächste Plan samt dem Betrag, der dafür noch aufzuladen ist, und wie viele Assets Sie heute angelegt haben. Die Tabelle zeigt die aktuellen Standardwerte.

Der Speicher gehört dem Inhaber: Seine kumulierten Aufladungen bestimmen den Plan, und seine persönliche Bibliothek sowie alle Team-Bibliotheken, die er besitzt, teilen sich ein Kontingent. usage deckt dieses gesamte Kontingent ab und enthält ein scope-Objekt (workspaceId, personal, role, canWrite, canTopUp), das sagt, welche Bibliothek Ihr Key erreicht und was er dort darf. Ist canTopUp false (ein Team-Key, den nicht der Team-Inhaber erstellt hat), entfallen lifetimeTopupUsd und topupToNextTierUsd, und bei voller Bibliothek werden Sie gebeten, sich an den Team-Inhaber zu wenden, statt dass ein Betrag genannt wird.

PlanKumulierte AufladungenSpeicherAnzahlPro Datei
Freekeinekeiner: Neues kann nicht gespeichert werden0entfällt
Plusab $10500 MB200100 MB
Proab $1001 GB400100 MB
Studioab $2502 GB800100 MB
Maxab $1.00010 GB4.000100 MB

Ohne Aufladung können Sie nichts Neues speichern, bereits gespeicherte Assets bleiben aber nutzbar. Das Limit von 100 MB pro Datei gilt für Task-Ausgaben und Links. Dateien, die per Upload hereinkommen, unterliegen weiterhin den Upload-Limits selbst (Bilder 10 MiB, Audio und Video 90 MiB).

Ratenlimits und Fehler

  • Lesen (Liste, Abruf, usage): 60 Anfragen pro Minute. Speichern: 30 Anfragen pro Minute und 200 neue Assets pro UTC-Tag. Das Löschen eines Assets gibt das Tageskontingent nicht zurück. Beim Erreichen eines Limits erhalten Sie 429 mit dem Header Retry-After.
  • Eine volle Bibliothek, noch nicht freigeschalteter Speicher oder eine Datei über dem Limit pro Datei liefern 400 mit invalid_request. Die Meldung nennt Ihren aktuellen Plan und wie viel Sie aufladen müssen, um den nächsten freizuschalten.
  • 403 mit data.reason asset_workspace_forbidden, wenn der Key die Bibliothek nicht erreicht, oder asset_permission_denied, wenn Ihre Teamrolle die Änderung nicht erlaubt. 503, wenn der Plattformspeicher vorübergehend voll ist; versuchen Sie es später erneut.

Ein Fehler an diesen Endpunkten kann ein maschinenlesbares data.reason tragen: asset_library_locked, asset_library_full, asset_file_too_large, asset_daily_limit, asset_storage_full, asset_permission_denied, asset_workspace_forbidden oder asset_entity_name_taken. code, HTTP-Status und errorCode ändern sich mit oder ohne Grund nicht, und der Grund erscheint nur an den Endpunkten der Asset-Bibliothek; die OpenAI-kompatible und andere Kompatibilitätsschichten behalten ihre eigenen Fehlerformen. Behandeln Sie unbekannte Werte wie einen fehlenden Grund. Siehe Ursachencodes der Asset-Bibliothek.

In der Web-App gibt es außerdem einen Schalter für das automatische Speichern in der Bibliothek. Er gilt nur für Generierungen, die Sie in der Web-App starten; über die API gestartete Generierungen werden nie automatisch gespeichert. Rufen Sie für die, die Sie behalten möchten, POST /api/v1/assets auf.

Uploadaufbewahrung

Eine hochgeladene Referenz-URI wird ab erfolgreichem Commit 1 Tag aufbewahrt. Diese Plattformobergrenze können Sie unter Aufbewahrung und Vernichtung verkürzen oder durch Vernichtung nach Abschluss ersetzen. Nutzbar ist sie nur bis zum dort zurückgegebenen expiresAt. Danach wird sie unbrauchbar, auch für Wiederholung eines darauf verweisenden Tasks. Objektspeicher-Lebenszykluslöschung kann anschließend bis 24 Stunden verzögert sein; das verlängert nicht die Nutzbarkeit. Bewahren Sie für erneuten Bedarf eine eigene Kopie auf und laden sie erneut hoch.

Auf dieser Seite