De hele API in één curl
Verget de SDK even. Elk voorbeeld hieronder stuurt hetzelfde: een JSON body naar POST /v1/chat/completions met een draagtoken. Als je dat kunt, ben je klaar. Haal een sleutel van de aanmeldpagina (het gratis proeftegoed vereist geen betaalgegevens), exporteer het als API_KEY en voer dit eerst uit.
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
}'Het antwoord is een OpenAI-stijl object. De tekst staat bij choices[0].message.content, en een usage blok vertelt wat de aanroep kostte in tokens. Er is één model, uncensored, dus het model veld verandert nooit. Dit bevestig je met GET /v1/models.
curl https://api.unrestrictedaiapi.com/v1/models -H "Authorization: Bearer $API_KEY"Twee limieten die je moet onthouden voordat je code schrijft: het contextvenster is 100.000 tokens, gedeeld tussen prompt en antwoord, en max_tokens staat standaard op 2048, tenzij je het verhoogt (tot 32.000). Als je output stopt in de zin, is deze standaardwaarde de boosdoener.
Python met httpx
httpx geeft je een synchronische client met verstandige time-outs en een async variant als je die nodig hebt. Stel de time-out expliciet in. Lange generaties kunnen even duren en de standaard van vijf seconden stopt ze.
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() zet elke 4xx of 5xx om in een uitzondering, wat prima is voor een script. Voor langlopende taken wrap je de aanroep zodat twee statuscodes een tweede kans krijgen: 429 (je raakt de limiet van 300 verzoeken per minuut per sleutel) en 503 (upstream_busy, wat binnen enkele seconden oplost). Alles anders, inclusief 401, 402 en 403, lost zichzelf niet op bij retry.
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."}]))Vervang httpx.post door httpx.AsyncClient().post binnen een async def en je hebt de async versie met dezelfde payload.
JavaScript met fetch
Node 18 en nieuwer leveren fetch, dus er is niets te installeren. Sla het bestand op als .mjs (of stel "type": "module" in) zodat top-level await werkt. Browsers hebben dezelfde API, maar zet de sleutel niet in frontend code. Proxy de aanroep via je eigen 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);Let op de volgorde van controles. fetch faalt alleen bij netwerkfouten, dus een 402 of 403 komt binnen als normaal antwoord. Test res.ok zelf en lees de fout-JSON, die altijd de structuur {"error":{"code":...,"message":...}} heeft.
Go met net/http
Go vereist een paar regels meer maar geen afhankelijkheden. Definieer alleen de structvelden die je leest; encoding/json negeert de rest. Het lichaam in bytes lezen voordat je de status controleert, stelt je in staat het foutbericht van de server af te drukken als er iets misgaat.
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)
}Voor productie bouw je één http.Client{Timeout: 60 * time.Second} en hergebruik je deze. De standaard client heeft helemaal geen time-out, waardoor een vastgelopen verzoek een vastgelopen goroutine wordt.
PHP met cURL en Ruby met Net::HTTP
De cURL-extensie van PHP staat op de meeste hosts al. Bouw de request body met json_encode, retourneer de transfer als string en lees de status met curl_getinfo. De statuscontrole overslaan is de klassieke fout: een 402 body decodeert gelukkig als JSON en je code faalt dan op een ontbrekend choices veld.
<?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";Op een gedeelde host met een lage max_execution_time, houd max_tokens bescheiden of verplaats de aanroep naar een wachtrij-worker.
De standaard bibliotheek is hier ook genoeg. ENV.fetch gooit direct een fout als de variabele ontbreekt, wat beter is dan een leeg draagtoken sturen en tien minuten debuggen voor een 401.
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")Als je later naar Rails verhuist, werkt dezelfde body vanuit een achtergrondtaak. Houd de aanroep buiten de request cyclus; generaties zijn langzamer dan een database query.
Beide snippets sturen precies de body die de Python versie stuurt, wat het punt is: als één taal werkt en een andere niet, diff de JSON, niet de client. Print de raw request body één keer en vergelijk byte voor byte. Negen keer op de tien is de boosdoener een ontbrekende Content-Type header of een model name die met een hoofdletter is getypt.
Streaming zonder library
Streaming is de enige plek waar plain HTTP je iets meer werk vraagt. Met stream: true antwoordt de server met server-sent events: elke regel begint met data: , draagt een JSON chunk, en de stream eindigt met data: [DONE]. Een laatste usage chunk wordt automatisch toegevoegd, dus je krijgt token counts zelfs bij streaming. Die chunk heeft geen choices entries, waarom de loop hieronder checkt voordat hij indexeert.
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()Hetzelfde patroon porteer je direct. In JavaScript, lees res.body met een reader en split op newlines. In Go, wrap resp.Body in een bufio.Scanner. In Ruby, geef een block door aan http.request en gebruik read_body. In PHP, stel een CURLOPT_WRITEFUNCTION callback in. De logica verandert nooit: strip de prefix, stop bij de sentinel, parse de JSON, print de delta.
Een vijf minuten smoke test en een budget check
Voordat je bovenop een van deze snippets bouwt, voer een snelle sanity pass uit. Roep eerst /v1/models aan met je sleutel; een 200 bewijst de sleutel en netwerk pad. Stuur ten tweede het kleinste chat verzoek dat je kunt met max_tokens ingesteld op 20. Breek ten derde iets opzettelijk, zoals een lege bearer token, en bevestig dat je code de 401 duidelijk toont in plaats van crashen op een ontbrekend veld.
Doe de rekenkunde dan één keer zodat niets je verrast. Prijzen zijn $0,25 per miljoen input tokens en $1,00 per miljoen output tokens. Als aanname voor illustratie: stel een verzoek stuurt 600 prompt tokens en ontvangt 400 tokens terug. Dat is 600 x $0,25 / 1.000.000 = $0,00015 in, plus 400 x $1,00 / 1.000.000 = $0,0004 uit, ongeveer $0,00055 per aanroep. Het gratis proeftegoed van $0,50 dekt ongeveer 900 aanroepen van deze grootte. Je echte prompts verschillen, dus lees het usage blok op een paar antwoorden en vermenigvuldig daar vanaf.
Het gratis proeftegoed duurt zeven dagen en vereist geen betaalgegevens, wat ruim voldoende is om alle vijf de talen te testen. Als je een use case wilt nastreven, toont de NSFW content guide een compleet voorbeeld. Onthoud dat één sleutel bij één account hoort; als je deze regeneert, update dan alle scripts tegelijk, omdat de oude sleutel direct stopt met werken.
Fouten die er in elke taal hetzelfde uitzien
Omdat de API standaard HTTP is, is de foutafhandeling identiek welke taal je ook kiest. Onthoud deze tabel en je kunt elke snippet hierboven porten.
| Status | Betekenis | Actie |
|---|---|---|
| 400 | Slecht verzoek, zoals prompt plus max_tokens boven de 100k | Knip de prompt of verlaag max_tokens |
| 401 | Ontbrekende of ongeldige sleutel | Controleer API_KEY; een vernieuwde sleutel maakt de oude direct ongeldig |
| 402 | no_credit: saldo opgebruikt of proefperiode verlopen | Laad het prepaid tegoed op |
| 403 | content_blocked | Niet opnieuw proberen; seksuele inhoud met minderjarigen wordt altijd geblokkeerd |
| 429 | Meer dan 300 verzoeken per minuut | Wacht en probeer het opnieuw |
| 503 | upstream_busy | Probeer het opnieuw na een paar seconden |
Zodra de basis werkt, stel je stream: true in voor token-voor-token output en lees de server-sent events regel voor regel. Schrijf betere prompts met de prompting guide, of koppel dezelfde aanroep aan een chatplatform met de bot walkthrough. Tarieven staan op de pricing page.