OpenAI-kompatible API

Eine Base-URL. Jedes Modell.

Richte ein beliebiges OpenAI-SDK auf unbottled.ai. 202 Modelle, Streaming, Tool-Aufrufe. Dein Schlüssel, deine Credits, kein neuer Client.

Schnellstart

In 60 Sekunden zum ersten Aufruf

Die API spricht das OpenAI-Format. Läuft dein Code gegen OpenAI, läuft er auch hier, nach zwei Änderungen: Base-URL und Schlüssel.

1

Schlüssel holen

Erstelle unter Einstellungen > API-Schlüssel einen sk-unb-Schlüssel. Er wird nur einmal angezeigt. Die API gehört zu jedem bezahlten Tarif: ein aktives Abo oder ein gekauftes Credit-Paket schaltet sie frei.

Base-URL: http://localhost:3000/api/de-PL/v1

Jede Sprache im Pfad funktioniert (en-US, de-DE, pl-PL). Sie bestimmt nur die Sprache der Fehlermeldungen.

2

Anfrage senden

Reines HTTP. Funktioniert in jeder Sprache.

curl
curl http://localhost:3000/api/de-PL/v1/chat/completions \
  -H "Authorization: Bearer sk-unb-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kimi-k2",
    "messages": [
      { "role": "system", "content": "You answer in one sentence." },
      { "role": "user", "content": "Why is the sky blue?" }
    ]
  }'
3

Oder ein OpenAI-SDK nutzen

baseURL und apiKey ändern. Mehr nicht.

Python
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:3000/api/de-PL/v1",
    api_key="sk-unb-YOUR_KEY",
)

reply = client.chat.completions.create(
    model="kimi-k2",
    messages=[{"role": "user", "content": "Why is the sky blue?"}],
)
print(reply.choices[0].message.content)
Node.js
import OpenAI from "openai";

const client = new OpenAI({
  baseURL: "http://localhost:3000/api/de-PL/v1",
  apiKey: process.env.UNBOTTLED_API_KEY, // sk-unb-YOUR_KEY
});

const reply = await client.chat.completions.create({
  model: "kimi-k2",
  messages: [{ role: "user", content: "Why is the sky blue?" }],
});
console.log(reply.choices[0].message.content);
Nur mit bezahltem Tarif. Schlüssel erstellen und /v1 aufrufen geht nur mit bezahltem Konto: aktives Abo oder mindestens ein Credit-Kauf. Konten im kostenlosen Tarif bekommen HTTP 402 mit dem Code paid_plan_required, die Doku können sie trotzdem lesen. Deine Credits bleiben dein: Einmal ein Paket kaufen, und die API läuft, bis das Guthaben aufgebraucht ist.
Authentifizierung

Authentifizierung

Sende deinen Schlüssel bei jeder Anfrage als Bearer-Token. Schlüssel beginnen mit sk-unb-.

header
Authorization: Bearer sk-unb-DEIN_SCHLUESSEL

Wir speichern einen Hash, nicht den Schlüssel. Verloren heißt: neuen erstellen.

Ein Schlüssel gilt nur für /v1. Auf Konto, Zahlungen oder Einstellungen kommt er nicht.

Bis zu 20 aktive Schlüssel pro Konto. Jeden Schlüssel kannst du jederzeit widerrufen: Er stoppt sofort.

Fehlender oder widerrufener Schlüssel liefert 401. Ein Schlüssel, dessen Konto keinen bezahlten Tarif hat, liefert 402.

Modelle

GET /v1/models

Listet die Modelle, die dein Schlüssel jetzt aufrufen kann, mit Kontextgröße, Tool-Unterstützung und Credit-Preisen pro Million Token. Die id nutzt du als model-Feld.

request
curl http://localhost:3000/api/de-PL/v1/models \
  -H "Authorization: Bearer sk-unb-YOUR_KEY"
200
{
  "object": "list",
  "data": [
    {
      "id": "kimi-k2",
      "object": "model",
      "created": 1735689600,
      "owned_by": "moonshot",
      "name": "Kimi K2",
      "context_length": 131072,
      "supports_tools": true,
      "input_modalities": ["text"],
      "output_modalities": ["text"],
      "pricing": {
        "input_credits_per_million_tokens": 8,
        "output_credits_per_million_tokens": 32
      }
    }
  ]
}

Die Liste hängt von deinem Konto ab. Unbekannte oder nicht verfügbare IDs liefern 404 model_not_found. Die API tauscht dein Modell nie heimlich aus.

Chat Completions

POST /v1/chat/completions

Standard-Chat-Completions im OpenAI-Format. Nachrichten rein, eine Assistenten-Nachricht raus. Text- und Bildeingaben (image_url als https-URL oder base64-Data-URL).

Ohne Streaming

Der Standard. Du bekommst einen JSON-Body, sobald das Modell fertig ist. usage.credits_charged zeigt, was der Aufruf gekostet hat.

request
curl http://localhost:3000/api/de-PL/v1/chat/completions \
  -H "Authorization: Bearer sk-unb-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kimi-k2",
    "messages": [
      { "role": "system", "content": "You answer in one sentence." },
      { "role": "user", "content": "Why is the sky blue?" }
    ]
  }'
200
{
  "id": "chatcmpl-3f9c1a...",
  "object": "chat.completion",
  "created": 1767225600,
  "model": "kimi-k2",
  "choices": [
    {
      "index": 0,
      "message": { "role": "assistant", "content": "Air scatters blue light more than red." },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 31,
    "completion_tokens": 9,
    "total_tokens": 40,
    "credits_charged": 0.1
  }
}

Streaming

Mit stream: true kommen Server-Sent-Events im OpenAI-Chunk-Format, abgeschlossen durch data: [DONE]. Mit stream_options.include_usage erhältst du einen letzten Chunk mit Token-Zahlen und credits_charged. Schließt du die Verbindung, stoppt die Generierung. Du zahlst nur, was lief.

request
curl -N http://localhost:3000/api/de-PL/v1/chat/completions \
  -H "Authorization: Bearer sk-unb-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kimi-k2",
    "stream": true,
    "stream_options": { "include_usage": true },
    "messages": [{ "role": "user", "content": "Count to three." }]
  }'
200
data: {"id":"chatcmpl-3f9c...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}

data: {"id":"chatcmpl-3f9c...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"One, "},"finish_reason":null}]}

data: {"id":"chatcmpl-3f9c...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

data: {"id":"chatcmpl-3f9c...","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":12,"completion_tokens":7,"total_tokens":19,"credits_charged":0.1}}

data: [DONE]
Python
stream = client.chat.completions.create(
    model="kimi-k2",
    messages=[{"role": "user", "content": "Count to three."}],
    stream=True,
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")
Node.js
const stream = await client.chat.completions.create({
  model: "kimi-k2",
  messages: [{ role: "user", content: "Count to three." }],
  stream: true,
});
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
}

Anfragefelder

FeldWirkung
modelPflicht. Eine Modell-ID aus GET /v1/models.
messagesPflicht. system-, developer-, user-, assistant- und tool-Nachrichten. User-Inhalt darf ein String sein oder text- und image_url-Teile enthalten.
streamtrue für Server-Sent-Events. Standard false.
stream_options{ include_usage: true } hängt einen letzten Usage-Chunk an.
max_tokensObergrenze für erzeugte Token. max_completion_tokens hat Vorrang. Gedeckelt bei 32768.
temperature0 bis 2. Wird von Modellen ohne Unterstützung ignoriert.
top_p0 bis 1.
stopEin Stopp-String oder eine Liste davon.
nNur 1 wird unterstützt. Andere Werte liefern 400.
toolsFunktions-Tools, die das Modell aufrufen darf. Siehe Tool-Aufrufe.
tool_choicenone, auto, required oder eine benannte Funktion.
Tool-Aufrufe

Tool-Aufrufe

Function Calling funktioniert wie bei OpenAI. Du beschreibst Funktionen, das Modell entscheidet sich für einen Aufruf, du führst ihn aus und schickst das Ergebnis zurück. Deine Tools führen wir nie aus.

Der Ablauf

Schicke tools zusammen mit deinen Nachrichten.

Das Modell antwortet mit tool_calls und finish_reason: tool_calls. Jeder Aufruf hat eine id, einen Funktionsnamen und Argumente als JSON-String.

Führe die Funktion selbst aus. Hänge die Assistenten-Nachricht an, danach pro Aufruf eine tool-Nachricht mit der passenden tool_call_id.

Rufe erneut auf. Das Modell nutzt die Ergebnisse und antwortet.

1. Anfrage mit Tools

request
curl http://localhost:3000/api/de-PL/v1/chat/completions \
  -H "Authorization: Bearer sk-unb-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kimi-k2",
    "messages": [{ "role": "user", "content": "Weather in Berlin?" }],
    "tools": [{
      "type": "function",
      "function": {
        "name": "get_weather",
        "description": "Current weather for a city",
        "parameters": {
          "type": "object",
          "properties": { "city": { "type": "string" } },
          "required": ["city"]
        }
      }
    }],
    "tool_choice": "auto"
  }'

2. Das Modell verlangt einen Aufruf

200
{
  "object": "chat.completion",
  "model": "kimi-k2",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call_8x2k",
            "type": "function",
            "function": { "name": "get_weather", "arguments": "{\"city\":\"Berlin\"}" }
          }
        ]
      },
      "finish_reason": "tool_calls"
    }
  ]
}

3. Ergebnis zurückgeben

request
# Run get_weather yourself, then send the result back:
curl http://localhost:3000/api/de-PL/v1/chat/completions \
  -H "Authorization: Bearer sk-unb-YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "kimi-k2",
    "messages": [
      { "role": "user", "content": "Weather in Berlin?" },
      { "role": "assistant", "content": null, "tool_calls": [
        { "id": "call_8x2k", "type": "function",
          "function": { "name": "get_weather", "arguments": "{\"city\":\"Berlin\"}" } }
      ]},
      { "role": "tool", "tool_call_id": "call_8x2k", "content": "18C, cloudy" }
    ],
    "tools": [ ...same tools as before... ]
  }'

Derselbe Ablauf mit dem Python-SDK

Python
import json

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Current weather for a city",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]
messages = [{"role": "user", "content": "Weather in Berlin?"}]

first = client.chat.completions.create(
    model="kimi-k2", messages=messages, tools=tools
)
call = first.choices[0].message.tool_calls[0]
result = get_weather(**json.loads(call.function.arguments))  # your code

messages += [
    first.choices[0].message,
    {"role": "tool", "tool_call_id": call.id, "content": result},
]
final = client.chat.completions.create(
    model="kimi-k2", messages=messages, tools=tools
)
print(final.choices[0].message.content)

Tool-Aufrufe im Stream

Mit stream: true kommen Aufrufe als delta.tool_calls-Chunks, adressiert über index. Der erste Chunk eines Index trägt id und function.name. Spätere Chunks hängen an function.arguments an. Setze sie zusammen und parse das JSON, sobald finish_reason tool_calls ist. Jedes OpenAI-SDK erledigt das für dich.

Regeln

Das Modell muss Tools unterstützen: Prüfe supports_tools in GET /v1/models. Andere liefern 400 tools_not_supported.

tool_choice none, auto und required funktionieren wie bei OpenAI. Eine Funktion, die nicht in tools steht, liefert 400.

Funktionsnamen: Buchstaben, Ziffern, Unterstrich, Bindestrich, bis zu 64 Zeichen.

Tool-Definitionen und Tool-Ergebnisse sind Eingabe-Token. Aufrufe sind Ausgabe-Token. Sie werden wie alle anderen Token abgerechnet.

Credits und Abrechnung

Credits und Abrechnung

Die API zieht von denselben Credits wie die Chat-App, zu denselben Preisen. Ein Guthaben, eine Rechnung.

Kosten = Eingabe- plus Ausgabe-Token zum Credit-Preis des Modells. Die Preise pro Million Token stehen in GET /v1/models.

Jede Antwort nennt usage.credits_charged. Dein Guthaben findest du in der App unter Credits.

Vor jedem Aufruf prüfen wir dein Guthaben gegen eine Worst-Case-Schätzung. Reicht es nicht: HTTP 402 insufficient_credits, nichts läuft, nichts wird berechnet.

Nach dem Aufruf zahlst du die echte Token-Nutzung, die der Anbieter meldet, nicht die Schätzung.

Credits leer? Kaufe ein Paket oder verlängere deinen Tarif. Der nächste Aufruf läuft sofort.

402
HTTP/1.1 402 Payment Required
{
  "error": {
    "message": "Insufficient credits. This call needs about 1.2, you have 0.4. Top up to continue.",
    "type": "insufficient_quota",
    "param": null,
    "code": "insufficient_credits"
  }
}
Rate Limits

Rate Limits

Jeder Schlüssel erlaubt standardmäßig 60 Anfragen pro Minute. Darüber liefert die API HTTP 429 mit einem Retry-After-Header in Sekunden. Warte so lange und versuche es erneut. Limits gelten pro Schlüssel: Verteile große Lasten auf mehrere Schlüssel.

429
HTTP/1.1 429 Too Many Requests
Retry-After: 12
{ "error": { "type": "rate_limit_error", "code": "rate_limit_exceeded", ... } }
Fehler

Fehler

Fehler nutzen den OpenAI-Umschlag, die SDKs werfen also ihre gewohnten Exceptions. Werte error.code aus, nicht die Meldung.

error
{
  "error": {
    "message": "Invalid or revoked API key. ...",
    "type": "authentication_error",
    "param": null,
    "code": "invalid_api_key"
  }
}
HTTP / codeBedeutung
401 missing_api_keyKein Authorization-Header. Sende Bearer sk-unb-...
401 invalid_api_keySchlüssel ist falsch oder widerrufen.
402 paid_plan_requiredDas Konto hat keinen bezahlten Tarif. Abonniere oder kaufe Credits.
402 insufficient_creditsGuthaben reicht für diesen Aufruf nicht. Auffüllen.
402 free_tier_cap_reachedTageslimit des kostenlosen Tarifs erreicht. Credits lösen das.
404 model_not_foundDas Modell existiert nicht oder steht dir nicht zur Verfügung.
400 invalid_value / unsupported_parameter / tools_not_supportedUngültige Anfrage: nicht unterstützter Parameter, falsche tool_choice, falsche Bild-URL.
429 rate_limit_exceededÜber 60 Anfragen pro Minute für diesen Schlüssel. Lies Retry-After.
502 upstream_errorDer Modellanbieter ist ausgefallen. Wiederholen ist sicher. Streams enden mit einem Fehler-Event.
503 balance_unavailableDein Guthaben war nicht lesbar, deshalb lief nichts. Wiederholen.
Fehler mitten im Stream kommen als data:-Event mit demselben Fehlerobjekt, gefolgt von data: [DONE].

Bau auf jedem Modell.

Schlüssel erstellen, SDK umstellen, ausliefern.