Jeden adres bazowy. Każdy model.
Skieruj dowolne SDK OpenAI na unbottled.ai. 202 modeli, streaming, wywoływanie narzędzi. Twój klucz, twoje kredyty, żadnego nowego klienta do nauki.
Pierwsze wywołanie w 60 sekund
API mówi formatem OpenAI. Jeśli twój kod działa z OpenAI, zadziała i tu po dwóch zmianach: adres bazowy i klucz.
Weź klucz
Utwórz klucz sk-unb w Ustawienia > Klucze API. Widać go tylko raz. API jest częścią każdego płatnego planu: odblokowuje je aktywna subskrypcja albo zakup pakietu kredytów.
W ścieżce możesz użyć dowolnego języka (en-US, de-DE, pl-PL). Zmienia tylko język komunikatów o błędach.
Wyślij zapytanie
Czyste HTTP. Działa w każdym języku.
curl http://localhost:3000/api/pl-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?" }
]
}'Albo użyj SDK OpenAI
Zmień baseURL i apiKey. Nic więcej.
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:3000/api/pl-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)import OpenAI from "openai";
const client = new OpenAI({
baseURL: "http://localhost:3000/api/pl-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);Uwierzytelnianie
Wysyłaj klucz w każdym zapytaniu jako token Bearer. Klucze zaczynają się od sk-unb-.
Authorization: Bearer sk-unb-TWOJ_KLUCZPrzechowujemy hash, nie klucz. Zgubisz go, tworzysz nowy.
Klucz działa tylko na /v1. Nie dotknie konta, płatności ani ustawień.
Do 20 aktywnych kluczy na konto. Każdy klucz możesz unieważnić w dowolnej chwili: przestaje działać natychmiast.
Brak klucza albo klucz unieważniony zwraca 401. Klucz konta bez płatnego planu zwraca 402.
GET /v1/models
Zwraca modele, które twój klucz może teraz wywołać, z rozmiarem kontekstu, obsługą narzędzi i ceną w kredytach za milion tokenów. Użyj id jako pola model w zapytaniach.
curl http://localhost:3000/api/pl-PL/v1/models \
-H "Authorization: Bearer sk-unb-YOUR_KEY"{
"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
}
}
]
}Lista zależy od twojego konta. Nieznane lub niedostępne id zwracają 404 model_not_found. API nigdy po cichu nie podmienia modelu.
POST /v1/chat/completions
Standardowe chat completions w formacie OpenAI. Wiadomości na wejściu, jedna wiadomość asystenta na wyjściu. Wejście tekstowe i obrazy (image_url jako adres https albo base64 data URL).
Bez streamingu
Tryb domyślny. Dostajesz jedno ciało JSON, gdy model skończy. usage.credits_charged pokazuje, ile kosztowało wywołanie.
curl http://localhost:3000/api/pl-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?" }
]
}'{
"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
Z stream: true dostajesz zdarzenia server-sent w formacie chunków OpenAI, zakończone przez data: [DONE]. Dodaj stream_options.include_usage, by dostać ostatni chunk z liczbą tokenów i credits_charged. Zamknięcie połączenia zatrzymuje generowanie. Płacisz tylko za to, co się wykonało.
curl -N http://localhost:3000/api/pl-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." }]
}'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]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="")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 ?? "");
}Pola zapytania
Wywoływanie narzędzi
Function calling działa jak w OpenAI. Opisujesz funkcje, model decyduje się na wywołanie, ty je wykonujesz i odsyłasz wynik. Twoich narzędzi nigdy nie uruchamiamy.
Pętla
Wyślij tools razem z wiadomościami.
Model odpowiada z tool_calls i finish_reason: tool_calls. Każde wywołanie ma id, nazwę funkcji i argumenty jako string JSON.
Wykonaj funkcję samodzielnie. Dołącz wiadomość asystenta, a potem po jednej wiadomości tool na wywołanie, z jej tool_call_id.
Wywołaj ponownie. Model użyje wyników i odpowie.
1. Zapytanie z narzędziami
curl http://localhost:3000/api/pl-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. Model prosi o wywołanie
{
"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. Odeślij wynik
# Run get_weather yourself, then send the result back:
curl http://localhost:3000/api/pl-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... ]
}'Ta sama pętla w SDK Pythona
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)Wywołania narzędzi w streamie
Z stream: true wywołania przychodzą jako chunki delta.tool_calls, adresowane przez index. Pierwszy chunk danego indeksu niesie id i function.name. Kolejne dopisują do function.arguments. Złóż je w całość i sparsuj JSON, gdy finish_reason to tool_calls. Każde SDK OpenAI robi to za ciebie.
Model musi obsługiwać narzędzia: sprawdź supports_tools w GET /v1/models. Pozostałe zwracają 400 tools_not_supported.
tool_choice none, auto i required działają jak w OpenAI. Funkcja spoza tools zwraca 400.
Nazwy funkcji: litery, cyfry, podkreślenie, myślnik, do 64 znaków.
Definicje narzędzi i ich wyniki to tokeny wejściowe. Wywołania to tokeny wyjściowe. Rozliczane są jak wszystkie inne tokeny.
Kredyty i rozliczenia
API czerpie z tych samych kredytów co czat, po tych samych cenach. Jedno saldo, jeden rachunek.
Koszt = tokeny wejściowe plus wyjściowe po cenie modelu w kredytach. Ceny za milion tokenów podaje GET /v1/models.
Każda odpowiedź zawiera usage.credits_charged. Saldo znajdziesz w aplikacji w zakładce Kredyty.
Przed wywołaniem porównujemy saldo z oszacowaniem na najgorszy przypadek. Za mało: HTTP 402 insufficient_credits, nic się nie wykonuje, nic nie jest pobierane.
Po wywołaniu płacisz za rzeczywiste zużycie tokenów zgłoszone przez dostawcę, nie za oszacowanie.
Skończyły się kredyty? Kup pakiet albo odnów plan. Następne wywołanie działa od razu.
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"
}
}Limity zapytań
Każdy klucz domyślnie pozwala na 60 zapytań na minutę. Powyżej limitu dostajesz HTTP 429 z nagłówkiem Retry-After w sekundach. Odczekaj tyle i spróbuj ponownie. Limity są na klucz, więc większe obciążenia rozłóż na kilka kluczy.
HTTP/1.1 429 Too Many Requests
Retry-After: 12
{ "error": { "type": "rate_limit_error", "code": "rate_limit_exceeded", ... } }Błędy
Błędy mają kopertę OpenAI, więc SDK zgłaszają swoje zwykłe wyjątki. Opieraj się na error.code, nie na treści komunikatu.
{
"error": {
"message": "Invalid or revoked API key. ...",
"type": "authentication_error",
"param": null,
"code": "invalid_api_key"
}
}