uttapen

curl و HTTP خام

کار مستقیم با API uttapen بدون SDK؛ chat، استریم با curl -N، هدرهای هزینه و rate limit، رویداد uttapen.meta، embeddings، فهرست مدل‌ها، وضعیت حساب و.

به‌روزرسانی: ۱۶ شهریور ۱۴۰۵

هر زبانی که HTTP و JSON دارد با uttapen کار می‌کند. این صفحه هر endpoint را با curl نشان می‌دهد؛ برای Bash، اسکریپت CI، یا وقتی می‌خواهی دقیقاً ببینی روی سیم چه می‌گذرد.

کلید را یک بار در محیط بگذار:

export UTTAPEN_API_KEY=sk-up-...
export UTTAPEN=https://api.uttapen.ir/v1

Chat (غیراستریم)

curl -s -i "$UTTAPEN/chat/completions" \
  -H "Authorization: Bearer $UTTAPEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5-mini",
    "messages": [{"role": "user", "content": "سه مزیت Postgres را در سه خط بگو."}],
    "max_tokens": 200
  }'

-i هدرها را هم نشان می‌دهد:

HTTP/1.1 200 OK
Content-Type: application/json
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 1788734238
X-Uttapen-Balance-Toman: 97564.413809
X-Uttapen-Cost-Toman: 6.659688
X-Uttapen-Model: openai/gpt-5-mini
X-Uttapen-Request-Id: 01a078dd-853e-7480-aa83-262d3239e6a2

فقط متن پاسخ با jq:

curl -s "$UTTAPEN/chat/completions" -H "Authorization: Bearer $UTTAPEN_API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-5-nano","messages":[{"role":"user","content":"سلام"}]}' \
  | jq -r '.choices[0].message.content'

X-API-Key: $UTTAPEN_API_KEY هم به‌جای Authorization پذیرفته می‌شود.

استریم

curl -s -N "$UTTAPEN/chat/completions" \
  -H "Authorization: Bearer $UTTAPEN_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Uttapen-Include-Meta: 1" \
  -d '{"model":"openai/gpt-5-mini","messages":[{"role":"user","content":"یک شعر کوتاه بگو."}],"stream":true}'

-N بافر خروجی curl را خاموش می‌کند تا چانک‌ها همان لحظه چاپ شوند. خروجی خطوط data: است و آخرین‌ها این شکلی‌اند:

data: {"id":"gen-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":10,"completion_tokens":24,"total_tokens":34}}

data: {"id":"gen-…","object":"uttapen.meta","cost_toman":"6.659688","balance_toman":"97557.754121","hold_toman":"100.000000"}

data: [DONE]

فقط متن، به‌صورت زنده، با jq:

curl -s -N "$UTTAPEN/chat/completions" -H "Authorization: Bearer $UTTAPEN_API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-5-mini","messages":[{"role":"user","content":"سلام"}],"stream":true}' \
  | sed -u 's/^data: //' | grep -v '^\[DONE\]' | grep -v '^:' | grep . \
  | jq -rj '.choices[0].delta.content // empty'

Embeddings

curl -s "$UTTAPEN/embeddings" \
  -H "Authorization: Bearer $UTTAPEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"openai/text-embedding-3-small","input":["قرارداد اجاره","اجاره‌نامهٔ ملک"]}' \
  | jq '.data | length, (.[0].embedding | length)'

Tool calling

curl -s "$UTTAPEN/chat/completions" \
  -H "Authorization: Bearer $UTTAPEN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5-mini",
    "messages": [{"role": "user", "content": "هوای تهران؟"}],
    "tools": [{"type": "function", "function": {"name": "get_weather",
      "parameters": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}}}]
  }' | jq '.choices[0].message.tool_calls'

نتیجهٔ اجرای تابع را با {"role":"tool","tool_call_id":"…","content":"…"} به messages اضافه کن و دوباره بفرست (Tool calling).

فهرست مدل‌ها (بدون کلید)

curl -s "$UTTAPEN/models" | jq -r '.data[] | "\(.id)\t\(.uttapen_pricing.prompt_toman_per_1m)\t\(.uttapen_pricing.completion_toman_per_1m)"' | column -t
curl -s "$UTTAPEN/models/openai/gpt-5-mini" | jq '{id, context_length, input_modalities, supported_parameters, uttapen_pricing}'

مدل‌های vision:

curl -s "$UTTAPEN/models" | jq -r '.data[] | select(.input_modalities | index("image")) | .id'

وضعیت حساب و مصرف

curl -s "$UTTAPEN/uttapen/me" -H "Authorization: Bearer $UTTAPEN_API_KEY" | jq '.wallet, .key'

curl -s "$UTTAPEN/uttapen/usage?group_by=day&from=2026-09-01" -H "Authorization: Bearer $UTTAPEN_API_KEY" \
  | jq -r '.data[] | "\(.bucket)\t\(.requests)\t\(.charge_toman)"'

خطاها

curl -s -i "$UTTAPEN/chat/completions" -H "Authorization: Bearer sk-up-wrong" -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-5-mini","messages":[{"role":"user","content":"hi"}]}'
HTTP/1.1 401 Unauthorized
Content-Type: application/json; charset=utf-8

{"error":{"message":"کلید API نامعتبر، لغوشده یا منقضی است.","type":"invalid_api_key","code":"invalid_api_key","param":null}}

در اسکریپت، status را جدا بگیر:

code=$(curl -s -o /tmp/resp.json -w '%{http_code}' "$UTTAPEN/chat/completions" -H "Authorization: Bearer $UTTAPEN_API_KEY" \
  -H "Content-Type: application/json" -d @request.json)
if [ "$code" != "200" ]; then jq -r '.error.code + ": " + .error.message' /tmp/resp.json; fi

نکته‌ها

  • بدنه را با @file.json بفرست تا با نقل‌قول‌های Bash درگیر نشوی؛ متن فارسی داخل -d '...' مشکلی ندارد ولی ' داخل متن، رشته را می‌بندد.
  • Content-Type: application/json الزامی است.
  • --max-time برای غیراستریم بگذار (مثلاً 180)؛ برای استریم نگذار یا بزرگ بگذار.
  • برای دیدن هدرهای درخواست و پاسخ با هم، -v را اضافه کن؛ کلید در خروجی چاپ می‌شود، در لاگ نگهش ندار.
  • اگر از پشت proxy شرکتی هستی و استریم یک‌جا در پایان می‌رسد، proxy بافر می‌کند؛ با -N ربطی ندارد.

batch در Bash

برای چند صد پرسش از یک فایل، بدنه را با jq -n --arg بساز تا نقل‌قول و خطوط جدید درست escape شوند، هزینهٔ هر پاسخ را از هدر بخوان و بین درخواست‌ها به اندازهٔ سقف دقیقه‌ای کلید فاصله بگذار.

#!/usr/bin/env bash
set -euo pipefail
total=0
while IFS= read -r line; do
  body=$(jq -n --arg q "$line" '{model:"openai/gpt-5-nano", max_tokens:120, messages:[{role:"user", content:$q}]}')
  hdr=$(mktemp)
  answer=$(curl -s -D "$hdr" "$UTTAPEN/chat/completions" \
    -H "Authorization: Bearer $UTTAPEN_API_KEY" -H "Content-Type: application/json" \
    -d "$body" | jq -r '.choices[0].message.content // .error.message')
  cost=$(grep -i '^x-uttapen-cost-toman:' "$hdr" | tr -d '\r' | awk '{print $2}')
  printf '%s\t%s\t%s\n' "$line" "$cost" "$answer"
  rm -f "$hdr"
  sleep 1   # ۶۰ درخواست در دقیقه
done < questions.txt

-D file هدرهای پاسخ را در فایل می‌ریزد و -s نوار پیشرفت را خاموش می‌کند. اگر خطا گرفتی، .error.message به‌جای پاسخ چاپ می‌شود و اسکریپت ادامه می‌دهد؛ برای توقف روی 402، status را با -w '%{http_code}' جدا بگیر و چک کن.

HTTPie، Postman و Insomnia

با HTTPie همان درخواست کوتاه‌تر است و JSON را خودش می‌سازد:

http POST "$UTTAPEN/chat/completions" "Authorization:Bearer $UTTAPEN_API_KEY" \
  model=openai/gpt-5-mini messages:='[{"role":"user","content":"سلام"}]' --stream

در Postman و Insomnia، تب Authorization را روی «Bearer Token» بگذار و کلید را آن‌جا وارد کن؛ بدنه را «raw / JSON» انتخاب کن. استریم در Postman به‌صورت زنده نمایش داده نمی‌شود و کل پاسخ در پایان می‌آید؛ برای دیدن چانک‌ها curl با -N بهتر است. کلید را در Environment ذخیره کن نه داخل خود درخواست، تا وقتی collection را export و به همکار می‌دهی، کلید همراهش نرود.

چک سریع قبل از استقرار

سه دستور که در چند ثانیه می‌گویند سرور تو آمادهٔ کار با یوتاپن هست یا نه:

curl -s -o /dev/null -w '%{http_code} %{time_total}s\n' https://api.uttapen.ir/healthz      # 200 و زمان اتصال
curl -s "$UTTAPEN/uttapen/me" -H "Authorization: Bearer $UTTAPEN_API_KEY" | jq '.wallet.available_toman, .key.allowed_models'
curl -s "$UTTAPEN/chat/completions" -H "Authorization: Bearer $UTTAPEN_API_KEY" -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-5-nano","messages":[{"role":"user","content":"ok"}],"max_tokens":5}' | jq -r '.choices[0].message.content'

اولی شبکه و DNS را چک می‌کند، دومی کلید و موجودی و محدودیت مدل را، سومی یک درخواست واقعی چند تومانی است. اگر سرور از proxy خروجی استفاده می‌کند، --proxy http://… را به curl بده یا HTTPS_PROXY را ست کن؛ SDKها هم همان متغیر را می‌خوانند. این سه خط را در اسکریپت deploy بگذار تا خرابی تنظیمات قبل از رسیدن به کاربر معلوم شود.