sicherAI-API für Entwickler
Technische Anleitung für die Programmierschnittstelle (API) von sicherAI: Adresse, Anmeldung, Anfragen, Antworten, Fehler und Beispiele. Zahlen zu den Grenzen nennt nur die Leistungsbeschreibung.
Überblick
- Base URL
https://api.sicherai.com/v1- Endpunkte
GET /v1/modelsfür die Modellliste,POST /v1/chat/completionsfür eine Antwort als JSON oder als Stream- Vertragsversion
v1-2026-10-20für Felder, Fehler und Grenzen
Die API folgt dem Format der OpenAI-API, aber nur in dem geprüften Ausschnitt, den diese Seite beschreibt. Andere Pfade beantwortet sie mit 404 not_found, andere Methoden mit 405 method_not_allowed und unbekannte Felder mit 400 unsupported_parameter. Pfade gelten genau so, wie sie hier stehen: ohne Schrägstrich am Ende, ohne Query-Parameter und in Kleinbuchstaben.
Rufe die API nur von deinem Server auf. Sie setzt keine CORS-Kopfzeilen; Aufrufe aus dem Browser sind nicht vorgesehen.
Sende Anfragen nur über https. Hast du einen Schlüssel über http gesendet, widerrufe ihn und lege einen neuen an. Anfragen über http beantwortet die API mit 400 invalid_request, ohne Weiterleitung.
Schnellstart
- Ein Inhaber oder Admin gibt in der App unter Einstellungen › Organisation › API-Zugang die API der Organisation frei und legt ihre API-Monatsgrenze fest.
- Dort legt ein Inhaber oder Admin einen Schlüssel an. Der volle Schlüssel erscheint nur einmal; kopiere ihn sofort.
- Lege den Schlüssel in der Umgebungsvariable
SICHERAI_API_KEYab. Schreibe ihn nie in den Code. - Rufe
GET /v1/modelsauf und wähle eine Kennung aus der Liste. - Sende damit die erste Anfrage an
POST /v1/chat/completions. Die Codebeispiele zeigen beide Schritte.
Die Beispiele verwenden SICHERAI_API_KEY statt OPENAI_API_KEY. So senden andere OpenAI-Programme in derselben Umgebung den Schlüssel nicht an api.openai.com.
Authentisierung
Sende den Schlüssel im Kopf Authorization: Bearer <Schlüssel>. Andere Wege liest die API nicht: weder Query-Parameter noch Cookies noch Kopfzeilen wie x-api-key.
Die Organisation ergibt sich allein aus dem Schlüssel. Kopfzeilen wie OpenAI-Organization oder OpenAI-Project wertet die API nicht aus.
Ein Schlüssel hat die Form sai_<8 Zeichen>_<43 Zeichen> und besteht nur aus Buchstaben und Ziffern. Die App zeigt nach dem Anlegen nur das Präfix sai_<8 Zeichen>.
Fehlt der Schlüssel oder ist er falsch geformt, unbekannt, abgelaufen, widerrufen oder gesperrt, antwortet die API einheitlich mit 401 invalid_api_key und dem Kopf WWW-Authenticate: Bearer.
Modelle
GET /v1/models liefert die Modelle, die dein Schlüssel jetzt nutzen darf. Sende die Anfrage ohne Query-Parameter und ohne Body: Ein Query-Parameter ergibt 404 not_found, ein Body 400 invalid_request.
{
"object": "list",
"data": [
{ "id": "<modell-id>", "object": "model", "created": 1792454400, "owned_by": "sicherai" }
]
}Welche Modelle in der Liste stehen, hängt von deiner Organisation, ihren Modellfreigaben und der optionalen Modellauswahl des Schlüssels ab. Lies die Liste deshalb aus, statt Kennungen fest einzutragen.
created ist der Tag, an dem das Modell in die API aufgenommen wurde, nicht der Zeitpunkt der Abfrage. owned_by ist immer sicherai. Die Liste hat genau eine Seite. Der Abruf verbraucht keine Token; für ihn gilt eine eigene Grenze je Minute.
Steht ein Modell nicht in deiner Liste, beantwortet POST /v1/chat/completions die Anfrage mit 404 model_not_found, gleich aus welchem Grund. Diese Seite nennt keine Modelle: In den Beispielen steht der Platzhalter <modell-id>; setze dort eine Kennung aus deiner Liste ein.
Chat Completions als JSON
POST /v1/chat/completions nimmt einen JSON-Body in UTF-8 an, mit dem Kopf Content-Type: application/json. Pflicht sind model und messages.
messages ist eine Liste von Nachrichten mit genau den Feldern role und content. content ist ein nicht leerer Text oder eine Liste von Textblöcken der Form {"type": "text", "text": "…"}. Die Rolle user mit Text nimmt jedes Modell an. Die Rollen system, developer und assistant sowie Textblöcke gelten nur für Modelle, deren Route sie unterstützt; sonst antwortet die API mit 400 unsupported_parameter.
| Gruppe | Felder | Verhalten |
|---|---|---|
| Pflicht | model, messages | model nennt eine Kennung aus GET /v1/models; messages enthält mindestens eine Nachricht. |
| Unterstützt | stream, stream_options, max_completion_tokens | stream_options nur zusammen mit "stream": true und nur mit include_usage. max_completion_tokens begrenzt die Ausgabe; ohne das Feld gilt ein Standardwert. |
| Je nach Modell | max_tokens, temperature, top_p, stop, reasoning_effort | Nur für Modelle, deren Route das Feld unterstützt, und nur im dort geprüften Wertebereich; sonst 400 unsupported_parameter. max_tokens nie zusammen mit max_completion_tokens. |
| Ohne Wirkung | n: 1, store: false, logprobs: false, modalities: ["text"], tool_choice: "none", frequency_penalty: 0, presence_penalty: 0, user | Angenommen nur in genau dieser Form; jede andere ergibt 400 unsupported_parameter. user ist ein Text ohne Wirkung; ist er zu lang, folgt 400 invalid_request. |
| Abgewiesen | audio, function_call, functions, logit_bias, metadata, parallel_tool_calls, prediction, response_format, seed, service_tier, tools, top_logprobs, web_search_options | Jeder Wert außer null ergibt 400 unsupported_parameter, ebenso jedes unbekannte Feld. |
null gilt wie ein fehlendes Feld, außer bei model und messages.
{
"id": "chatcmpl-0123456789abcdef0123456789abcdef",
"object": "chat.completion",
"created": 1792540800,
"model": "<modell-id>",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "OK" },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 21, "completion_tokens": 2, "total_tokens": 23 }
}id beginnt mit chatcmpl-, created zählt in Unix-Sekunden, model ist die angefragte Kennung. choices enthält genau einen Eintrag; finish_reason ist stop, length oder content_filter. usage nennt prompt_tokens, completion_tokens einschließlich Reasoning-Token und total_tokens als Summe beider.
Antwortköpfe: content-type: application/json, cache-control: no-store und x-request-id mit einer Kennung aus req_ und 32 Hexzeichen. Fehler, die schon der vorgelagerte Proxy beantwortet, etwa 404 not_found für einen unbekannten Pfad, tragen keine x-request-id.
Die JSON-Antwort kommt erst, wenn die Antwort vollständig vorliegt; bis dahin sendet die API auch keine Kopfzeilen. Für lange Ausgaben nimm den Stream.
Stream
Mit "stream": true antwortet die API mit Server-Sent Events. Kopfzeilen: content-type: text/event-stream, cache-control: no-store, x-accel-buffering: no und x-request-id. Jedes Ereignis ist eine Zeile data: <JSON> mit einer Leerzeile danach, in UTF-8. stream_options ist nur bei "stream": true erlaubt und kennt nur include_usage.
- Ein Chunk
chat.completion.chunkmit"delta": {"role": "assistant", "content": ""}.id,createdundmodelbleiben im ganzen Stream gleich. - Textteile in
choices[0].delta.content;finish_reasonistnull. - Ein Abschlusschunk mit leerem
deltaundfinish_reasonstop,lengthodercontent_filter. - Nur mit
"stream_options": {"include_usage": true}: ein letzter Chunk mit"choices": []und der vollständigenusage. Alle Chunks davor tragen"usage": null; ohne die Option fehlt das Feldusage. data: [DONE]genau einmal; danach endet der Stream.
Ein Stream ohne data: [DONE] ist nicht erfolgreich. Die SDKs reichen [DONE] nicht an dich weiter und beenden die Schleife ohne Fehler, wenn der Stream ohne [DONE] endet. Fordere deshalb include_usage an und prüfe am Ende, ob der Usage-Chunk angekommen ist: Er kommt erst, wenn die Antwort vollständig ist.
Fehler vor dem ersten Chunk beantwortet die API als HTTP-Fehler im JSON-Format. Danach sendet sie ein Ereignis data: {"error": {…}} im selben Format und beendet den Stream ohne [DONE]; der HTTP-Status bleibt 200. Die SDKs werfen dann einen APIError mit code, aber ohne HTTP-Status. Nach dem Start eines Streams kommen nur Fehler ohne automatische Wiederholung; aus 503 service_unavailable wird dort 500 internal_error.
Brichst du den Stream ab, etwa mit einem AbortController, beendet die API auch den Aufruf beim Modellanbieter. Während des Streams prüft die API wiederholt Schlüssel, Organisation und Zugang. Fällt eine Voraussetzung weg, etwa durch einen Widerruf, endet der Stream mit einem Fehlerereignis.
Jede Anfrage hat eine Höchstdauer. Danach endet ein Stream mit dem Fehlerereignis upstream_timeout, eine JSON-Anfrage mit 504 upstream_timeout. Lies den Stream zügig: Was dein Client bis zur Höchstdauer nicht gelesen hat, verfällt mit der Verbindung.
Fehler
Jede Fehlerantwort der API hat diesen Körper:
{
"error": {
"message": "Die Anfrage entspricht nicht dem Vertrag der sicherAI-API.",
"type": "invalid_request_error",
"param": null,
"code": "invalid_request"
}
}Werte code aus, nicht message: message ist ein fester deutscher Text für Menschen. param nennt höchstens den Namen eines Felds, nie einen Wert. Fehler ohne automatische Wiederholung tragen den Kopf x-should-retry: false; die SDKs wiederholen sie dann nicht von sich aus. 401 trägt WWW-Authenticate: Bearer, 405 trägt Allow, 429 mit rate_limit_error trägt Retry-After in ganzen Sekunden, 503 kann Retry-After tragen.
| code | HTTP | type | Bedeutung | Automatisch wiederholen |
|---|---|---|---|---|
invalid_request | 400 | invalid_request_error | Die Anfrage passt nicht zum Vertrag, etwa ungültiges JSON, ein leerer Text, ein Body bei GET /v1/models, ein ungültiger Idempotency-Key oder eine Anfrage über http. | Nein. Anfrage korrigieren. |
unsupported_parameter | 400 | invalid_request_error | Ein Feld oder Wert wird nicht unterstützt, von der API oder von der Route des Modells. param nennt das Feld, wenn es zur Feldmatrix gehört. | Nein. Anfrage korrigieren. |
context_length_exceeded | 400 | invalid_request_error | Die Eingabe ist zu lang: zu viele Nachrichten, zu viel Text oder mehr Token, als das Modell oder eine Minute zulässt. | Nein. Eingabe kürzen. |
invalid_api_key | 401 | authentication_error | Der Schlüssel fehlt, ist falsch geformt, unbekannt, abgelaufen, widerrufen oder gesperrt. | Nein. Schlüssel prüfen oder neu anlegen. |
api_access_denied | 403 | permission_error | Der API-Zugang der Organisation ist nicht freigegeben oder gesperrt. | Nein. Ein Inhaber oder Admin prüft den API-Zugang. |
contract_required | 403 | permission_error | Der Organisation fehlen Vereinbarungen, die der API-Zugang voraussetzt. | Nein. Ein Inhaber oder Admin klärt das in der App. |
model_not_found | 404 | invalid_request_error | Die Modellkennung steht nicht in der Modellliste deines Schlüssels. | Nein. Kennung aus GET /v1/models nehmen. |
not_found | 404 | invalid_request_error | Diesen Pfad gibt es nicht, auch nicht mit Schrägstrich am Ende oder mit Query-Parametern. | Nein. Pfad prüfen. |
method_not_allowed | 405 | invalid_request_error | Die Methode passt nicht zum Pfad; der Kopf Allow nennt die erlaubte. | Nein. Methode aus Allow nehmen. |
idempotency_conflict | 409 | invalid_request_error | Der Idempotency-Key ließ sich für diese Anfrage nicht verwenden. | Nein. |
request_in_progress | 409 | invalid_request_error | Eine Anfrage mit demselben Idempotency-Key läuft noch, oder ihr Ausgang ist offen. | Nein. |
request_already_processed | 409 | invalid_request_error | Eine Anfrage mit demselben Idempotency-Key ist abgeschlossen; ihre Antwort kommt nicht erneut. | Nein. |
request_too_large | 413 | invalid_request_error | Der Body ist zu groß. | Nein. Anfrage verkleinern. |
rate_limit_exceeded | 429 | rate_limit_error | Zu viele Anfragen oder Token im laufenden Minutenfenster. | Ja, nach der Zeit in Retry-After. |
concurrency_limit_exceeded | 429 | rate_limit_error | Zu viele gleichzeitige Anfragen. | Ja, nach der Zeit in Retry-After. |
org_budget_exceeded | 429 | insufficient_quota | Das Nutzungsbudget der Organisation ist für den laufenden Zeitraum aufgebraucht. | Nein. |
api_budget_exceeded | 429 | insufficient_quota | Die API-Monatsgrenze der Organisation ist erreicht oder auf 0 gesetzt. | Nein. |
key_budget_exceeded | 429 | insufficient_quota | Die Monatsgrenze dieses Schlüssels ist erreicht oder auf 0 gesetzt. | Nein. |
short_window_exceeded | 429 | insufficient_quota | Das Kurzzeitbudget der Organisation ist ausgeschöpft; es wird mit dem gleitenden Zeitfenster wieder frei. | Nein. |
internal_error | 500 | server_error | Interner Fehler. Der Aufruf kann den Modellanbieter schon erreicht haben. | Nein. Wenn überhaupt, dann einmal mit demselben Idempotency-Key. |
upstream_error | 502 | server_error | Der Modellanbieter hat nicht erfolgreich geantwortet. | Nein. Wenn überhaupt, dann einmal mit demselben Idempotency-Key. |
service_unavailable | 503 | server_error | Die API nimmt vorübergehend keine Anfragen an; der Modellanbieter wurde noch nicht aufgerufen. | Ja, später. |
upstream_timeout | 504 | server_error | Der Modellanbieter hat nicht rechtzeitig geantwortet, oder die Höchstdauer der Anfrage ist erreicht. | Nein. Wenn überhaupt, dann einmal mit demselben Idempotency-Key. |
Die SDKs bilden die Statuscodes auf Fehlerklassen ab: 400 BadRequestError, 401 AuthenticationError, 403 PermissionDeniedError, 404 NotFoundError, 409 ConflictError, 429 RateLimitError, ab 500 InternalServerError. 405 und 413 haben keine eigene Klasse. Ohne Antwort melden sie einen Verbindungsfehler oder eine Zeitüberschreitung.
Außerhalb dieses Formats: Eine fehlerhafte Kopfzeile ergibt 400 Bad Request, zu große Kopfzeilen ergeben 431, beide als Text vom HTTP-Server. Die SDKs liefern dann code und type ohne Wert.
Wiederholungen und Idempotency-Key
- Schalte die automatischen Wiederholungen des SDKs aus:
maxRetries: 0in Node,max_retries=0in Python. Sonst wiederholt das SDK nach Verbindungsfehlern und Zeitüberschreitungen von selbst, auch wenn die erste Anfrage den Modellanbieter schon erreicht hat. Ohne Idempotency-Key liefe der Aufruf dann zweimal. - Wiederhole selbst nur bei
429 rate_limit_exceededoder429 concurrency_limit_exceedednach der Zeit inRetry-Afterund bei503 service_unavailable. Andere Fehler wiederholst du nicht automatisch. - Der Kopf
Idempotency-Keyist optional. Erlaubt sind 1 bis 255 Zeichen sichtbares ASCII ohne Komma, etwa eine UUID; andere Werte ergeben400 invalid_request. Der Key gilt nur für deinen Schlüssel. - Weitere POST-Anfragen mit demselben Key:
- Der erste Aufruf läuft noch, oder sein Ausgang ist offen:
409 request_in_progress. Das gilt auch für zwei gleichzeitige Aufrufe. - Er ist abgeschlossen:
409 request_already_processed, bis eine feste Frist nach dem Abschluss verstrichen ist; danach ist der Key wieder frei. - Er scheiterte, bevor die Übergabe an den Modellanbieter begann: Der Key ist sofort wieder frei, und der neue Aufruf läuft.
409 idempotency_conflictentsteht nur bei einem Wettlauf um den Key; auch dann nicht automatisch wiederholen.
409. - Der erste Aufruf läuft noch, oder sein Ausgang ist offen:
- Die API liefert die Antwort des ersten Aufrufs nicht erneut aus. Ohne Key ist jeder POST ein eigener Aufruf.
- So sendest du den Key: in Node als Anfrageoption
headers: { "Idempotency-Key": … }(die OptionidempotencyKeysendet in openai 7.42.0 keinen Kopf), in Python alsextra_headers={"Idempotency-Key": …}, mit curl als-H "Idempotency-Key: …". - Bleibt eine Antwort aus, etwa nach einem Verbindungsabbruch, sende dieselbe Anfrage mit demselben Key einmal erneut.
409 request_in_progressoder409 request_already_processedheißt dann: Der erste Aufruf ist angekommen, seine Antwort kommt aber nicht mehr.
Grenzen
Die Zahlen nennt nur die Leistungsbeschreibung. Hier stehen Art und Fehlerbild jeder Grenze, damit jede Zahl genau eine Quelle hat.
| Grenze | Fehlerbild |
|---|---|
| Anfragen pro Minute, je Schlüssel und je Organisation; die Grenze der Organisation zählt den Chat in der App mit | 429 rate_limit_exceeded mit Retry-After |
| Token pro Minute, je Schlüssel und je Organisation; beide zählen nur Token der API | 429 rate_limit_exceeded mit Retry-After |
| Gleichzeitige Anfragen an POST /v1/chat/completions, je Schlüssel und je Organisation; der Chat in der App zählt nicht mit | 429 concurrency_limit_exceeded mit Retry-After |
| Abrufe von GET /v1/models pro Minute, je Schlüssel | 429 rate_limit_exceeded mit Retry-After |
| Abgewiesene Anfragen pro Minute, je Schlüssel: Wiederholt fehlerhafte Anfragen bremsen den Schlüssel | 429 rate_limit_exceeded mit Retry-After |
| Lastbremse vor der Anmeldung; sie kann auch durch Anfragen anderer Clients ausgelöst werden | 429 rate_limit_exceeded mit Retry-After |
| Größe des Bodys | 413 request_too_large |
| Zahl der Nachrichten und Länge aller Texte in UTF-16-Codeeinheiten | 400 context_length_exceeded |
| Eingabe-Token je Anfrage und Modell: Die API schätzt sie vor dem Aufruf nach oben ab, aus den UTF-8-Bytes aller Texte und Stoppfolgen und einem festen Zuschlag je Anfrage, Nachricht und Text. Zeichen mit mehreren Bytes, etwa Umlaute, zählen mehrfach | 400 context_length_exceeded |
| Token einer einzelnen Anfrage, also geschätzte Eingabe plus Ausgabegrenze, über der Grenze für Token pro Minute | 400 context_length_exceeded, denn Warten hilft hier nicht |
| Ausgabe-Token: Standardwert ohne max_completion_tokens und Höchstwert | über dem Höchstwert oder dem Deckel des Modells: 400 invalid_request |
| Länge des Felds user | 400 invalid_request mit param user |
| Höchstdauer einer Anfrage | JSON: 504 upstream_timeout; Stream: Fehlerereignis upstream_timeout |
| Ablauf eines Schlüssels: Standard und Höchstwert in Tagen, oder ohne Ablauf | nach dem Ablauf: 401 invalid_api_key |
| Duplikatschutz nach dem Abschluss einer Anfrage mit Idempotency-Key | 409 request_already_processed |
| Monatsgrenze des Schlüssels und API-Monatsgrenze der Organisation, beide in der App festgelegt; Nutzungsbudget und Kurzzeitbudget der Organisation | 429 insufficient_quota mit key_budget_exceeded, api_budget_exceeded, org_budget_exceeded oder short_window_exceeded, ohne Retry-After |
Die Grenzen je Minute zählen in festen Minutenfenstern; Retry-After nennt ganze Sekunden. Die Monatsgrenzen gelten je Kalendermonat in UTC.
Schlüssel verwalten
Schlüssel verwalten Inhaber und Admins in der App unter Einstellungen › Organisation › API-Zugang: anlegen, Grenzen ändern, widerrufen und die API der Organisation sperren. Ohne Freigabe oder ohne API-Monatsgrenze bleibt die API der Organisation gesperrt (403 api_access_denied). Eine API-Monatsgrenze von 0 sperrt Anfragen an POST /v1/chat/completions (429 api_budget_exceeded).
- Ablauf: ganze Tage oder „läuft nie ab“. Ohne Angabe gilt der Standardablauf; Standard und Höchstwert nennt die Leistungsbeschreibung. Nach dem Ablauf antwortet die API mit
401 invalid_api_key. - Monatsgrenze je Schlüssel: Eine Grenze von 0 sperrt seine Anfragen an
POST /v1/chat/completions(429 key_budget_exceeded). - Erlaubte Modelle: optional; ohne Auswahl gilt keine zusätzliche Einschränkung.
Schlüssel ohne Ablauf bleiben gültig bis zum Widerruf, solange keiner der unten genannten Fälle sie sperrt. Erneuere sie regelmäßig: neuen Schlüssel anlegen, Anwendung umstellen, alten widerrufen. Einen Austausch an Ort und Stelle gibt es nicht.
Der volle Schlüssel erscheint nur einmal, direkt nach dem Anlegen. Danach zeigt die App nur das Präfix. Ist er verloren, widerrufe ihn und lege einen neuen an.
Ein Widerruf gilt einzeln für einen Schlüssel und wirkt ab der nächsten Anfrage. Ein laufender Stream endet bei der nächsten Prüfung mit einem Fehlerereignis.
Jeder Schlüssel ist dem Inhaber oder Admin zugeordnet, der ihn angelegt hat. Ohne Widerruf sperren diese Fälle seine Schlüssel für neue Anfragen mit 401 invalid_api_key: Die Person verlässt die Organisation, verliert die Rolle Inhaber oder Admin, ihr Konto wird gesperrt, oder die App verlangt von ihr, die E-Mail-Adresse zu bestätigen oder die Zustimmung nachzuholen, etwa nach einer neuen Fassung der AGB. Sperrt die Organisation ihre API, antworten alle ihre Schlüssel mit 403 api_access_denied.
Brauchst du einen Schlüssel nicht mehr, widerrufe ihn. Ist ein Schlüssel offengelegt, widerrufe ihn sofort; betrifft es mehrere Schlüssel, sperre die API der Organisation.
Versionen und Änderungen
- Vertragsversion
v1-2026-10-20: Sie umfasst Felder, Fehler und Grenzen der API. - Modellkatalog
2026-10-20: Die Version steigt, wenn ein Modell aufgenommen oder gestrichen wird. - Die Beispiele folgen openai 7.42.0 für Node und openai 3.37.0 für Python. Andere Versionen können sich anders verhalten, etwa bei Wiederholungen oder Umgebungsvariablen.
Abschaltungen von Modellen und Änderungen an Endpunkten stehen auf dieser Seite mit Datum und, soweit vorhanden, Ersatz, in der Regel mindestens 30 Tage vorher. Abschaltungen und Änderungen, die ein Modellanbieter vorgibt, sowie dringende Sicherheitskorrekturen kündigen wir hier so früh wie möglich an.
Angekündigte Änderungen
Derzeit keine.
Codebeispiele
Die Beispiele sind synthetisch. Sie lesen den Schlüssel nur aus SICHERAI_API_KEY, setzen die Base URL ausdrücklich und schalten automatische Wiederholungen aus. Ersetze <modell-id> durch eine Kennung aus GET /v1/models.
# Schlüssel nur aus der Umgebung (SICHERAI_API_KEY), nie im Skript.
# 1 Modellliste
curl https://api.sicherai.com/v1/models \
-H "Authorization: Bearer $SICHERAI_API_KEY"
# 2 Antwort als JSON. Den Idempotency-Key einmal je Vorgang erzeugen und bei
# einer eigenen Wiederholung erneut senden.
IDEMPOTENCY_KEY=$(uuidgen)
curl https://api.sicherai.com/v1/chat/completions \
-H "Authorization: Bearer $SICHERAI_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-d '{
"model": "<modell-id>",
"messages": [{"role": "user", "content": "Synthetischer Testtext: Bitte antworte mit OK."}]
}'
# 3 Antwort als Stream (-N: ohne Puffer). Die Usage kommt als letzter Chunk vor
# data: [DONE]; ohne data: [DONE] ist der Stream nicht vollständig.
curl -N https://api.sicherai.com/v1/chat/completions \
-H "Authorization: Bearer $SICHERAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<modell-id>",
"messages": [{"role": "user", "content": "Synthetischer Testtext: Zähle bis drei."}],
"stream": true,
"stream_options": {"include_usage": true}
}'# Python 3, openai 3.37.0 (pip install "openai==3.37.0"). Schlüssel nur aus der Umgebung.
import os
import uuid
from openai import APIError, OpenAI
api_key = os.environ.get("SICHERAI_API_KEY")
# Ohne eigenen Wert läse die Bibliothek OPENAI_API_KEY und sendete diesen Schlüssel an die sicherAI-API.
if not api_key:
raise SystemExit("SICHERAI_API_KEY ist nicht gesetzt.")
# Die Bibliothek sendete diese Werte als Kopfzeilen mit; eine Zeile Authorization
# in OPENAI_CUSTOM_HEADERS ersetzte sogar den Schlüssel.
for name in ("OPENAI_ORG_ID", "OPENAI_PROJECT_ID", "OPENAI_CUSTOM_HEADERS"):
if os.environ.get(name):
raise SystemExit(f"{name} ist gesetzt.")
client = OpenAI(
base_url="https://api.sicherai.com/v1", # ohne base_url sendet die Bibliothek an api.openai.com
api_key=api_key,
max_retries=0, # keine automatischen Wiederholungen
)
# 1 Modellliste: eine Seite; der Abruf verbraucht keine Token.
for model in client.models.list():
print(model.id)
# 2 Antwort als JSON. Den Idempotency-Key einmal je Vorgang erzeugen;
# eine eigene Wiederholung sendet denselben Wert.
idempotency_key = str(uuid.uuid4())
try:
antwort = client.chat.completions.create(
model="<modell-id>",
messages=[{"role": "user", "content": "Synthetischer Testtext: Bitte antworte mit OK."}],
extra_headers={"Idempotency-Key": idempotency_key},
)
except APIError as fehler:
# Vertragsfehler: code und, bei HTTP-Fehlern, die Kennung aus x-request-id.
print(fehler.code, getattr(fehler, "request_id", None))
raise
print(antwort.choices[0].message.content, antwort.usage.total_tokens)
# 3 Antwort als Stream. Mit include_usage kommt die Usage als letzter Chunk mit
# choices: []; erst er zeigt, dass die Antwort vollständig ist.
stream = client.chat.completions.create(
model="<modell-id>",
messages=[{"role": "user", "content": "Synthetischer Testtext: Zähle bis drei."}],
stream=True,
stream_options={"include_usage": True},
)
usage = None
for teil in stream:
if teil.choices:
print(teil.choices[0].delta.content or "", end="")
elif teil.usage:
usage = teil.usage
if usage is None:
raise RuntimeError("Der Stream endete ohne Usage und ist unvollständig.")
print()
print(usage.total_tokens, "Token")// Node 22 (ESM), openai 7.42.0 (npm install openai@7.42.0). Schlüssel nur aus der Umgebung.
import { randomUUID } from "node:crypto";
import OpenAI, { APIError } from "openai";
const apiKey = process.env.SICHERAI_API_KEY;
// Ohne eigenen Wert läse das SDK OPENAI_API_KEY und sendete diesen Schlüssel an die sicherAI-API.
if (!apiKey) throw new Error("SICHERAI_API_KEY ist nicht gesetzt.");
// Das SDK sendete diese Kopfzeilen mit; eine Zeile Authorization ersetzte den Schlüssel.
if (process.env.OPENAI_CUSTOM_HEADERS) throw new Error("OPENAI_CUSTOM_HEADERS ist gesetzt.");
const client = new OpenAI({
baseURL: "https://api.sicherai.com/v1", // ohne baseURL sendet das SDK an api.openai.com
apiKey,
organization: null, // sonst sendete das SDK OPENAI_ORG_ID mit
project: null, // sonst sendete das SDK OPENAI_PROJECT_ID mit
maxRetries: 0, // keine automatischen Wiederholungen
});
// 1 Modellliste: eine Seite; der Abruf verbraucht keine Token.
const models = await client.models.list();
for (const model of models.data) console.log(model.id);
// 2 Antwort als JSON. Den Idempotency-Key setzt der Aufrufer als Kopfzeile;
// eine eigene Wiederholung sendet denselben Wert.
const idempotencyKey = randomUUID();
try {
const { data: completion, request_id: requestId } = await client.chat.completions
.create(
{
model: "<modell-id>",
messages: [{ role: "user", content: "Synthetischer Testtext: Bitte antworte mit OK." }],
},
{ headers: { "Idempotency-Key": idempotencyKey } },
)
.withResponse();
console.log(requestId, completion.choices[0]?.message.content, completion.usage?.total_tokens);
} catch (error) {
// Vertragsfehler: HTTP-Status, code und die Kennung aus x-request-id.
if (error instanceof APIError) console.error(error.status, error.code, error.requestID);
throw error;
}
// 3 Antwort als Stream. Mit include_usage kommt die Usage als letzter Chunk mit
// choices: []; erst er zeigt, dass die Antwort vollständig ist.
const controller = new AbortController(); // controller.abort() beendet den Stream
const stream = await client.chat.completions.create(
{
model: "<modell-id>",
messages: [{ role: "user", content: "Synthetischer Testtext: Zähle bis drei." }],
stream: true,
stream_options: { include_usage: true },
},
{ signal: controller.signal },
);
let usage = null;
for await (const chunk of stream) {
if (chunk.choices.length > 0) process.stdout.write(chunk.choices[0]?.delta.content ?? "");
else if (chunk.usage) usage = chunk.usage;
}
if (!usage) throw new Error("Der Stream endete ohne Usage und ist unvollständig.");
console.log("\n" + usage.total_tokens + " Token");