کلید و احراز هویت
کلید را در پنل بسازید: منو ← اتصالها و 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 انتشار | تأیید و انتشار، زمانبندی، ساخت ترکیب هفتگی محتوا (قانون خودکار)، ساخت و ویرایش و حذف وبهوک | پلن فعال |
تأیید هزینه (بدون غافلگیری)
هر کاری که اعتبار خرج میکند دو مرحله دارد و در مرحلهٔ اول هیچ پولی کم نمیشود:
POST /drafts/generateپیشنمایش (عنوان، فهرست آیتمها، هزینه به تومان) و یکconfirm_tokenبرمیگرداند؛ ۳۰ دقیقه اعتبار دارد و یکبار مصرف است.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 / 410 | confirm_token قبلاً استفاده شده یا کلید مشغول است / مهلتش تمام شده |
| 429 | از سقف نرخ کلید بیشتر شد؛ هدر Retry-After ثانیههای انتظار را میگوید |
| 503 | API یا وبهوک موقتاً از طرف مدیریت خاموش شده (api_disabled) |
سقف نرخ هر کلید پیشفرض ۶۰ درخواست در دقیقه است (تا ۶۰۰). هر کلید میتواند تاریخ انقضا داشته باشد و هر لحظه باطل میشود. هر فراخوانی در پنل (آخرین ۱۰۰ مورد) ثبت میشود.
پایداری: مسیرها و فیلدهای v1 عوض نمیشوند؛ تغییر ناسازگار فقط با /api/v2 میآید و پیش از آن اعلام میشود.