Die gesamte API in einem curl
Vergiss das SDK für einen Moment. Jedes Beispiel unten sendet dasselbe: einen JSON-Body an POST /v1/chat/completions mit einem Bearer-Token. Wenn du das senden kannst, bist du fertig. Hol dir einen Schlüssel von der Registrierungsseite (das Testguthaben benötigt keine Zahlungsdaten), exportiere ihn als API_KEY und führe dies aus.
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
}'Die Antwort ist ein OpenAI-Style-Objekt. Der Text liegt unter choices[0].message.content, und ein usage-Block zeigt dir, was der Aufruf in Token gekostet hat. Es gibt nur ein Modell, uncensored, daher ändert sich das Feld model nie. Das kannst du mit GET /v1/models bestätigen.
curl https://api.unrestrictedaiapi.com/v1/models -H "Authorization: Bearer $API_KEY"Zwei Limits, die du dir merken solltest, bevor du Code schreibst: Das Kontextfenster umfasst 100.000 Token, die sich auf Prompt und Completion verteilen, und max_tokens hat standardmäßig den Wert 2048, es sei denn, du erhöhst ihn (bis zu 32.000). Wenn deine Ausgabe mitten im Satz abbricht, ist dies der übliche Grund.
Python mit httpx
httpx bietet dir einen synchronen Client mit vernünftigen Timeouts und einen asynchronen Zwilling, wenn du ihn brauchst. Setze das Timeout explizit. Lange Generierungen können dauern, und das Standard-Timeout von fünf Sekunden wird sie abschneiden.
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() wandelt 4xx- oder 5xx-Fehler in eine Exception um, was für ein Skript in Ordnung ist. Für langlaufende Prozesse solltest du den Aufruf so umschließen, dass zwei Statuscodes eine weitere Chance erhalten: 429 (du hast das pro Schlüssel begrenzte Limit von 300 Anfragen pro Minute erreicht) und 503 (upstream_busy, das sich nach wenigen Sekunden von selbst löst). Alles andere, einschließlich 401, 402 und 403, behebt sich bei einem erneuten Versuch nicht von selbst.
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."}]))Tausche httpx.post gegen httpx.AsyncClient().post innerhalb eines async def aus, und du hast die asynchrone Version mit derselben Payload.
JavaScript mit fetch
Node 18 und neuer liefern fetch mit aus, also musst du nichts installieren. Speichere die Datei als .mjs (oder setze "type": "module"), damit top-level await funktioniert. Browser haben dieselbe API, aber gib den Schlüssel nicht in Frontend-Code ein. Proxye den Aufruf über dein eigenes 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);Beachte die Reihenfolge der Prüfungen. fetch löst nur bei Netzwerkfehlern aus, daher kommt ein 402 oder 403 als normale Antwort an. Prüfe res.ok selbst und lies den Fehler-JSON, der immer die Struktur {"error":{"code":...,"message":...}} hat.
Go mit net/http
Go benötigt ein paar Zeilen mehr, aber keine Abhängigkeiten. Definiere nur die Struct-Felder, die du liest; encoding/json ignoriert den Rest. Das Lesen des Bodies in Bytes vor der Statusprüfung ermöglicht es dir, die Fehlermeldung des Servers bei einem Fehler auszugeben.
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)
}Erstelle für die Produktion einen http.Client{Timeout: 60 * time.Second} und verwende ihn wieder. Der Standard-Client hat überhaupt kein Timeout, was dazu führt, dass eine hängende Anfrage zu einem hängenden Goroutine wird.
PHP mit cURL und Ruby mit Net::HTTP
Die cURL-Erweiterung von PHP ist auf den meisten Hosts bereits vorhanden. Erstelle den Body mit json_encode, gib die Übertragung als String zurück und lies den Status mit curl_getinfo. Das Überspringen der Statusprüfung ist der klassische Fehler: Ein 402-Body lässt sich problemlos als JSON decodieren, und dein Code scheitert dann an einem fehlenden choices-Schlüssel.
<?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";Halte auf einem Shared Host mit einem niedrigen max_execution_time max_tokens moderat oder verschiebe den Aufruf in einen Queue-Worker.
Auch hier reicht die Standardbibliothek. ENV.fetch löst sofort aus, wenn die Variable fehlt, was besser ist, als ein leeres Bearer-Token zu senden und zehn Minuten lang einen 401 zu debuggen.
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")Wenn du später zu Rails wechselst, funktioniert derselbe Körper aus einem Hintergrundjob. Halte den Aufruf außerhalb des Anfragezyklus; Generierungen sind langsamer als eine Datenbankabfrage.
Beide Snippets senden genau den Körper, den das Python-Snippet sendet, was der Punkt ist: Wenn eine Sprache funktioniert und eine andere nicht, diff den JSON, nicht den Client. Drucke den rohen Anfragekörper einmal aus und vergleiche Byte für Byte. Neun von zehn Mal ist der Übeltäter ein fehlender Content-Type-Header oder ein Modellname, der mit einem Großbuchstaben getippt wurde.
Streaming ohne Bibliothek
Streaming ist der einzige Bereich, in dem plain HTTP etwas mehr von dir verlangt. Mit stream: true antwortet der Server mit Server-Sent Events: Jede Zeile beginnt mit data: , enthält ein JSON-Chunk und der Stream endet mit data: [DONE]. Ein finaler Usage-Chunk wird automatisch hinzugefügt, sodass du Token-Anzahlen auch beim Streaming erhältst. Dieser Chunk hat keine choices-Einträge, weshalb die Schleife unten vor dem Indizieren prüft.
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()Dieses Muster lässt sich direkt portieren. Lies in JavaScript res.body mit einem Reader und teile es an Newlines. Wickle in Go resp.Body in einen bufio.Scanner. Übergebe in Ruby einen Block an http.request und verwende read_body. Setze in PHP einen CURLOPT_WRITEFUNCTION-Callback. Die Logik ändert sich nie: Entferne das Präfix, stoppe am Sentinel, parse den JSON, gib das Delta aus.
Ein fünfminütiger Smoke-Test und Budget-Check
Bevor du auf einem dieser Snippets aufbaust, führe eine schnelle Sanity-Prüfung durch. Rufe zuerst /v1/models mit deinem Schlüssel auf; ein 200er-Status beweist, dass Schlüssel und Netzwerkpfad funktionieren. Sende zweitens die kleinstmögliche Chat-Anfrage mit auf max_tokens gesetztem Wert 20. Breche drittens bewusst etwas, z. B. ein leeres Bearer-Token, und stelle sicher, dass dein Code den 401 klar ausgibt, anstatt an einem fehlenden Feld abzustürzen.
Mache dann einmal die Rechnung, damit dich nichts überrascht. Die Preise liegen bei 0,25 $ pro Million Input-Token und 1,00 $ pro Million Output-Token. Zur Veranschaulichung gehen wir davon aus, dass eine Anfrage 600 Prompt-Token sendet und 400 Token zurückbekommt. Das sind 600 x 0,25 $ / 1.000.000 = 0,00015 $ rein plus 400 x 1,00 $ / 1.000.000 = 0,0004 $ raus, also etwa 0,00055 $ pro Aufruf. Das Testguthaben von 0,50 $ würde etwa 900 Aufrufe dieser Größe abdecken. Deine echten Prompts werden abweichen, also lies den usage-Block bei einigen Antworten und multipliziere damit.
Das Testguthaben gilt sieben Tage und erfordert keine Zahlungsdaten, was für Tests aller fünf Sprachen mehr als ausreicht. Für einen Anwendungsfall zeigt der NSFW-Inhaltsleitfaden ein vollständiges Beispiel. Merke: Ein Schlüssel gehört zu einem Konto; bei einer Erneuerung aktualisiere alle Skripte gleichzeitig, da der alte Schlüssel sofort ungültig wird.
Fehler, die in jeder Sprache gleich aussehen
Da die API plain HTTP ist, ist die Fehlerbehandlung in jeder Sprache identisch. Lerne diese Tabelle auswendig, und du kannst jedes Code-Snippet oben portieren.
| Status | Bedeutung | Aktion |
|---|---|---|
| 400 | Fehlerhafte Anfrage, z. B. Prompt plus max_tokens über 100k | Kürze den Prompt oder senke max_tokens |
| 401 | Fehlender oder ungültiger Schlüssel | Prüfe API_KEY; ein neu generierter Schlüssel macht den alten sofort ungültig |
| 402 | no_credit: Guthaben aufgebraucht oder Testguthaben abgelaufen | Lade das Prepaid-Guthaben auf |
| 403 | content_blocked | Keine Wiederholung; sexuelle Inhalte mit Minderjährigen werden immer blockiert |
| 429 | Mehr als 300 Anfragen pro Minute | Warte kurz und versuche es erneut |
| 503 | upstream_busy | Warte ein paar Sekunden und versuche es erneut |
Sobald die Basics funktionieren, setze stream: true für Token-für-Token-Ausgaben und lese die Server-Sent-Events zeilenweise. Verbessere deine Prompts mit dem Prompting-Guide oder binde denselben Aufruf in eine Chat-Plattform ein mit der Bot-Anleitung. Die Preise findest du auf der Preisliste.