curlでAPI全体を1行で
SDKは一旦忘れてください。以下のすべての例は同じものを送信します:Bearerトークン付きのPOST /v1/chat/completionsへのJSONボディです。これが送れれば完了です。サインアップページからキーを取得し(無料トライアルクレジットには支払い情報不要)、それを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"コードを書く前に覚えておくべき2つの制限があります:コンテキストウィンドウはプロンプトとcompletionで合計100,000トークンであり、max_tokensは引き上げない限りデフォルトの2048です(最大32,000まで)。出力が文の途中で止まる場合、このデフォルト値が原因であることがほとんどです。
Python with httpx
httpxは、合理的なタイムアウトを持つ同期クライアントと、必要な場合に備えた非同期版を提供します。タイムアウトを明示的に設定してください。長時間の生成には時間がかかるため、デフォルトの5秒では切れてしまいます。
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を例外に変換しますが、スクリプトでは問題ありません。長時間実行される処理では、呼び出しをラップして2つのステータスに再試行の機会を与えてください:429(キーごとの1分あたり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をasync def内でhttpx.AsyncClient().postに置き換えると、同じペイロードを持つ非同期版が完成します。
JavaScript with 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を自分でテストし、常に{"error":{"code":...,"message":...}}という構造を持つエラーJSONを読み取ってください。
Go with 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}を1つ構築して再利用してください。デフォルトクライアントにはタイムアウトがないため、リクエストが固まるとゴルーチンも固まってしまいます。
cURL を使った PHP と Net::HTTP を使った Ruby
PHPのcURL拡張機能はほとんどのホストで既に利用可能です。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 は即座に例外を発生させるため、空のベアートークンを送信して 10 分間 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を比較してください。リクエストボディを1回だけ出力し、バイト単位で比較します。90%の確率で原因は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をリーダーで読み取り、改行で分割します。Goでは、resp.Bodyをbufio.Scannerでラップします。Rubyでは、http.requestにブロックを渡し、read_bodyを使用します。PHPでは、CURLOPT_WRITEFUNCTIONコールバックを設定します。ロジックは変化しません:プレフィックスを削除し、センチネルで停止し、JSONを解析し、差分を出力します。
5分間のスモークテストと予算確認
これらのスニペットの上に構築する前に、簡単な健全性チェックを実行してください。まず、キーで/v1/modelsを呼び出し、200が返ればキーとネットワークパスが有効なことを証明します。次に、max_tokensを20に設定して、可能な限り最小のチャットリクエストを送信します。最後に、空のBearerトークンなど意図的にエラーを発生させ、フィールドの欠落でクラッシュするのではなく、401が明確に表示されることを確認してください。
次に、驚きがないように計算を1回行ってください。価格は入力トークン100万トークンあたり$0.25、出力トークン100万トークンあたり$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ブロックを読み取り、そこから計算してください。
無料トライアルクレジットは7日間有効で、支払い情報が必要ないため、5言語すべてをテストするのに十分です。目指すユースケースがある場合は、NSFWコンテンツガイドに完全な例が示されています。1つのキーは1つのアカウントに属していることを覚えておいてください。キーを再生成した場合、古いキーは即座に無効になるため、すべてのスクリプトを同時に更新してください。
すべての言語で同じエラー
APIはプレーンHTTPであるため、エラー処理は選択した言語によって同じです。この表を暗記すれば、上記のスニペットをどの言語にも移植できます。
| ステータス | 意味 | アクション |
|---|---|---|
| 400 | プロンプトとmax_tokensの合計が100,000トークンを超えるなどの不正なリクエスト | プロンプトを短くするか、max_tokensを下げる |
| 401 | キーの欠落または無効 | API_KEYを確認してください。再生成すると、古いキーは即座に無効になります。 |
| 402 | no_credit:残高がゼロまたはトライアルが期限切れ | 前払いクレジットの残高をチャージしてください。 |
| 403 | content_blocked | 再試行しないでください。未成年者を含む性的なコンテンツは常にブロックされます。 |
| 429 | 1分間に300件を超えるリクエスト | バックオフして再試行してください。 |
| 503 | upstream_busy | 数秒後に再試行してください。 |
基本が動作したら、stream: trueを設定してトークンごとの出力を有効にし、サーバー送信イベントの行を1行ずつ読み取ってください。プロンプティングガイドでより良いプロンプトを作成するか、ボットの手順書を使用して同じ呼び出しをチャットプラットフォームに接続してください。料金は料金ページに記載されています。