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

با این API از n8n، Make، اسکریپت خودتان یا هر ابزار دیگری آیتم بسازید، پیش‌نویس بگیرید، تأیید و زمان‌بندی کنید و رویدادهای برندتان را با وب‌هوک بشنوید. نسخهٔ v1 پایدار است.

کلید و احراز هویت

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

curl https://uttapost.ir/api/v1/me \
  -H "Authorization: Bearer up_live_..."

پاسخ‌ها همیشه JSON‌اند: موفق {"data": …, "message": …} و خطا {"message": …, "code": …}. مبلغ‌ها همه تومان هستند. سند ماشین‌خوان: https://uttapost.ir/api/v1/openapi.json (OpenAPI 3.1، بدون نیاز به کلید).

در n8n: گرهٔ HTTP Request ← Authentication: Generic Credential Type ← Header Auth ← نام Authorization و مقدار Bearer up_live_…. یا سند OpenAPI بالا را مستقیم وارد کنید.

دسترسی‌ها

هر کلید یک یا چند دسترسی دارد. «خواندن» همیشه هست. دسترسی کلید فقط سقف کار است؛ پلن برند هم باید اجازه بدهد، درست مثل پنل.

دسترسیچه می‌شود کردشرط برند
read خواندنبرند، آیتم‌ها، دانش برند، پیش‌نویس‌ها، پست‌ها و آمار، کیف پول، قانون‌ها، فهرست وب‌هوک‌ها—
draft ساخت پیش‌نویسافزودن آیتم، پیش‌نمایش و ساخت پیش‌نویس، رد پیش‌نویسپلن فعال یا اعتبار (مثل پنل)
publish انتشارتأیید و انتشار، زمان‌بندی، ساخت ترکیب هفتگی محتوا (قانون خودکار)، ساخت و ویرایش و حذف وب‌هوکپلن فعال

تأیید هزینه (بدون غافلگیری)

هر کاری که اعتبار خرج می‌کند دو مرحله دارد و در مرحلهٔ اول هیچ پولی کم نمی‌شود:

  1. POST /drafts/generate پیش‌نمایش (عنوان، فهرست آیتم‌ها، هزینه به تومان) و یک confirm_token برمی‌گرداند؛ ۳۰ دقیقه اعتبار دارد و یک‌بار مصرف است.
  2. POST /confirm با همان token کار را انجام می‌دهد و هزینه را کم می‌کند.
curl -X POST https://uttapost.ir/api/v1/drafts/generate -H "Authorization: Bearer up_live_..." \
  -H "Content-Type: application/json" -d '{"item_ids":[12,15],"format":"post"}'
# → {"data":{"cost":24000,"confirm_token":"81.9f2c…","lines":[…]}}

curl -X POST https://uttapost.ir/api/v1/confirm -H "Authorization: Bearer up_live_..." \
  -H "Content-Type: application/json" -d '{"confirm_token":"81.9f2c…"}'

هر کلید سقف خرج روزانه (به وقت تهران) دارد؛ پیش‌فرض ۲۰۰,۰۰۰ تومان و ۰ یعنی هیچ خرجی. token فقط با همان کلیدی که پیش‌نمایش را گرفته کار می‌کند.

مسیرها

روشمسیرکاردسترسی
GET /api/v1/openapi.json این سند (OpenAPI 3.1) بدون کلید
GET /api/v1/me برند و کلید جاری خواندن
GET /api/v1/items فهرست آیتم‌های کتابخانه خواندن
POST /api/v1/items افزودن آیتم
نیاز به scope «draft» و پلن یا اعتبار.
ساخت پیش‌نویس
GET /api/v1/items/{id} یک آیتم خواندن
GET /api/v1/kb دانش برند خواندن
GET /api/v1/drafts فهرست پیش‌نویس‌ها خواندن
POST /api/v1/drafts/generate پیش‌نمایش ساخت پیش‌نویس (بدون هزینه)
هیچ هزینه‌ای کم نمی‌شود؛ هزینه با POST /confirm و confirm_token گرفته می‌شود.
ساخت پیش‌نویس
GET /api/v1/drafts/{id} یک پیش‌نویس خواندن
POST /api/v1/drafts/{id}/approve تأیید و انتشار انتشار
POST /api/v1/drafts/{id}/schedule تأیید و زمان‌بندی انتشار
POST /api/v1/drafts/{id}/reject رد پیش‌نویس ساخت پیش‌نویس
POST /api/v1/confirm تأیید یک هزینه (یک‌بار مصرف)
token باید از همان کلیدی باشد که پیش‌نمایش را گرفته.
ساخت پیش‌نویس
GET /api/v1/posts پست‌های منتشرشده و در جریان خواندن
GET /api/v1/posts/stats آمار پست‌ها خواندن
GET /api/v1/wallet اعتبار کیف پول خواندن
GET /api/v1/channels کانال‌های وصل‌شده خواندن
GET /api/v1/rules قانون‌های خودکار خواندن
GET /api/v1/webhooks وب‌هوک‌ها خواندن
POST /api/v1/webhooks ساخت وب‌هوک انتشار
PATCH /api/v1/webhooks/{id} ویرایش وب‌هوک انتشار
DELETE /api/v1/webhooks/{id} حذف وب‌هوک انتشار
POST /api/v1/webhooks/{id}/test ping آزمایشی امضاشده انتشار
GET /api/v1/topics موضوع‌های محتوای برند
موضوع‌هایی که پست‌های «مرتبط با کسب‌وکار» و ترکیب هفتگی از آن‌ها می‌سازند. فقط خواندنی.
خواندن
POST /api/v1/rules/mix پیشنهاد ترکیب هفتگی محتوا (بدون ساخت)
چیزی ساخته یا کم نمی‌شود؛ با POST /confirm و confirm_token ترکیب ساخته می‌شود. ریلز و ویدیو در ترکیب نیست.
انتشار
POST /api/v1/assistant/ask یک پرسش از دستیار برند
دستیار به‌جای سازندهٔ کلید و در برند همان کلید پاسخ می‌دهد و از سقف روزانهٔ دستیار کم می‌کند. اگر کاری پیشنهاد کند، card.confirm_token می‌آید و تا POST /confirm چیزی اجرا نمی‌شود.
ساخت پیش‌نویس

وب‌هوک

در «اتصال‌ها و API ← وب‌هوک‌ها» آدرس https بدهید و رویدادها را انتخاب کنید. هر رویداد با یک POST امضاشده می‌رسد. آدرس‌های داخلی و ریدایرکت پذیرفته نمی‌شوند و پاسخ باید ظرف ۱۰ ثانیه برسد.

رویدادیعنی
draft.createdپیش‌نویس ساخته شد
approval.requestedپیش‌نویس منتظر تأیید است
post.publishedپست منتشر شد
post.failedانتشار پست شکست خورد
handoff.openedگفتگو به شما سپرده شد
credit.lowاعتبار رو به اتمام است
rule.pausedیک قانون خودکار متوقف شد

کپشن با draft.created نمی‌رسد. این رویداد همان لحظهٔ شروع ساخت می‌آید و در آن draft.status برابر generating و draft.caption برابر null است. وقتی متن آماده شد و پیش‌نویس منتظر تأیید ماند، رویداد approval.requested می‌رسد و draft.caption در آن پر است؛ یا همان موقع پیش‌نویس را با GET /api/v1/drafts/{id} بخوانید.

بدنه و هدرها

POST https://your-endpoint.example/hook
X-UttaPost-Event: post.published
X-UttaPost-Delivery: 7b0c…            (شناسهٔ یکتا؛ برای جلوگیری از پردازش تکراری)
X-UttaPost-Signature: t=1767000000,v1=3f9a…

{"id":"evt_…","event":"post.published","created_at":"…","brand":{"id":3,"name":"…"},
 "data":{"post":{"content_id":41,"network":"instagram","post_url":"…"}}}

تأیید امضا

امضا HMAC-SHA256 رشتهٔ t + "." + بدنهٔ خام با secret وب‌هوک است. باید بایت‌های خام بدنه را امضا کنید؛ اگر بدنه را parse و دوباره stringify کنید (یا فارسی و اسلش‌ها دوباره escape شوند) امضا نمی‌خواند. در n8n گرهٔ Webhook را روی گزینهٔ Raw Body بگذارید. فاصلهٔ زمانی بیش از ۵ دقیقه را رد کنید.

Node.js / n8n

const crypto = require('crypto');

// rawBody: the exact bytes received (a Buffer or the raw string), never JSON.stringify(req.body).
function verifyUttaPost(secret, header, rawBody, toleranceSec = 300) {
    const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || '');
    if (!m) return false;
    if (Math.abs(Date.now() / 1000 - Number(m[1])) > toleranceSec) return false;
    const h = crypto.createHmac('sha256', secret);
    h.update(m[1] + '.');
    h.update(rawBody);
    const expected = Buffer.from(h.digest('hex'));
    const given = Buffer.from(m[2]);
    return expected.length === given.length && crypto.timingSafeEqual(expected, given);
}

PHP

function uttapost_verify(string $secret, string $header, string $rawBody, int $tolerance = 300): bool
{
    if (! preg_match('/^t=(\d+),v1=([0-9a-f]{64})$/', $header, $m)) {
        return false;
    }
    if (abs(time() - (int) $m[1]) > $tolerance) {
        return false;
    }

    return hash_equals(hash_hmac('sha256', $m[1].'.'.$rawBody, $secret), $m[2]);
}

تلاش دوباره

اگر پاسخ ۲xx نیامد، ارسال بعد از ۱ دقیقه، ۵ دقیقه، ۳۰ دقیقه، ۲ ساعت، ۶ ساعت دوباره تلاش می‌شود و بعد از آن شکست‌خورده ثبت می‌شود. بعد از ۲۰ شکست پشت‌سرهم وب‌هوک خاموش می‌شود و به مالک اطلاع داده می‌شود. دکمهٔ «تست» یک رویداد ping امضاشده می‌فرستد.

خطاها و سقف‌ها

کدیعنی
401کلید نامعتبر، باطل یا منقضی (invalid_api_key، key_revoked، key_expired)
403دسترسی کلید یا پلن برند کافی نیست (insufficient_scope، subscription_required)؛ یا هدر X-Account-Id برندی غیر از برند کلید را نام برده
402اعتبار کیف پول یا سقف روزانهٔ کلید کافی نیست (insufficient_credit، daily_cap_reached، spending_disabled)
404 / 422مورد پیدا نشد / داده نامعتبر
409 / 410confirm_token قبلاً استفاده شده یا کلید مشغول است / مهلتش تمام شده
429از سقف نرخ کلید بیشتر شد؛ هدر Retry-After ثانیه‌های انتظار را می‌گوید
503API یا وب‌هوک موقتاً از طرف مدیریت خاموش شده (api_disabled)

سقف نرخ هر کلید پیش‌فرض ۶۰ درخواست در دقیقه است (تا ۶۰۰). هر کلید می‌تواند تاریخ انقضا داشته باشد و هر لحظه باطل می‌شود. هر فراخوانی در پنل (آخرین ۱۰۰ مورد) ثبت می‌شود.

پایداری: مسیرها و فیلدهای v1 عوض نمی‌شوند؛ تغییر ناسازگار فقط با /api/v2 می‌آید و پیش از آن اعلام می‌شود.