راهنماها
Headerها و ساختار Response
headerهایی که مهوش میشناسد، دو قالب response، صفحهبندی، شیء خطا، تکرار request و idempotency؛ همهٔ چیزهایی که یک کلاینت قابلاعتماد لازم دارد تا با همهٔ فراخوانیها یکسان رفتار کند.
headerهای request
این headerها در همهٔ endpointهایی که آنها را میپذیرند، معنای یکسانی دارند:
| header | چه وقت بفرستید | کاربرد |
|---|---|---|
Authorization | همهٔ requestهای احرازشده | Bearer <API key پروژه> یا Bearer <JWT کاربر>، بسته به نیاز endpoint |
Content-Type | requestهایی که body دارند | application/json، یا multipart/form-data که کتابخانهٔ HTTP برای آپلود میسازد |
X-Mahvash-Organization | requestهای JWT کاربر، وقتی در سازمانی غیر از سازمان شخصی کار میکنید | UUID یا slug سازمانی که عضو فعالش هستید |
X-Mahvash-Project | requestهای JWT کاربر، وقتی در پروژهای غیر از پروژهٔ پیشفرض کار میکنید | UUID یا slug پروژهای که به آن دسترسی دارید |
X-Request-ID | برای همهٔ requestها توصیه میشود | شناسهٔ ردگیری شما که در همهٔ پاسخها برمیگردد |
Idempotency-Key | requestهای پولی هوش مصنوعی که ممکن است تکرار شوند | بهجای اجرای دوبارهٔ کار، نتیجهٔ قبلی را برمیگرداند |
Accept: text/event-stream | stream در Responses یا Chat Completions | نیت stream را صریح میکند؛ رفتار اصلی را stream: true تعیین میکند |
یک request کامل با همهٔ این headerها چنین شکلی دارد:
curl https://api.mahvashai.ir/v1/responses \
-H "Authorization: Bearer $MAHVASH_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-H "X-Request-ID: checkout-7f3a91" \
-H "Idempotency-Key: order-1042-summary-v1" \
-d '{"model": "auto", "input": "Summarize order 1042", "stream": true}'- برای body از نوع JSON، header
Content-Type: application/jsonرا بفرستید. برای آپلود فایل بگذارید کتابخانهٔ HTTP خودشmultipart/form-dataرا بسازد و boundary را دستی تنظیم نکنید. - فرستادن
X-Request-IDاختیاری است. اگر مقداری بین ۱ تا ۱۲۸ نویسه ازA-Za-z0-9._:-بفرستید، مهوش از همان استفاده میکند؛ در غیر این صورت خودش یک شناسه (req_...) میسازد. در هر دو حالت این مقدار در headerX-Request-IDپاسخ برمیگردد؛ چه در پاسخ موفق، چه در خطا و چه در stream. به این ترتیب لاگهای شما با لاگهای مهوش قابل تطبیق است.
دو قالب response
از روی URL میفهمید body را چطور بخوانید:
| بخش | body پاسخ موفق | body خطا | فهرستها |
|---|---|---|---|
/v1/* | خود شیء | { "error": { message, type, param, code } } | بسته به endpoint؛ معمولاً data، first_id، last_id و has_more |
/api/v1/* | { "status", "data" } | { "status", "data" } با جزئیات خطا داخل data | بسته به endpoint و داخل data؛ معمولاً با page و page_size |
باینری یا 204 | خود بایتها یا بدون body | فقط کد وضعیت | ندارد |
{
"id": "resp_<uuid>",
"object": "response",
"status": "completed",
"output": []
}- endpointهای
/v1/*خود شیء را مستقیم برمیگردانند؛ درست همان چیزی که SDKهای OpenAI انتظار دارند. - endpointهای
/api/v1/*داده را داخل ساختار{ "status": <http status>, "data": ... }قرار میدهند. مقدارstatusهمان کد وضعیت HTTP است و دادهٔ اصلی، چه در موفقیت و چه در خطا، همیشه داخلdataاست. - دانلودهای باینری (مثل محتوای فایل یا صوت ساختهشده) و پاسخهای
204 No Contentاصلاً body از نوع JSON ندارند.
نکته:برای هر کدام یک تابع کمکی کوچک بنویسید؛ مثلاً unwrap() برای /api/v1 و خواندن مستقیم برای /v1. اینطور لازم نیست برای هر فراخوانی جداگانه حدس بزنید.
صفحهبندی
فهرستها همه یک شکل ندارند؛ پس همیشه فیلدهای صفحهبندی همان endpointی را بخوانید که صدا میزنید. دو الگوی رایج اینهاست:
- صفحهبندی با cursor در
/v1: پاسخ شاملobject: "list"،data،first_id،last_idوhas_moreاست. برای صفحهٔ بعد، آخرین شناسهای را که دیدهاید درafterبفرستید (یا برای صفحهٔ قبل درbefore) و اندازهٔ صفحه را باlimitتعیین کنید. - صفحهبندی با شمارهٔ صفحه در
/api/v1: پارامترهایpageوpage_sizeرا میپذیرد و موارد فهرست و اطلاعات صفحه را داخلdataبرمیگرداند.
# First page
curl "https://api.mahvashai.ir/v1/responses/resp_<uuid>/input_items?limit=20" \
-H "Authorization: Bearer $MAHVASH_API_KEY"
# {"object": "list", "data": [...], "first_id": "...", "last_id": "msg_x", "has_more": true}
# Next page: pass the previous last_id as "after"
curl "https://api.mahvashai.ir/v1/responses/resp_<uuid>/input_items?limit=20&after=msg_x" \
-H "Authorization: Bearer $MAHVASH_API_KEY"وقتی has_more برابر false شد یا صفحهای خالی برگشت، متوقف شوید. اندازهٔ پیشفرض صفحه را ثابت فرض نکنید و شناسههای یک فهرست را بهعنوان cursor در فهرست دیگری به کار نبرید.
خطاها
endpointهای /v1 خطا را با یک شیء ثابت گزارش میکنند که همیشه چهار کلید دارد. endpointهای /api/v1 جزئیات خطا را داخل data قرار میدهند:
{
"error": {
"message": "The selected model does not support the requested capabilities or limits.",
"type": "invalid_request_error",
"param": null,
"code": "invalid_request"
}
}کد وضعیت HTTP نوع خطا را نشان میدهد:
| HTTP | کدهای رایج | معنا | چه باید کرد |
|---|---|---|---|
| 400 | invalid_request | فیلد ناشناخته، نوع یا بازهٔ اشتباه، یا قابلیتی که مدل از آن پشتیبانی نمیکند | request را اصلاح کنید؛ بدون تغییر تکرارش نکنید |
| 401 | invalid_api_key | token فرستاده نشده، منقضی یا باطل شده، یا نامعتبر است | JWT را تمدید کنید یا کلید را بررسی کنید |
| 402 | insufficient_balance | موجودی سازمان برای این request کافی نیست | موجودی را افزایش دهید یا request کمهزینهتری بفرستید |
| 403 | permission_denied | احراز هویت شدهاید، اما اجازهٔ این کار را ندارید | از کلید یا نقشی با دسترسی لازم استفاده کنید |
| 404 | not_found، response_not_found | منبع وجود ندارد یا خارج از دسترس شماست | شناسه و پروژهٔ انتخابشده را بررسی کنید |
| 409 | idempotency_key_reused، idempotent_request_in_progress، idempotent_stream_not_replayable | تداخل در idempotency یا وضعیت | برای کار تازه کلید تازه بسازید یا نتیجه را بازیابی کنید |
| 429 | rate_limit_exceeded، quota_exceeded، active_run_limit | به سقف تعداد request، سهمیه یا کارهای همزمان رسیدهاید | کمی صبر کنید و به Retry-After توجه کنید |
| 502 | provider_outcome_unknown، invalid_structured_output | ارائهدهنده شکست خورده، قالب خواستهشده را رعایت نکرده، یا شاید request را پردازش کرده باشد | نتیجهٔ نامعلوم را بیگدار تکرار نکنید؛ اول بررسیاش کنید |
| 503 | provider_unavailable، feature_disabled | هیچ مسیر مناسبی نیست یا سرویسی که لازم است در دسترس نیست | بعداً دوباره تلاش کنید یا مدل و گزینهها را تغییر دهید |
| 500 | internal_error | خطای پیشبینینشده در سرور | با فاصلهٔ زمانی دوباره تلاش کنید و X-Request-ID را گزارش دهید |
در /v1، مقدار error.type از روی کد وضعیت تعیین میشود و بهترین فیلد برای تصمیمگیری است: invalid_request_error یعنی خطا دائمی است و باید request را اصلاح کنید؛ rate_limit_error و server_error موقتیاند و بعد از کمی صبر میتوانید دوباره تلاش کنید. error.code دلیل دقیق را مشخص میکند. این کدها ثابتاند، اما ممکن است کد تازهای اضافه شود؛ پس همیشه یک حالت پیشفرض برای کدهای ناشناخته داشته باشید.
تکرار امن request
بیشتر فراخوانیهای هوش مصنوعی هزینه دارند؛ پس سیاست تکرار باید طوری باشد که برای یک کار دو بار پول ندهید:
- خطاهای
429و5xxرا با فاصلهٔ زمانی رو به افزایش (exponential backoff) و کمی تصادفیسازی دوباره امتحان کنید. اگرRetry-Afterوجود داشت، دستکم همان تعداد ثانیه صبر کنید. - بقیهٔ خطاهای
4xxرا بدون تغییر تکرار نکنید؛ دوباره به همان شکل شکست میخورند. provider_outcome_unknown، timeout سمت کلاینت یا قطع اتصال پس از ارسال request را هرگز بیگدار تکرار نکنید. ممکن است ارائهدهنده کار را انجام داده باشد. پیش از هر تصمیمی، response را با شناسهاش بازیابی کنید یا با همان idempotency key دوباره بفرستید.- در همهٔ تلاشهای مربوط به یک کار، از همان
Idempotency-Keyاستفاده کنید. کلید تازه یعنی request تازه با هزینهٔ جداگانه.
const RETRYABLE = new Set(["rate_limit_error", "server_error"]);
export async function callMahvash(path, body, { idempotencyKey, attempts = 3 } = {}) {
for (let attempt = 1; ; attempt++) {
const response = await fetch("https://api.mahvashai.ir" + path, {
method: "POST",
headers: {
Authorization: "Bearer " + process.env.MAHVASH_API_KEY,
"Content-Type": "application/json",
"Idempotency-Key": idempotencyKey, // the SAME key on every attempt
},
body: JSON.stringify(body),
signal: AbortSignal.timeout(60_000),
});
if (response.ok) return response.json();
const requestId = response.headers.get("X-Request-ID");
const { error } = await response.json().catch(() => ({ error: {} }));
const ambiguous = error?.code === "provider_outcome_unknown";
if (!RETRYABLE.has(error?.type) || ambiguous || attempt >= attempts) {
throw Object.assign(new Error(error?.message ?? "Mahvash error"), {
status: response.status, code: error?.code, requestId,
});
}
const retryAfter = Number(response.headers.get("Retry-After"));
const delay = retryAfter > 0 ? retryAfter * 1000 : 500 * 2 ** attempt + Math.random() * 250;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}مهم:تکرار خودکار را در کتابخانههای HTTP و SDKها خاموش کنید (مثلاً با maxRetries: 0)، مگر مطمئن باشید همان idempotency key شما را دوباره میفرستند. دو لایهٔ تکرار، تعداد تلاشها را چند برابر میکند.
کلید idempotency
Idempotency-Key پرسش «آیا request من انجام شد؟» را بیخطر میکند. endpointهای ساخت در بخش هوش مصنوعی آن را میپذیرند؛ یعنی Responses، Chat Completions، embedding، تصویر، صوت، moderation، search و batch. در مرجع API، این header در فهرست headerهای هر endpoint آمده است.
- کلید باید بین ۱ تا ۲۵۵ نویسهٔ قابلنمایش ASCII باشد. آن را از شناسهٔ خودتان برای آن کار بسازید، مثلاً
ticket-812-reply-v3، یا یک UUID که پیش از اولین تلاش ذخیره کردهاید. - هر کلید در محدودهٔ یک پروژه و یک نوع عملیات معتبر است و بهطور پیشفرض ۲۴ ساعت نگه داشته میشود.
# First call: runs the model once and stores the result
curl https://api.mahvashai.ir/v1/responses \
-H "Authorization: Bearer $MAHVASH_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: invoice-8812-summary" \
-d '{"model": "auto", "input": "Summarize invoice 8812"}'
# Same key + same body within the window: the stored response is replayed,
# no second model call and no second charge.
# Same key + different body: 409 idempotency_key_reusedوقتی یک کلید دوباره فرستاده شود:
- همان کلید و همان request، و request قبلی با موفقیت تمام شده (بدون stream): همان response ذخیرهشده برمیگردد؛ بدون فراخوانی دوبارهٔ مدل و بدون هزینهٔ تازه.
- همان کلید با body متفاوت: خطای
409 idempotency_key_reused. - همان کلید درحالیکه request اول هنوز در حال اجراست: خطای
409 idempotent_request_in_progress. کمی صبر کنید و دوباره بفرستید یا response را بازیابی کنید. - requestهای stream هرگز دوباره پخش نمیشوند: خطای
409 idempotent_stream_not_replayable. بهجای آن، response ذخیرهشده را بازیابی کنید. - اگر request قبلی شکست خورده یا timeout شده باشد، کلید تا پایان اعتبارش رزرو میماند؛ چون ممکن است نتیجهٔ آن نزد ارائهدهنده هنوز مشخص نباشد.