Całe API w jednym curl
Na chwilę zapomnij o SDK. Każda z poniższych przykładowych operacji wysyła to samo: ciało JSON do POST /v1/chat/completions z tokenem Bearer. Jeśli potrafisz to wysłać, masz wszystko. Pobierz klucz ze strony rejestracji (do kredytu próbnego nie są potrzebne dane do płatności), wyeksportuj go jako API_KEY i uruchom to jako pierwszy krok.
curl https://api.unrestrictedaiapi.com/v1/chat/completions \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "uncensored",
"messages": [{"role": "user", "content": "Write a two-line noir opening set in a laundromat."}],
"max_tokens": 120
}'Odpowiedź to obiekt w stylu OpenAI. Tekst znajduje się pod choices[0].message.content, a blok usage informuje Cię, ile kosztowało wywołanie w tokenach. Istnieje jeden model, uncensored, więc pole model się nie zmienia. Możesz to potwierdzić przez GET /v1/models.
curl https://api.unrestrictedaiapi.com/v1/models -H "Authorization: Bearer $API_KEY"Dwa limity, które warto zapamiętać przed napisaniem jakiegokolwiek kodu: okno kontekstu wynosi 100 000 tokenów dzielonych między prompt a completion, a max_tokens domyślnie wynosi 2048, chyba że podniesiesz tę wartość (do 32 000). Jeśli Twoja odpowiedź ciągle przerywa się w połowie zdania, to właśnie ten domyślny limit jest zazwyczaj winny.
Python z httpx
httpx daje Ci synchronicznego klienta z rozsądnymi limitami czasu i jego asynchroniczną wersję, gdy jest potrzebna. Ustaw limit czasu wyraźnie. Długie generacje mogą trwać długo, a domyślny limit pięciu sekund je przerywa.
import os
import httpx
resp = httpx.post(
"https://api.unrestrictedaiapi.com/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
json={
"model": "uncensored",
"messages": [
{"role": "system", "content": "You are a blunt, vivid fiction writer."},
{"role": "user", "content": "Pitch a heist where the vault is a night market."},
],
"temperature": 0.9,
"max_tokens": 300,
},
timeout=60.0,
)
resp.raise_for_status()
print(resp.json()["choices"][0]["message"]["content"])raise_for_status() zamienia każdy kod 4xx lub 5xx na wyjątek, co jest w porządku dla skryptu. W przypadku procesów długotrwałych opakuj wywołanie tak, aby dwa kody statusu miały szansę na ponowienie: 429 (przekroczyłeś limit 300 zapytań na minutę dla klucza) i 503 (upstream_busy, który ustaje po kilku sekundach). Wszystko inne, w tym 401, 402 i 403, nie naprawi się przy ponownym wysłaniu.
import os
import time
import httpx
def chat(messages, retries=3):
for attempt in range(retries + 1):
r = httpx.post(
"https://api.unrestrictedaiapi.com/v1/chat/completions",
headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
json={"model": "uncensored", "messages": messages, "max_tokens": 400},
timeout=60.0,
)
if r.status_code in (429, 503) and attempt < retries:
time.sleep(2 ** attempt)
continue
r.raise_for_status()
return r.json()["choices"][0]["message"]["content"]
print(chat([{"role": "user", "content": "Name five cursed objects found in a thrift store."}]))Zamień httpx.post na httpx.AsyncClient().post wewnątrz async def i masz wersję asynchroniczną z tym samym payloadem.
JavaScript z fetch
Node 18 i nowsze wersje zawierają fetch, więc nic nie musisz instalować. Zapisz plik jako .mjs (lub ustaw "type": "module"), aby działał top-level await. Przeglądarki mają ten sam interfejs API, ale nie umieszczaj klucza w kodzie frontendowym. Proxyj zapytanie przez własny backend.
const res = await fetch("https://api.unrestrictedaiapi.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "uncensored",
messages: [{ role: "user", content: "Describe a villain's morning routine in four bullets." }],
max_tokens: 250,
}),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(`HTTP ${res.status}: ${err?.error?.message ?? "unknown error"}`);
}
const data = await res.json();
console.log(data.choices[0].message.content);Zwróć uwagę na kolejność sprawdzania. fetch odrzuca tylko przy awarii sieci, więc 402 lub 403 przychodzi jako normalna odpowiedź. Sprawdź res.ok samodzielnie i odczytaj błąd JSON, który zawsze ma strukturę {"error":{"code":...,"message":...}}.
Go z net/http
Go potrzebuje kilku więcej linii, ale nie wymaga zależności. Zdefiniuj tylko pola struktury, których używasz; encoding/json zignoruje resztę. Odczyt ciała do bajtów przed sprawdzeniem statusu pozwala wypisać komunikat błędu serwera, gdy coś pójdzie nie tak.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
)
type reply struct {
Choices []struct {
Message struct {
Content string `json:"content"`
} `json:"message"`
} `json:"choices"`
}
func main() {
payload, _ := json.Marshal(map[string]any{
"model": "uncensored",
"messages": []map[string]string{
{"role": "user", "content": "Give me a ghost story in exactly three sentences."},
},
"max_tokens": 200,
})
req, _ := http.NewRequest("POST", "https://api.unrestrictedaiapi.com/v1/chat/completions", bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+os.Getenv("API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != http.StatusOK {
panic(fmt.Sprintf("HTTP %d: %s", resp.StatusCode, body))
}
var out reply
if err := json.Unmarshal(body, &out); err != nil {
panic(err)
}
fmt.Println(out.Choices[0].Message.Content)
}W produkcji zbuduj jeden http.Client{Timeout: 60 * time.Second} i go ponownie używaj. Domyślny klient nie ma ustawionego limitu czasu, co jest przyczyną tego, że utknione zapytanie staje się utknioną goroutiną.
PHP z cURL i Ruby z Net::HTTP
Rozszerzenie cURL PHP jest już na większości hostów. Zbuduj ciało z json_encode, zwróć transfer jako string, a następnie odczytaj status przez curl_getinfo. Pominięcie sprawdzenia statusu to klasyczny błąd: ciało 402 dekoduje się bez problemu jako JSON, a Twój kod zawali się na brakującym kluczu choices.
<?php
$payload = json_encode([
"model" => "uncensored",
"messages" => [
["role" => "user", "content" => "Write a villanelle opening about a broken vending machine."],
],
"max_tokens" => 200,
]);
$ch = curl_init("https://api.unrestrictedaiapi.com/v1/chat/completions");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 60,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => $payload,
]);
$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($body === false || $status !== 200) {
fwrite(STDERR, "HTTP $status: $body\n");
exit(1);
}
$data = json_decode($body, true);
echo $data["choices"][0]["message"]["content"], "\n";Na wspólnym hoście z niskim max_execution_time, trzymaj max_tokens na umiarkowanym poziomie lub przenieś wywołanie do workera kolejki.
Standardowa biblioteka wystarczy i tutaj. ENV.fetch rzuca wyjątek natychmiast, jeśli brakuje zmiennej, co jest lepsze niż wysłanie pustego tokena Bearer i debugowanie błędu 401 przez dziesięć minut.
require "net/http"
require "json"
require "uri"
uri = URI("https://api.unrestrictedaiapi.com/v1/chat/completions")
req = Net::HTTP::Post.new(uri)
req["Authorization"] = "Bearer #{ENV.fetch('API_KEY')}"
req["Content-Type"] = "application/json"
req.body = JSON.generate(
model: "uncensored",
messages: [{ role: "user", content: "Invent a tavern rumor that sounds too specific to be fake." }],
max_tokens: 200
)
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 60) { |http| http.request(req) }
abort("HTTP #{res.code}: #{res.body}") unless res.is_a?(Net::HTTPSuccess)
puts JSON.parse(res.body).dig("choices", 0, "message", "content")Jeśli później przejdziesz na Rails, to samo ciało zadziała z zadania w tle. Trzymaj wywołanie z cyklu żądania; generacje są wolniejsze niż zapytanie do bazy danych.
Oba fragmenty wysyłają dokładnie to samo ciało, co fragment Pythona, o to chodzi: jeśli jeden język działa, a inny nie, porównaj JSON, a nie klienta. Wypisz surowe ciało żądania raz i porównaj bajt po bajcie. Dziewięć razy na dziesięć winnym jest brakujący nagłówek Content-Type lub nazwa modelu wpisana z wielkiej litery.
Strumieniowanie bez bibliotek
Strumieniowanie to jedyne miejsce, gdzie zwykły HTTP prosi Cię o nieco więcej. Przy stream: true serwer odpowiada zdarzeniami wysyłanymi przez serwer: każda linia zaczyna się od data: , zawiera fragment JSON, a strumień kończy się data: [DONE]. Ostateczny fragment usage jest dodawany automatycznie, więc otrzymujesz liczbę tokenów nawet przy strumieniowaniu. Ten fragment nie ma wpisów choices, dlatego pętla poniżej sprawdza przed indeksowaniem.
import json
import os
import httpx
body = {
"model": "uncensored",
"messages": [{"role": "user", "content": "Tell a campfire story about a lighthouse keeper."}],
"stream": True,
"max_tokens": 400,
}
headers = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
with httpx.stream("POST", "https://api.unrestrictedaiapi.com/v1/chat/completions",
headers=headers, json=body, timeout=60.0) as r:
r.raise_for_status()
for line in r.iter_lines():
if not line.startswith("data: "):
continue
data = line[6:]
if data == "[DONE]":
break
chunk = json.loads(data)
if chunk.get("choices"):
print(chunk["choices"][0]["delta"].get("content") or "", end="", flush=True)
print()Ten sam wzorzec można przenieść bezpośrednio. W JavaScript odczytaj res.body za pomocą czytnika i podziel go po znakach nowej linii. W Go opakuj resp.Body w bufio.Scanner. W Ruby przekaż blok do http.request i użyj read_body. W PHP ustaw callback CURLOPT_WRITEFUNCTION. Logika się nie zmienia: usuń prefiks, zatrzymaj się na sentinelu, przeanalizuj JSON, wydrukuj deltę.
Pięciominutowy test i kontrola budżetu
Zanim zbudujesz cokolwiek na podstawie tych fragmentów, wykonaj szybki test. Po pierwsze, wywołaj /v1/models ze swoim kluczem; 200 dowodzi, że klucz i ścieżka sieciowa działają. Po drugie, wyślij najmniejsze zapytanie chat, jakie możesz, z max_tokens ustawionym na 20. Po trzecie, celowo coś zepsuj, np. pusty token bearer, i potwierdź, że Twój kod wyraźnie zwraca 401, zamiast się sypać na brakującym polu.
Następnie wykonaj obliczenia raz, aby nic Cię nie zaskoczyło. Ceny wynoszą $0,25 za milion tokenów wejściowych i $1,00 za milion tokenów wyjściowych. Dla celów ilustracyjnych załóżmy, że zapytanie wysyła 600 tokenów promptu i otrzymuje 400 tokenów odpowiedzi. To 600 x $0,25 / 1 000 000 = $0,00015 in, plus 400 x $1,00 / 1 000 000 = $0,0004 out, około $0,00055 na wywołanie. Kredyt próbny w wysokości $0,50 wystarczyłby na około 900 takich wywołań. Twoje rzeczywiste prompty będą inne, więc przeczytaj blok usage w kilku odpowiedziach i pomnóż wyniki na tej podstawie.
Kredyt próbny ważny jest przez siedem dni i nie wymaga podawania danych do płatności, co w zupełności wystarczy do przetestowania wszystkich pięciu języków. Jeśli chcesz znaleźć przypadek użycia, na który możesz się celować, przewodnik po treści NSFW pokazuje kompletny przykład. Pamiętaj, że jeden klucz należy do jednego konta; jeśli go wygenerujesz ponownie, zaktualizuj wszystkie skrypty jednocześnie, ponieważ stary klucz przestaje działać natychmiast.
Błędy wyglądające tak samo w każdym języku
Ponieważ API to zwykłe HTTP, obsługa błędów jest identyczna niezależnie od wybranego języka. Zapamiętaj tę tabelę i możesz przenieść dowolny z powyższych fragmentów kodu.
| Status | Znaczenie | Akcja |
|---|---|---|
| 400 | Błędne zapytanie, np. prompt plus max_tokens przekraczające 100k | Skróć prompt lub obniż max_tokens |
| 401 | Brakujący lub nieprawidłowy klucz | Sprawdź API_KEY; wygenerowanie nowego klucza natychmiast unieważnia stary |
| 402 | no_credit: saldo wyczerpane lub kredyt próbny wygasł | Doładować saldo przedpłacone |
| 403 | content_blocked | Nie powtarzaj zapytania; treści seksualne z udziałem małoletnich są zawsze blokowane |
| 429 | Powyżej 300 zapytań na minutę | Zwolnij i powtórz zapytanie |
| 503 | upstream_busy | Ponów zapytanie za kilka sekund |
Gdy podstawy zadziałają, ustaw stream: true dla wyjścia token po tokenie i czytaj zdarzenia wysyłane przez serwer (SSE) linia po linii. Stwórz lepsze prompty z poradnikiem promptowania lub podłącz to samo wywołanie do platformy czatu, korzystając z przewodnika po botach. Stawki znajdziesz na stronie z cenami.