Весь API в одном curl
На минуту забудьте про SDK. Каждый пример ниже отправляет одно и то же: JSON-тело в POST /v1/chat/completions с токеном авторизации. Если вы можете это отправить, задача решена. Получите ключ на странице регистрации (для пробного баланса не нужны данные карты), экспортируйте его как API_KEY и запустите это.
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
}'Ответ — объект в стиле OpenAI. Текст находится по адресу choices[0].message.content, а блок usage сообщает, сколько стоил вызов в токенах. Есть одна модель, uncensored, поэтому поле model никогда не меняется. Вы можете подтвердить это с помощью GET /v1/models.
curl https://api.unrestrictedaiapi.com/v1/models -H "Authorization: Bearer $API_KEY"Два лимита, которые стоит запомнить перед написанием кода: контекстное окно составляет 100 000 токенов, общих для промпта и ответа, а max_tokens по умолчанию равен 2048, если вы не увеличите его (до 32 000). Если ваш вывод постоянно обрывается на полуслове, причина обычно в этом значении по умолчанию.
Python с httpx
httpx предоставляет синхронный клиент с разумными таймаутами и асинхронную версию, когда она нужна. Установите таймаут явно. Долгие генерации могут занять много времени, а стандартные пять секунд их оборвут.
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() преобразует любой 4xx или 5xx в исключение, что нормально для скрипта. Для любых длительных операций оберните вызов так, чтобы два статуса получили еще один шанс: 429 (вы достигли лимита в 300 запросов в минуту для ключа) и 503 (upstream_busy, который проходит через несколько секунд). Все остальное, включая 401, 402 и 403, само не исправится при повторной попытке.
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."}]))Замените httpx.post на httpx.AsyncClient().post внутри async def, и у вас будет асинхронная версия с теми же данными.
JavaScript с fetch
Node 18 и новее поставляются с fetch, поэтому ничего устанавливать не нужно. Сохраните файл как .mjs (или установите "type": "module"), чтобы работал верхнеуровневый await. В браузерах тот же API, но не помещайте ключ в код фронтенда. Проксируйте вызов через свой бэкенд.
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);Обратите внимание на порядок проверок. fetch возвращает ошибку только при сбое сети, поэтому 402 или 403 приходит как обычный ответ. Проверьте res.ok самостоятельно и прочитайте JSON ошибки, который всегда имеет вид {"error":{"code":...,"message":...}}.
Go с net/http
Go требует нескольких дополнительных строк, но не требует зависимостей. Определите только поля структуры, которые вы читаете; encoding/json игнорирует остальные. Чтение тела в байты перед проверкой статуса позволяет вывести сообщение об ошибке сервера, когда что-то идет не так.
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)
}Для продакшена создайте один http.Client{Timeout: 60 * time.Second} и переиспользуйте его. Клиент по умолчанию не имеет таймаута вообще, что превращает зависший запрос в зависший горутину.
PHP с cURL и Ruby с Net::HTTP
Расширение cURL PHP уже есть на большинстве хостов. Создайте тело с помощью json_encode, верните передачу как строку, затем прочитайте статус с помощью curl_getinfo. Пропуск проверки статуса — классическая ошибка: тело 402 успешно декодируется как JSON, и ваш код затем падает из-за отсутствующего ключа 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";На общем хосте с низким max_execution_time держите max_tokens умеренным или переместите вызов в воркер очереди.
Стандартной библиотеки здесь также достаточно. ENV.fetch сразу вызывает ошибку, если переменная отсутствует, что лучше, чем отправка пустого токена bearer и отладки 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")Если позже вы перейдете на Rails, то же тело работает из фоновой задачи. Держите вызов вне цикла запроса; генерации медленнее, чем запрос к базе данных.
Оба фрагмента отправляют ровно то тело, которое отправляет Python-версия, в чем и суть: если один язык работает, а другой нет, сравнивайте JSON, а не клиент. Выведите сырое тело запроса один раз и сравните побайтово. В девяти случаях из десяти виноват отсутствующий заголовок Content-Type или имя модели, набранное с заглавной буквы.
Потоковая передача без библиотеки
Потоковая передача — единственное место, где обычный HTTP просит от вас немного больше. С stream: true сервер отвечает событиями, отправляемыми сервером: каждая строка начинается с data: , содержит фрагмент JSON, а поток заканчивается с data: [DONE]. Финальный фрагмент usage добавляется автоматически, поэтому вы получаете подсчет токенов даже при потоковой передаче. Этот фрагмент не имеет записей choices, поэтому цикл ниже проверяет перед индексацией.
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()Та же логика работает напрямую. В JavaScript прочитайте res.body с помощью reader и разбейте по символам новой строки. В Go оберните resp.Body в bufio.Scanner. В Ruby передайте блок в http.request и используйте read_body. В PHP установите колбэк CURLOPT_WRITEFUNCTION. Логика не меняется: удалите префикс, остановитесь на маркере, распарсите JSON, выведите разницу.
Пятиминутный smoke-тест и проверка бюджета
Прежде чем строить что-то на основе этих фрагментов, выполните быструю проверку. Сначала вызовите /v1/models со своим ключом; 200 подтверждает ключ и сетевой путь. Во-вторых, отправьте минимальный запрос чата с max_tokens, установленным в 20. В-третьих, намеренно сломайте что-то, например, пустой токен bearer, и подтвердите, что ваш код четко выводит 401, а не падает из-за отсутствующего поля.
Затем выполните арифметику один раз, чтобы вас ничего не удивило. Цены: $0,25 за миллион входных токенов и $1,00 за миллион выходных токенов. Для иллюляции предположим, что запрос отправляет 600 промпт-токенов и получает 400 токенов в ответ. Это 600 x $0,25 / 1 000 000 = $0,00015 на вход, плюс 400 x $1,00 / 1 000 000 = $0,0004 на выход, около $0,00055 за вызов. Пробный баланс в $0,50 покроет примерно 900 вызовов такого размера. Ваши реальные промпты будут отличаться, поэтому прочитайте блок usage в нескольких ответах и умножьте от туда.
Пробный баланс действует семь дней и не требует данных карты, что вполне достаточно для тестирования всех пяти языков. Если вам нужен пример использования, руководство по NSFW-контенту показывает полный пример. Помните, что один ключ принадлежит одному аккаунту; если вы сгенерируете его заново, обновите все скрипты одновременно, так как старый ключ перестанет работать немедленно.
Ошибки, которые выглядят одинаково на всех языках
Поскольку API — это обычный HTTP, обработка сбоев идентична независимо от выбранного языка. Запомните эту таблицу, и вы сможете портировать любой фрагмент выше.
| Статус | Значение | Действие |
|---|---|---|
| 400 | Неверный запрос, например, промпт плюс max_tokens более 100 тыс. | Уменьшите промпт или снизьте max_tokens |
| 401 | Отсутствующий или недействительный ключ | Проверьте API_KEY; при регенерации старый ключ мгновенно становится недействительным |
| 402 | no_credit: баланс исчерпан или пробный период истёк | Пополните предоплаченный баланс |
| 403 | content_blocked | Не повторяйте запрос; контент сексуального характера с участием несовершеннолетних блокируется всегда |
| 429 | Более 300 запросов в минуту | Сделайте паузу и повторите запрос |
| 503 | upstream_busy | Повторите запрос через несколько секунд |
Когда основы заработают, установите stream: true для вывода токенов по одному и читайте потоковые события построчно. Улучшайте промпты с помощью руководства по промптам или подключите тот же вызов к чат-платформе с помощью инструкции по созданию бота. Тарифы указаны на странице цен.