Toute l'API en un seul curl
Oubliez le SDK un instant. Chaque exemple ci-dessous envoie la même chose : un corps JSON à POST /v1/chat/completions avec un bearer token. Si vous pouvez envoyer cela, c'est terminé. Récupérez une clé sur la page d'inscription (le crédit d'essai gratuit ne nécessite pas de détails de paiement), exportez-la en tant que API_KEY, et lancez ceci en premier.
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
}'La réponse est un objet de style OpenAI. Le texte se trouve à choices[0].message.content, et un bloc usage vous indique ce que l'appel a coûté en tokens. Il n'y a qu'un seul modèle, uncensored, donc le champ model ne change jamais. Vous pouvez le confirmer avec GET /v1/models.
curl https://api.unrestrictedaiapi.com/v1/models -H "Authorization: Bearer $API_KEY"Deux limites à mémoriser avant d'écrire du code : la fenêtre de contexte est de 100 000 tokens partagés entre prompt et complétion, et max_tokens est par défaut de 2048 sauf si vous l'augmentez (jusqu'à 32 000). Si votre sortie s'arrête souvent en plein milieu d'une phrase, ce défaut en est généralement la cause.
Python avec httpx
httpx vous offre un client synchrone avec des délais d'attente raisonnables et un jumeau asynchrone lorsque vous en avez besoin. Définissez le délai d'attente explicitement. Les longues générations peuvent prendre du temps et le défaut de cinq secondes les coupera.
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() convertit tout 4xx ou 5xx en exception, ce qui est bien pour un script. Pour tout ce qui est long, enveloppez l'appel pour que deux statuts aient une autre chance : 429 (vous avez atteint la limite par clé de 300 requêtes par minute) et 503 (upstream_busy, qui se résout en quelques secondes). Tout le reste, y compris 401, 402 et 403, ne se réparera pas de lui-même lors d'un réessai.
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."}]))Remplacez httpx.post par httpx.AsyncClient().post à l'intérieur d'un async def et vous avez la version asynchrone avec la même charge utile.
JavaScript avec fetch
Node 18 et versions ultérieures incluent fetch, donc rien à installer. Enregistrez le fichier sous .mjs (ou définissez "type": "module") pour que await de niveau supérieur fonctionne. Les navigateurs disposent de la même API, mais ne placez pas la clé dans le code front-end. Faites un proxy de l'appel via votre propre 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);Notez l'ordre des vérifications. fetch ne rejette qu'en cas d'échec réseau, donc un 402 ou 403 arrive comme une réponse normale. Testez res.ok vous-même et lisez le JSON d'erreur, qui a toujours la forme {"error":{"code":...,"message":...}}.
Go avec net/http
Go nécessite quelques lignes supplémentaires mais aucune dépendance. Définissez uniquement les champs de la structure que vous lisez ; encoding/json ignore le reste. Lire le corps en octets avant de vérifier le code d'état vous permet d'imprimer le message d'erreur du serveur en cas de problème.
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)
}Pour la production, créez un seul http.Client{Timeout: 60 * time.Second} et réutilisez-le. Le client par défaut n'a aucun délai d'attente, ce qui fait qu'une requête bloquée devient une goroutine bloquée.
PHP avec cURL et Ruby avec Net::HTTP
L'extension cURL de PHP est déjà présente sur la plupart des hébergeurs. Construisez le corps avec json_encode, retournez le transfert sous forme de chaîne, puis lisez le statut avec curl_getinfo. Sauter la vérification du statut est l'erreur classique : un corps 402 se décode bien en JSON et votre code échoue ensuite sur une clé choices manquante.
<?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";Sur un hébergement partagé avec un max_execution_time faible, gardez max_tokens modeste ou déplacez l'appel dans un worker de file d'attente.
La bibliothèque standard suffit ici aussi. ENV.fetch lève immédiatement une exception si la variable est manquante, ce qui vaut mieux que d'envoyer un token bearer vide et de déboguer un 401 pendant dix minutes.
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")Si vous passez plus tard à Rails, le même corps fonctionne depuis un job en arrière-plan. Gardez l'appel en dehors du cycle de requête ; les générations sont plus lentes qu'une requête de base de données.
Les deux extraits envoient exactement le même corps que celui de Python, c'est le point : si un langage fonctionne et qu'un autre non, comparez le JSON, pas le client. Imprimez le corps de la requête brute une fois et comparez octet par octet. Neuf fois sur dix, la cause est un en-tête Content-Type manquant ou un nom de modèle tapé avec une majuscule.
Streaming sans bibliothèque
Le streaming est le seul endroit où HTTP brut vous demande un peu plus. Avec stream: true, le serveur répond avec des événements envoyés par le serveur : chaque ligne commence par data: , contient un chunk JSON, et le flux se termine avec data: [DONE]. Un dernier chunk d'utilisation est ajouté automatiquement, donc vous obtenez des compteurs de tokens même en streaming. Ce chunk n'a pas d'entrées choices, c'est pourquoi la boucle ci-dessous vérifie avant d'indexer.
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()Le même pattern s'adapte directement. En JavaScript, lisez res.body avec un lecteur et divisez sur les nouvelles lignes. En Go, enveloppez resp.Body dans un bufio.Scanner. En Ruby, passez un bloc à http.request et utilisez read_body. En PHP, définissez un rappel CURLOPT_WRITEFUNCTION. La logique ne change jamais : supprimez le préfixe, arrêtez-vous à la sentinelle, analysez le JSON, imprimez le delta.
Un test de fumée de cinq minutes et un contrôle du budget
Avant de construire sur l'un de ces extraits, effectuez une vérification rapide. D'abord, appelez /v1/models avec votre clé ; un 200 prouve la clé et le chemin réseau. Ensuite, envoyez la requête de chat la plus petite possible avec max_tokens défini à 20. Troisièmement, cassez délibérément quelque chose, comme un token bearer vide, et confirmez que votre code affiche clairement le 401 au lieu de planter sur une clé manquante.
Faites ensuite l'arithmétique une fois pour que rien ne vous surprenne. Les prix sont de $0,25 par million de tokens d'entrée et de $1,00 par million de tokens de sortie. En supposition pour l'illustration, disons qu'une requête envoie 600 tokens de prompt et reçoit 400 tokens en retour. Cela fait 600 x $0,25 / 1 000 000 = $0,00015 entrant, plus 400 x $1,00 / 1 000 000 = $0,0004 sortant, soit environ $0,00055 par appel. Le crédit d'essai gratuit de $0,50 couvrirait environ 900 appels de cette taille. Vos vrais prompts différeront, alors lisez le bloc usage sur quelques réponses et multipliez à partir de là.
Le crédit d'essai gratuit dure sept jours et ne nécessite pas de détails de paiement, ce qui le rend amplement suffisant pour tester les cinq langages. Si vous voulez un cas d'utilisation à viser, le guide de contenu NSFW montre un exemple complet. Rappelez-vous qu'une clé appartient à un compte ; si vous la régénérez, mettez à jour tous les scripts en même temps, car l'ancienne clé cesse de fonctionner immédiatement.
Les erreurs qui se ressemblent dans tous les langages
Comme l'API est HTTP brut, la gestion des échecs est identique quel que soit le langage que vous choisissez. Mémorisez ce tableau et vous pouvez porter n'importe quel extrait ci-dessus.
| Statut | Signification | Action |
|---|---|---|
| 400 | Mauvaise requête, telle que prompt plus max_tokens au-delà de 100k | Réduisez le prompt ou baissez max_tokens |
| 401 | Clé manquante ou invalide | Vérifiez API_KEY ; une clé régénérée invalide immédiatement l’ancienne |
| 402 | no_credit : solde épuisé ou essai expiré | Rechargez le solde prépayé |
| 403 | content_blocked | Ne réessayez pas ; le contenu sexuel impliquant des mineurs est toujours bloqué |
| 429 | Plus de 300 requêtes par minute | Reculez et réessayez |
| 503 | upstream_busy | Réessayez après quelques secondes |
Une fois les bases fonctionnelles, définissez stream: true pour une sortie token par token et lisez les événements envoyés par le serveur ligne par ligne. Rédigez de meilleurs prompts avec le guide de prompting, ou intégrez le même appel dans une plateforme de chat en suivant le tutoriel bot. Les tarifs sont sur la page des tarifs.