REST API · v1

مستندات API اوپتی‌پیک

با چند خط کد، هر تصویر را از طریق آدرس یا آپلود مستقیم بفرستید و نسخه‌ی بهینه‌شده را پس بگیرید — همان موتوری که پلاگین وردپرس و ابزار داخل سایت هم روی آن سوار هستند.

معرفی

API اوپتی‌پیک یک واسط REST ساده و مبتنی بر JSON است. یک تصویر (به‌صورت فایل یا آدرس) می‌فرستید، یک شناسه‌ی job پس می‌گیرید، وضعیتش را پیگیری می‌کنید و وقتی آماده شد دانلودش می‌کنید. برای پردازش چند تصویر با هم، از حالت دسته‌ای (batch) استفاده کنید.

آدرس پایه

همه‌ی درخواست‌های این API به آدرس زیر ارسال می‌شوند — این هم یک لایه‌ی واسط است، نه بک‌اند پردازشی خام؛ صرفاً روی زیردامنه‌ی مخصوص API میزبانی می‌شود تا بار ترافیک برنامه‌نویسی جدا از سایت اصلی توزیع شود. هرگز مستقیم به بک‌اند پردازشی وصل نشوید — این آدرس تنها نقطه‌ی ورود پایدار و پشتیبانی‌شده برای API است.

https://api.optipic.ir/api?action=<endpoint>

احراز هویت

هر درخواست (به‌جز دانلود، که با یک شناسه‌ی غیرقابل‌حدس محافظت می‌شود) به یک جفت ایمیل و کلید API نیاز دارد.

email — ایمیل حساب کاربری شما در اوپتی‌پیک.
token — کلید API حساب شما؛ در داشبورد → تب «API و مصرف» قابل مشاهده و بازتولید است. با هر بار بازتولید، کلید قبلی فوراً از کار می‌افتد.
هیچ CSRF token یا کوکی سشن لازم نیست. این محافظت مخصوص مرورگری است که این سایت را رندر کرده — برای یک تماس سرور-به-سرور مثل پلاگین وردپرس معنایی ندارد. ایمیل و توکن به‌تنهایی هویت را ثابت می‌کنند.

کجا فرستاده می‌شوند؟

همیشه در بدنه‌ی درخواست — به‌صورت فیلد JSON یا multipart، نه هدر HTTP. برای درخواست‌های GET پیگیری وضعیت که بدنه ندارند، به‌صورت query string فرستاده می‌شوند.

curl -X POST "https://api.optipic.ir/api?action=v1-create" \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","token":"sk_live_51H8x...redacted","imageUrl":"https://example.com/photo.jpg"}'
$payload = json_encode([
    'email' => 'you@example.com',
    'token' => $token,
    'imageUrl' => $imageUrl,
]);

$ch = curl_init('https://api.optipic.ir/api?action=v1-create');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
]);
$job = json_decode(curl_exec($ch), true);
curl_close($ch);
const res = await fetch("https://api.optipic.ir/api?action=v1-create", {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email, token, imageUrl }),
});
import requests

r = requests.post('https://api.optipic.ir/api?action=v1-create', json={
    'email': 'you@example.com',
    'token': token,
    'imageUrl': imageUrl,
})
job = r.json()
using var client = new HttpClient();
var payload = new { email = "you@example.com", token, imageUrl };
var res = await client.PostAsJsonAsync("https://api.optipic.ir/api?action=v1-create", payload);
var job = await res.Content.ReadFromJsonAsync<JobResponse>();

محدودیت آدرس IP (اختیاری)

اگر در داشبورد یک یا چند آدرس IP به لیست سفید اضافه کنید، درخواست‌ها فقط از همان آدرس‌ها پذیرفته می‌شوند. چون پلاگین از طریق همین سایت واسط صدا می‌زند، «آدرس تماس‌گیرنده» همان IP خروجی سرور میزبان سایت است، نه IP بازدیدکننده — اگر این قابلیت را فعال می‌کنید، IP سرور میزبان را وایت‌لیست کنید.

ساختار خطاها

خروجی خطای همه‌ی endpointها یک شکل ثابت دارد — منطق برنامه‌تان را بر اساس errorKey بنویسید (پایدار و ثابت)، نه error (که ممکن است متنش تغییر کند).

{
  "error": "Invalid API credentials.",
  "errorKey": "api_token_invalid"
}

محدودیت‌ها و بودجه

حجم فایل — حداکثر ۵۰ مگابایت برای هر فایل، چه در ساخت job تکی، چه هر آدرس داخل یک batch.
اندازه‌ی batch — حداکثر ۵۰ آدرس در هر درخواست ساخت batch.
budgetLimit / budgetUsed — هر حساب یک سقف مصرف تصویر دارد (در تب Account status داشبورد قابل مشاهده است). هر job که با موفقیت ساخته شود (هر آدرس داخل یک batch هم جداگانه) یک واحد از این بودجه کم می‌کند. با پر شدن بودجه، هر تلاش برای ساخت job جدید با 401 budget_exceeded رد می‌شود.
محدودیت روزانه‌ی رایگان سایت (بر اساس IP) فقط روی فرم آپلود بدون ثبت‌نام اعمال می‌شود، نه روی این API احرازشده — تنها سقف واقعی برای ترافیک پلاگین، همان بودجه‌ی حساب است.

ساخت یک job

POST /api?action=v1-create

یک تصویر را بهینه می‌کند — یا با آپلود مستقیم فایل، یا با دادن آدرس یک تصویر که خودمان دانلودش می‌کنیم. این دو حالت با هم قابل ترکیب نیستند.

A) آپلود مستقیم فایل

به‌صورت multipart/form-data.

فیلدالزامی؟توضیح
emailبله
tokenبله
fileبلهفایل تصویر
qualityاختیاریعدد صحیح ۱ تا ۱۰۰
losslessاختیاریtrue/false
convertToاختیاری"webp" یا "avif"
maxWidthاختیاریعدد صحیح مثبت
maxHeightاختیاریعدد صحیح مثبت
resizeFitBothاختیاریپیش‌فرض true؛ فقط وقتی هر دو بعد تنظیم شده باشند معنا دارد
fixCmykاختیاریتبدیل CMYK/YCCK به RGB (فقط JPEG)
curl -X POST "https://api.optipic.ir/api?action=v1-create" \
  -F "email=you@example.com" \
  -F "token=sk_live_51H8x...redacted" \
  -F "quality=75" \
  -F "file=@/path/to/photo.jpg"
$ch = curl_init('https://api.optipic.ir/api?action=v1-create');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => [
        'email' => 'you@example.com',
        'token' => $token,
        'quality' => 75,
        'file' => new CURLFile('/path/to/photo.jpg'),
    ],
    CURLOPT_RETURNTRANSFER => true,
]);
$job = json_decode(curl_exec($ch), true);
curl_close($ch);
const form = new FormData();
form.append('email', email);
form.append('token', token);
form.append('quality', '75');
form.append('file', fileInput.files[0]);

const res = await fetch('https://api.optipic.ir/api?action=v1-create', { method: 'POST', body: form });
const job = await res.json();
import requests

with open('photo.jpg', 'rb') as f:
    r = requests.post('https://api.optipic.ir/api?action=v1-create', data={
        'email': 'you@example.com',
        'token': token,
        'quality': 75,
    }, files={'file': f})
job = r.json()
using var client = new HttpClient();
using var form = new MultipartFormDataContent();
form.Add(new StringContent("you@example.com"), "email");
form.Add(new StringContent(token), "token");
form.Add(new StringContent("75"), "quality");
form.Add(new StreamContent(File.OpenRead("photo.jpg")), "file", "photo.jpg");

var res = await client.PostAsync("https://api.optipic.ir/api?action=v1-create", form);
var job = await res.Content.ReadFromJsonAsync<JobResponse>();

B) بهینه‌سازی از روی آدرس

به‌صورت JSON، همون فیلدهای بالا به‌علاوه‌ی imageUrl به‌جای file.

curl -X POST "https://api.optipic.ir/api?action=v1-create" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "you@example.com",
    "token": "sk_live_51H8x...redacted",
    "imageUrl": "https:\/\/example.com\/photo.jpg",
    "quality": 75,
    "convertTo": "webp"
}'
$payload = json_encode([
    'email' => 'you@example.com',
    'token' => $token,
    'imageUrl' => $imageUrl,
    'quality' => 75,
    'convertTo' => 'webp',
]);

$ch = curl_init('https://api.optipic.ir/api?action=v1-create');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
]);
$job = json_decode(curl_exec($ch), true);
curl_close($ch);
const res = await fetch('https://api.optipic.ir/api?action=v1-create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email, token, imageUrl, quality: 75, convertTo: 'webp' }),
});
const job = await res.json();
import requests

r = requests.post('https://api.optipic.ir/api?action=v1-create', json={
    'email': 'you@example.com',
    'token': token,
    'imageUrl': 'https://example.com/photo.jpg',
    'quality': 75,
})
job = r.json()
using var client = new HttpClient();
var payload = new {
    email = "you@example.com", token,
    imageUrl = "https://example.com/photo.jpg",
    quality = 75, convertTo = "webp",
};
var res = await client.PostAsJsonAsync("https://api.optipic.ir/api?action=v1-create", payload);
var job = await res.Content.ReadFromJsonAsync<JobResponse>();
201 پاسخ موفق
{
    "id": "6a8b1916-0f6b-4577-b7ec-7bb2fcfe06a4",
    "status": "Pending",
    "originalFileName": "photo.jpg",
    "inputSize": 70906,
    "outputSize": null,
    "savedPercent": null,
    "convertTo": null,
    "errorMessage": null,
    "createdAt": "2026-08-21T07:14:36.10Z",
    "completedAt": null,
    "downloadUrl": null,
    "alreadyOptimal": null,
    "outputWidth": null,
    "outputHeight": null
}

مقدار id را نگه دارید — برای پیگیری وضعیت لازمش دارید.

خطاهای ممکن: 401 api_token_invalid، 401 budget_exceeded، 400 invalid_request، 400 unsupported_file_type، 400 file_too_large، و برای حالت B: 400 invalid_url / 400 ssrf_blocked / 400 download_failed.

گرفتن وضعیت job

GET /api?action=v1-status&id=<jobId>&email=<email>&token=<token>

همان شکل پاسخ ساخت job را برمی‌گرداند، به‌روزشده. مالکیت بررسی می‌شود — job ساخته‌شده با اطلاعات یک حساب دیگر، با ۴۰۴ رد می‌شود (نه ۴۰۳، تا نشود با حدس زدن id فهمید یک job واقعاً وجود دارد یا نه).

curl "https://api.optipic.ir/api?action=v1-status&id=6a8b1916-0f6b-4577-b7ec-7bb2fcfe06a4&email=you@example.com&token=sk_live_51H8x...redacted"
$query = http_build_query([
    'id' => $jobId,
    'email' => 'you@example.com',
    'token' => $token,
]);

$ch = curl_init('https://api.optipic.ir/api?action=v1-status&' . $query);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$job = json_decode(curl_exec($ch), true);
curl_close($ch);
const url = new URL('https://api.optipic.ir/api');
url.searchParams.set('action', 'v1-status');
url.searchParams.set('id', jobId);
url.searchParams.set('email', email);
url.searchParams.set('token', token);
const job = await (await fetch(url)).json();
r = requests.get('https://api.optipic.ir/api', params={
    'action': 'v1-status', 'id': job_id, 'email': email, 'token': token,
})
job = r.json()
using var client = new HttpClient();
var url = "https://api.optipic.ir/api?action=v1-status" +
    $"&id={jobId}&email={email}&token={token}";
var job = await client.GetFromJsonAsync<JobResponse>(url);

status یکی از این چهار مقدار است: "Pending"، "Processing"، "Done"، یا "Failed". تا رسیدن به یکی از این دو حالت آخر پیگیری کنید.

200 پاسخ — وقتی آماده شد
{
    "id": "6a8b1916-0f6b-4577-b7ec-7bb2fcfe06a4",
    "status": "Done",
    "originalFileName": "photo.jpg",
    "inputSize": 70906,
    "outputSize": 42905,
    "savedPercent": 39.5,
    "convertTo": "webp",
    "errorMessage": null,
    "createdAt": "2026-08-21T07:14:36.10Z",
    "completedAt": "2026-08-21T07:14:39.14Z",
    "downloadUrl": "/api?action=download&id=6a8b1916-0f6b-4577-b7ec-7bb2fcfe06a4",
    "alreadyOptimal": false,
    "outputWidth": 700,
    "outputHeight": 525
}
downloadUrl یک مسیر نسبی به همین سایت است — همیشه دامنه‌ی خودتان را جلوی آن بگذارید، هرگز به‌عنوان آدرس مستقل فرض نکنید. وقتی alreadyOptimal: true باشد، یعنی فایل ورودی از قبل به بهترین حالت ممکن رسیده بوده — دانلود همچنان کار می‌کند و همان فایل اصلی را برمی‌گرداند.

دانلود نتیجه

GET /api?action=download&id=<jobId>

بدون نیاز به احراز هویت — خودِ شناسه‌ی غیرقابل‌حدس job کنترل دسترسی را انجام می‌دهد، مثل بقیه‌ی لینک‌های دانلود این سایت. فایل را با Content-Type و نام واقعی فایل استریم می‌کند. فقط وقتی job به "Done" رسیده باشد کار می‌کند.

curl -o result.jpg "https://api.optipic.ir/api?action=download&id=6a8b1916-0f6b-4577-b7ec-7bb2fcfe06a4"
$ch = curl_init('https://api.optipic.ir/api?action=download&id=' . $jobId);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$bytes = curl_exec($ch);
curl_close($ch);
file_put_contents('result.jpg', $bytes);
const blob = await (await fetch('https://api.optipic.ir/api?action=download&id=' + jobId)).blob();
r = requests.get('https://api.optipic.ir/api', params={'action': 'download', 'id': job_id})
with open('result.jpg', 'wb') as f:
    f.write(r.content)
using var client = new HttpClient();
var bytes = await client.GetByteArrayAsync(
    $"https://api.optipic.ir/api?action=download&id={jobId}");
await File.WriteAllBytesAsync("result.jpg", bytes);

ساخت یک batch

POST /api?action=v1-batch-create

یک job جدا به‌ازای هر آدرس در لیست می‌سازد (حداکثر ۵۰ تا) — مناسب برای «همه‌ی کتابخانه‌ی رسانه را یک‌جا بهینه کن». فقط با آدرس کار می‌کند؛ آپلود فایل در این حالت پشتیبانی نمی‌شود.

فیلدالزامی؟توضیح
emailبله
tokenبله
urlsبلهآرایه‌ای از آدرس‌ها، حداکثر ۵۰ تا
quality, lossless, convertTo, maxWidth, maxHeight, resizeFitBoth, fixCmykاختیاریدقیقاً مثل ساخت job تکی — روی همه‌ی آدرس‌های لیست یکسان اعمال می‌شود.
curl -X POST "https://api.optipic.ir/api?action=v1-batch-create" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "you@example.com",
    "token": "sk_live_51H8x...redacted",
    "urls": [
        "https:\/\/example.com\/a.jpg",
        "https:\/\/example.com\/b.jpg"
    ],
    "quality": 75
}'
$payload = json_encode([
    'email' => 'you@example.com',
    'token' => $token,
    'urls' => $urls,
    'quality' => 75,
]);

$ch = curl_init('https://api.optipic.ir/api?action=v1-batch-create');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
    CURLOPT_RETURNTRANSFER => true,
]);
$batch = json_decode(curl_exec($ch), true);
curl_close($ch);
const res = await fetch('https://api.optipic.ir/api?action=v1-batch-create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ email, token, urls, quality: 75 }),
});
const batch = await res.json();
r = requests.post('https://api.optipic.ir/api?action=v1-batch-create', json={
    'email': 'you@example.com', 'token': token, 'urls': urls, 'quality': 75,
})
batch = r.json()
using var client = new HttpClient();
var payload = new { email = "you@example.com", token, urls, quality = 75 };
var res = await client.PostAsJsonAsync("https://api.optipic.ir/api?action=v1-batch-create", payload);
var batch = await res.Content.ReadFromJsonAsync<BatchCreateResponse>();
201 پاسخ
{
    "batchId": "a1ddf93e-9223-402e-b8c5-fbaa452117b1",
    "items": [
        {
            "url": "https://example.com/a.jpg",
            "jobId": "2388bd8b-208e-475a-a2ad-0aae0a71d225",
            "error": null,
            "errorKey": null
        },
        {
            "url": "not-a-valid-url",
            "jobId": null,
            "error": "Invalid URL.",
            "errorKey": "invalid_url"
        }
    ]
}
یک آدرس خراب کل درخواست را نمی‌شکند. هر آیتم نتیجه‌ی جدا دارد — آیتمی که jobId: null دارد هیچ‌وقت واقعاً job نشده (دلیلش را در errorKey همان آیتم ببینید)، بقیه‌ی آیتم‌ها به‌طور معمول پردازش می‌شوند.

گرفتن وضعیت یک batch

GET /api?action=v1-batch-status&id=<batchId>&email=<email>&token=<token>

وضعیت همه‌ی jobهای زیرمجموعه‌ی این batch را یک‌جا برمی‌گرداند — به‌جای پیگیری تک‌تک، فقط یک id را بررسی کنید.

curl "https://api.optipic.ir/api?action=v1-batch-status&id=a1ddf93e-9223-402e-b8c5-fbaa452117b1&email=you@example.com&token=sk_live_51H8x...redacted"
$query = http_build_query([
    'id' => $batchId,
    'email' => 'you@example.com',
    'token' => $token,
]);

$ch = curl_init('https://api.optipic.ir/api?action=v1-batch-status&' . $query);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$batch = json_decode(curl_exec($ch), true);
curl_close($ch);
const url = new URL('https://api.optipic.ir/api');
url.searchParams.set('action', 'v1-batch-status');
url.searchParams.set('id', batchId);
url.searchParams.set('email', email);
url.searchParams.set('token', token);
const batch = await (await fetch(url)).json();
r = requests.get('https://api.optipic.ir/api', params={
    'action': 'v1-batch-status', 'id': batch_id, 'email': email, 'token': token,
})
batch = r.json()
using var client = new HttpClient();
var url = "https://api.optipic.ir/api?action=v1-batch-status" +
    $"&id={batchId}&email={email}&token={token}";
var batch = await client.GetFromJsonAsync<BatchStatusResponse>(url);
200 پاسخ
{
    "batchId": "a1ddf93e-9223-402e-b8c5-fbaa452117b1",
    "createdAt": "2026-08-21T08:12:01.47Z",
    "requestedCount": 3,
    "jobs": [
        {
            "id": "2388bd8b-208e-475a-a2ad-0aae0a71d225",
            "status": "Done",
            "downloadUrl": "/api?action=download&id=2388bd8b-208e-475a-a2ad-0aae0a71d225",
            "savedPercent": 39.5
        },
        {
            "id": "dc0cf9e1-02ad-4110-ba71-5bcbb8eea912",
            "status": "Processing",
            "downloadUrl": null,
            "savedPercent": null
        }
    ]
}

jobs شامل هر شیء کامل job (همان شکل v1-status) برای هر آدرسی است که واقعاً به یک job تبدیل شد — این آرایه می‌تواند کوتاه‌تر از requestedCount باشد اگر بعضی آدرس‌ها از ابتدا رد شده باشند (به items در پاسخ ساخت batch نگاه کنید). تا وقتی همه‌ی jobها به "Done" یا "Failed" برسند پیگیری کنید.

کدهای خطا

لیست کامل errorKeyهایی که این API برمی‌گردونه.

401 api_token_invalid

ایمیل یا توکن اشتباه/خالی است، یا مالکیت job/batch مطابقت ندارد.

401 budget_exceeded

بودجه‌ی حساب تمام شده — از داشبورد بودجه را افزایش دهید.

401 ip_not_whitelisted

IP تماس‌گیرنده در لیست سفید حساب نیست.

400 invalid_request

فیلد لازم غایب است یا مقدار یک تنظیم نامعتبر است.

400 unsupported_file_type

پسوند یا نوع فایل پشتیبانی نمی‌شود (فقط JPG/PNG/WebP/AVIF).

400 file_too_large

فایل از سقف ۵۰ مگابایت بیشتر است.

400 invalid_url

آدرس داده‌شده معتبر نیست (فقط در حالت URL).

400 ssrf_blocked

آدرس به یک شبکه‌ی خصوصی/داخلی اشاره می‌کند و مسدود شده.

400 download_failed

آدرس داده‌شده پاسخ نداد یا قابل‌دسترس نبود.

404 not_found

شناسه‌ی job یا batch وجود ندارد، یا متعلق به حساب دیگری است.

502 unexpected_error

خطای موقت سرویس — قابل تلاش مجدد است.

نکات عملی برای پلاگین

بازه‌ی پیگیری — قانون سخت‌گیرانه‌ای اعمال نمی‌شود؛ فرانت خود سایت هر ۱.۵ ثانیه بررسی می‌کند، بازه‌ای مشابه منطقی است.
timeout — خودِ فراخوانی ساخت job (تکی) معمولاً چند ثانیه طول می‌کشد؛ بهینه‌سازی واقعی به‌صورت async انجام می‌شود، پس باید پیگیری کنید. ساخت batch می‌تواند تا ۲ دقیقه طول بکشد (هر آدرس هم‌زمان دانلود می‌شود) — timeout کلاینت را متناسب تنظیم کنید.
چرخش کلید — وقتی کاربر توکن را از داشبورد بازتولید می‌کند، توکن قبلی فوراً از کار می‌افتد — هر مقداری که کاربر در تنظیمات پلاگین وارد می‌کند را دقیقاً همان‌طور ذخیره/استفاده کنید، هیچ توکنی را در کد پلاگین ثابت (hardcode) نکنید.
سوالی مونده؟ از داشبورد با تیم پشتیبانی در تماس باشید — تب Support.