ننیوارامستندات توسعه‌دهندگان
پروتکل انتشار نسخه ۱

سایت خودتان را به نیوارا وصل کنید

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

اتصالسایت با کد اتصال یک کلید اختصاصی می‌گیرد.
تأییدنیوارا با یک درخواست امضاشده مالکیت دامنه را بررسی می‌کند.
انتشارمحتوا با امضای HMAC به سایت فرستاده می‌شود.
به‌روزرسانیویرایش‌ها و انتشار زمان‌بندی‌شده روی همان پست اعمال می‌شود.
آدرس پایه نیوارا
https://studio.manova.ir
قالب داده
JSON با UTF-8
احراز هویت
HMAC-SHA256 با کلید جدا برای هر سایت

شروع سریع

  1. در پنل نیوارا، صفحه «سایت‌ها»، سایت را با پلتفرم CMS اختصاصی و دامنه دقیق آن اضافه کنید و کد اتصال (NV-XXXXXXXX) را بردارید.
  2. سه endpoint زیر را روی سایت پیاده کنید، زیر یک آدرس پایه مثل https://example.com/api/nivara:
    • GET /status
    • POST /publish
    • POST /disconnect
  3. در بخش مدیریت CMS، کد اتصال را بگیرید و درخواست اتصال و سپس تأیید را بفرستید.
  4. از این به بعد همه درخواست‌های نیوارا را با بررسی امضا بپذیرید.

۱. اتصال

POSThttps://studio.manova.ir/api/sites/connect

سایت (از سمت سرور، نه مرورگر) کد اتصال را می‌فرستد و یک کلید مخفی اختصاصی دریافت می‌کند.

فیلدنوعتوضیح
codestringکد اتصال از پنل نیوارا، به شکل NV- و ۸ کاراکتر هگز.
siteUrlURLآدرس اصلی سایت. دامنه (و پورت) باید دقیقاً همان دامنه ثبت‌شده در نیوارا باشد؛ www نادیده گرفته می‌شود.
endpointURLآدرس پایه سه endpoint شما. باید روی همان دامنه یا یک زیردامنه آن باشد.
platform"custom" | "wordpress"باید با پلتفرمی که سایت در پنل با آن ثبت شده یکی باشد.
software, version, siteNamestring (اختیاری)برای نمایش در پنل.
curl -X POST https://studio.manova.ir/api/sites/connect \
  -H "Content-Type: application/json" \
  -d '{
    "code": "NV-1A2B3C4D",
    "siteUrl": "https://example.com",
    "endpoint": "https://example.com/api/nivara",
    "platform": "custom",
    "software": "my-cms",
    "version": "2.3",
    "siteName": "My Site"
  }'

# پاسخ موفق:
# {"ok":true,"siteId":"6f1c…","secret":"q8Zr…","verifyUrl":"https://studio.manova.ir/api/sites/verify"}

curl -X POST https://studio.manova.ir/api/sites/verify \
  -H "Content-Type: application/json" \
  -d '{"siteId":"6f1c…"}'
# {"ok":true}

پاسخ موفق شامل siteId و secret است. هر دو را امن ذخیره کنید؛ کلید فقط همین یک بار برگردانده می‌شود. اتصال دوباره با همان کد، کلید جدید می‌سازد و کلید قبلی از کار می‌افتد.

محدودیت: حداکثر ۲۰ درخواست اتصال در ۱۵ دقیقه از هر آی‌پی.

۲. تأیید مالکیت

POSThttps://studio.manova.ir/api/sites/verify

بلافاصله بعد از ذخیره کلید، این درخواست را با بدنه {"siteId": "…"} بفرستید. نیوارا در همان لحظه یک درخواست امضاشده به GET {endpoint}/status می‌زند. سایت فقط وقتی «متصل» می‌شود که این درخواست با موفقیت جواب بگیرد؛ بنابراین کسی که فقط کد اتصال را دارد نمی‌تواند سرور دیگری را جای سایت شما وصل کند.

پاسخ: {"ok": true} یا {"ok": false, "error": "…"} با کد ۴۰۰.

امضای درخواست‌ها

هر درخواستی که نیوارا به سایت می‌فرستد این هدرها را دارد:

هدرمقدار
X-Nivara-Siteهمان siteId
X-Nivara-Timestampزمان یونیکس به ثانیه
X-Nivara-Signatureامضای هگز (۶۴ کاراکتر)
X-Nivara-Protocol1

امضا این‌طور ساخته می‌شود:

signature = hex( HMAC-SHA256( secret, "{timestamp}.{METHOD}.{path}.{body}" ) )
  • METHOD: با حروف بزرگ، مثل GET یا POST.
  • path: مسیر نسبت به endpoint، یعنی /status، /publish یا /disconnect.
  • body: متن خام بدنه درخواست، دقیقاً همان بایت‌ها (برای GET رشته خالی). امضا را قبل از تبدیل JSON بررسی کنید.

سایت باید درخواست را رد کند (کد ۴۰۱) اگر: X-Nivara-Site متفاوت باشد، اختلاف زمان بیش از ۳۰۰ ثانیه باشد، امضا درست نباشد (مقایسه با زمان ثابت)، یا همین امضا در ۱۰ دقیقه اخیر دیده شده باشد.

import { createHmac, timingSafeEqual } from "node:crypto";

const MAX_SKEW = 300;            // seconds
const seen = new Map();          // signature -> expiry (use Redis/DB if you run several servers)

/**
 * req: { method, path, headers, rawBody }
 *   path    = path relative to your endpoint, e.g. "/publish"
 *   rawBody = the exact request body string ("" for GET). Verify BEFORE JSON.parse.
 */
export function verifyNivara(req, { siteId, secret }) {
  const ts = Number(req.headers["x-nivara-timestamp"]);
  const sig = String(req.headers["x-nivara-signature"] || "");
  if (req.headers["x-nivara-site"] !== siteId) return "wrong site";
  if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > MAX_SKEW) return "expired";

  const expected = createHmac("sha256", secret)
    .update(`${ts}.${req.method}.${req.path}.${req.method === "GET" ? "" : req.rawBody}`)
    .digest("hex");
  const a = Buffer.from(expected), b = Buffer.from(sig);
  if (a.length !== b.length || !timingSafeEqual(a, b)) return "bad signature";

  const now = Date.now();
  for (const [k, exp] of seen) if (exp < now) seen.delete(k);
  if (seen.has(sig)) return "replayed";
  seen.set(sig, now + 2 * MAX_SKEW * 1000);
  return null; // OK
}
ساعت سرور را با NTP همگام نگه دارید؛ اختلاف ساعت بیش از ۵ دقیقه باعث رد شدن همه درخواست‌ها می‌شود.

GET /status

بررسی سلامت اتصال. هنگام تأیید و هر بار که کاربر در پنل «بررسی اتصال» بزند صدا زده می‌شود.

فیلد پاسخالزامیتوضیح
okبلهباید true باشد.
postCountخیرتعداد پست‌های منتشرشده؛ در پنل نمایش داده می‌شود.
software, version, siteNameخیربرای نمایش در پنل.
seoخیرنام افزونه سئو یا null.
HTTP/1.1 200 OK
Content-Type: application/json

{
  "ok": true,
  "software": "my-cms",
  "version": "2.3",
  "siteName": "My Site",
  "postCount": 128,
  "seo": null
}

POST /publish

ایجاد یا به‌روزرسانی یک پست. باید idempotent باشد: اولین بار که یک jobId می‌رسد پست بسازید و دفعات بعد (ویرایش، تولید دوباره، تبدیل پیش‌نویس به انتشار) همان پست را به‌روز کنید.

فیلدتوضیح
jobIdشناسه سفارش در نیوارا؛ کلید یکتای پست در سایت شما.
mode"publish" (انتشار عمومی) یا "draft" (پیش‌نویس برای بازبینی).
title, slug, excerptعنوان، اسلاگ انگلیسی و خلاصه.
htmlبدنه آماده با سرتیترها، لیست‌ها، جدول‌ها، تصاویر داخل متن، بخش پرسش‌های متداول و اسکیمای FAQ. شامل h1 نیست.
seometaTitle، metaDescription، focusKeyword و secondaryKeywords.
category, tagsنام دسته‌بندی و برچسب‌ها. اگر options.createCategories برابر false است دسته جدید نسازید.
featuredImage{ url, alt }؛ url ممکن است null باشد. تصویر را دانلود و در رسانه سایت ذخیره کنید.
inlineImagesتصاویر داخل متن: [{ url, alt, caption }]. همین آدرس‌ها در html هم آمده‌اند؛ می‌توانید دانلود و جایگزین کنید.
faqSchemaاسکیمای FAQPage برای JSON-LD (در html هم درج شده).
documentسند ساختاریافته کامل (پایین‌تر)، اگر می‌خواهید خودتان HTML بسازید.
POST /api/nivara/publish HTTP/1.1
Host: example.com
Content-Type: application/json
X-Nivara-Site: 6f1c2a4e-…
X-Nivara-Timestamp: 1791990000
X-Nivara-Signature: 9b1f0c…e4
X-Nivara-Protocol: 1

{
  "protocol": 1,
  "jobId": "a3d9b8c2-…",
  "mode": "publish",
  "title": "هاست وردپرس با هاست معمولی چه فرقی دارد؟",
  "slug": "wordpress-hosting-vs-shared-hosting",
  "excerpt": "…",
  "html": "<p>…</p><h2>…</h2>…",
  "seo": {
    "metaTitle": "…",
    "metaDescription": "…",
    "focusKeyword": "هاست وردپرس",
    "secondaryKeywords": ["…", "…"]
  },
  "category": "هاستینگ",
  "tags": ["وردپرس", "هاست"],
  "featuredImage": { "url": "https://studio.manova.ir/media/a3d9…/featured-1a2b3c.webp", "alt": "…" },
  "inlineImages": [{ "url": "https://studio.manova.ir/media/a3d9…/image-6-4d5e6f.webp", "alt": "…", "caption": "…" }],
  "faqSchema": { "@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [] },
  "options": { "createCategories": true },
  "document": { "title": "…", "blocks": [], "faq": [], "internalLinkIdeas": [] }
}
فیلد پاسخالزامیتوضیح
postIdبلهشناسه پست در سایت شما.
urlخیرنشانی پست؛ در پنل به کاربر نشان داده می‌شود.
status, postCountخیروضعیت پست و تعداد کل پست‌های منتشرشده.
warningsخیرآرایه‌ای از پیام‌های غیربحرانی (مثلاً «تصویر دانلود نشد»)؛ به کاربر نمایش داده می‌شود.

POST /disconnect

وقتی کاربر اتصال را در پنل قطع می‌کند فرستاده می‌شود (بدنه {}). siteId و secret را پاک کنید و {"ok": true} برگردانید. پنل کد اتصال جدیدی برای اتصال دوباره می‌سازد.

ساختار محتوا

فیلد document نسخه کامل و مستقل از پلتفرم محتواست: title، slug، metaTitle، metaDescription، focusKeyword، secondaryKeywords، excerpt، category، tags، featuredImage، blocks، faq و internalLinkIdeas. بدنه، آرایه‌ای از بلوک‌هاست:

{
  "type": "heading",        // heading | paragraph | list | quote | table | image
  "level": 2,               // headings only: 2 or 3
  "text": "…",              // heading, paragraph, quote (paragraphs may contain **bold**)
  "ordered": null,          // lists only
  "items": null,            // lists only: ["…", "…"]
  "headers": null,          // tables only: ["…", "…"]
  "rows": null,             // tables only: [["…", "…"], …]
  "alt": null,              // images only: Persian alt text
  "caption": null,          // images only
  "imagePrompt": null       // images only: English description used to generate the image
}

زمان‌بندی و تلاش دوباره

  • محتوای بدون زمان‌بندی بلافاصله بعد از تولید فرستاده می‌شود: اگر کاربر «انتشار خودکار» را روشن کرده باشد با mode: "publish" و در غیر این صورت "draft".
  • محتوای زمان‌بندی‌شده سر موعد با "publish" فرستاده می‌شود (دقت چند ثانیه).
  • اگر سایت خطا برگرداند یا در ۲۵ ثانیه پاسخ ندهد، نیوارا برای محتوای زمان‌بندی‌شده تا ۵ بار با فاصله دو دقیقه دوباره تلاش می‌کند. ارسال‌های دستی کاربر فوراً تکرار می‌شوند.
  • ریدایرکت دنبال نمی‌شود؛ endpoint را با آدرس نهایی (مثلاً https و بدون www اضافه) ثبت کنید.

خطاها

برای رد کردن درخواست، کد HTTP غیر 2xx و یک پیام برگردانید؛ متن message (یا error) عیناً به کاربر نیوارا نشان داده می‌شود، پس آن را خوانا بنویسید.

HTTP/1.1 401 Unauthorized
Content-Type: application/json

{ "message": "bad signature" }
خطای اتصالعلت
کد اتصال پیدا نشدکد اشتباه است یا سایت در پنل حذف شده.
این کد برای … است، اما درخواست از … آمدهsiteUrl با دامنه ثبت‌شده یکی نیست.
آدرس API باید روی همان دامنه سایت باشدendpoint روی دامنه دیگری است.
سایت پاسخ معتبری به بررسی اتصال ندادGET /status خطا داد، امضا را رد کرد یا ok: true برنگرداند.

نمونه کامل

یک گیرنده ساده که هر سه endpoint را پیاده می‌کند. نسخه اجرایی کامل (بدون وابستگی) را از اینجا دانلود کنید.

// Minimal receiver (Node.js, no dependencies). Full version: https://studio.manova.ir/downloads/custom-cms-receiver.mjs
import { createServer } from "node:http";
import { verifyNivara } from "./verify-nivara.js";   // the function from the "request signing" section

const BASE = "/api/nivara";
const config = await loadSetting("nivara");          // { siteId, secret } saved while connecting

createServer((req, res) => {
  let rawBody = "";
  req.on("data", c => (rawBody += c));
  req.on("end", async () => {
    const send = (status, json) => { res.writeHead(status, { "Content-Type": "application/json" }); res.end(JSON.stringify(json)); };
    const url = new URL(req.url, "http://x");
    if (!url.pathname.startsWith(BASE)) return send(404, { message: "not found" });
    const path = url.pathname.slice(BASE.length);       // "/status" | "/publish" | "/disconnect"

    const problem = verifyNivara({ method: req.method, path, headers: req.headers, rawBody }, config);
    if (problem) return send(401, { message: problem });

    if (req.method === "GET" && path === "/status")
      return send(200, { ok: true, software: "my-cms", version: "2.3", siteName: "My Site", postCount: await countPublished() });

    if (req.method === "POST" && path === "/publish") {
      const p = JSON.parse(rawBody);
      // Upsert by jobId: the same order always updates the same post.
      const post = await upsertPostByNivaraJob(p.jobId, {
        title: p.title, slug: p.slug, html: p.html, excerpt: p.excerpt,
        status: p.mode === "publish" ? "published" : "draft",
        metaTitle: p.seo.metaTitle, metaDescription: p.seo.metaDescription,
        category: p.category, tags: p.tags,
        featuredImage: p.featuredImage.url && await importImage(p.featuredImage.url, p.featuredImage.alt),
      });
      return send(200, { ok: true, postId: String(post.id), url: post.url, status: p.mode, postCount: await countPublished() });
    }

    if (req.method === "POST" && path === "/disconnect") {
      await deleteSetting("nivara");
      return send(200, { ok: true });
    }
    send(404, { message: "not found" });
  });
}).listen(8080);

وردپرس

برای وردپرس نیازی به برنامه‌نویسی نیست. پلاگین نیوارا را نصب کنید، در «تنظیمات، نیوارا» آدرس https://studio.manova.ir و کد اتصال را وارد کنید. پلاگین همین پروتکل را پیاده می‌کند و پست، دسته‌بندی، برچسب، تصویر شاخص و تصاویر داخل متن (در کتابخانه رسانه)، و فیلدهای Yoast یا Rank Math را تنظیم می‌کند.

REST API وردپرس (/wp-json/) باید از اینترنت در دسترس باشد. اگر افزونه امنیتی درخواست‌های ناشناس REST را می‌بندد، مسیر nivara/v1 را مجاز کنید.
API سفارش، نسخه ۱

سفارش محتوا از داخل CMS

علاوه بر دریافت محتوا، CMS متصل می‌تواند خودش در نیوارا سفارش ثبت کند: موجودی و قیمت را ببیند، سفارش بدهد، وضعیت را دنبال کند، دوباره تولید کند، تصویر بسازد، منتشر کند و کاربر را برای شارژ کیف پول به درگاه بفرستد. همه هزینه‌ها از کیف پول صاحب سایت در نیوارا کم می‌شود؛ CMS هیچ دسترسی مستقیمی به پول ندارد.

  • این قابلیت به‌صورت پیش‌فرض خاموش است. صاحب حساب باید در پنل نیوارا، کارت سایت، گزینه «سفارش محتوا از خود CMS» را روشن کند.
  • صاحب حساب می‌تواند سقف هزینه ماهانه (ماه شمسی) بگذارد؛ بدون سقف، محدودیتی جز موجودی کیف پول نیست.
  • محتوای سفارش‌ها مثل قبل از طریق POST /publish خود CMS تحویل داده می‌شود؛ پیگیری وضعیت اختیاری است.

امضای درخواست به نیوارا

همان siteId و secret اتصال و همان روش امضا، این بار در جهت برعکس. هر درخواست به https://studio.manova.ir/api/v1 باید هدرهای X-Nivara-Site، X-Nivara-Timestamp و X-Nivara-Signature را داشته باشد.

signature = hex( HMAC-SHA256( secret, "{timestamp}.{METHOD}.{path}.{body}" ) )
  • path: مسیر بعد از /api/v1 به‌همراه query string، مثلاً /jobs?status=draft&limit=20.
  • body: دقیقاً همان رشته JSON که می‌فرستید (برای GET رشته خالی). اول رشته را بسازید، امضا کنید، بعد همان را بفرستید.
  • زمان باید حداکثر ۵ دقیقه با ساعت نیوارا فاصله داشته باشد.
  • درخواست‌های تغییردهنده (POST، به‌جز /quote) با امضای تکراری اجرا نمی‌شوند و 409 duplicate_request می‌گیرند؛ این از ثبت دوباره سفارش با دوبار کلیک جلوگیری می‌کند. درخواست‌های GET از این قاعده مستثنی‌اند.
  • سایت باید «متصل» باشد؛ بعد از قطع اتصال یا اتصال دوباره، کلید قبلی کار نمی‌کند.
import { createHmac } from "node:crypto";

const NIVARA = "https://studio.manova.ir/api/v1";
const { siteId, secret } = await loadSetting("nivara");   // saved while connecting

/** Signed request to Nivara. path includes the query string, e.g. "/jobs?status=draft". */
export async function nivara(method, path, body) {
  const raw = body === undefined ? "" : JSON.stringify(body);
  const ts = Math.floor(Date.now() / 1000);
  const signature = createHmac("sha256", secret)
    .update(`${ts}.${method}.${path}.${method === "GET" ? "" : raw}`)
    .digest("hex");
  const res = await fetch(NIVARA + path, {
    method,
    headers: { "Content-Type": "application/json", "X-Nivara-Site": siteId, "X-Nivara-Timestamp": String(ts), "X-Nivara-Signature": signature },
    body: method === "GET" ? undefined : raw,
  });
  const json = await res.json();
  if (!json.ok) throw Object.assign(new Error(json.error.message), { code: json.error.code, status: res.status });
  return json.data;
}

// Examples
const account = await nivara("GET", "/account");
const { job } = await nivara("POST", "/jobs", { type: "article", title: "راهنمای انتخاب هاست", keyword: "هاست وردپرس" });
const detail = await nivara("GET", `/jobs/${job.id}`);

همه پاسخ‌ها قالب ثابت دارند: موفق { "ok": true, "data": … } و ناموفق { "ok": false, "error": { "code", "message" } }. متن message فارسی است و می‌توانید مستقیم به کاربر نشان دهید.

endpointهای سفارش

endpointکار
GET /accountموجودی، قیمت‌ها، فعال بودن سفارش و تصویر، سقف و هزینه این ماه، امکان شارژ.
POST /quoteقیمت سفارش قبل از ثبت: { type, autoImage } ← { cost, enoughBalance, withinMonthlyLimit, canOrder }.
POST /jobsثبت سفارش (کسر هزینه). پاسخ ۲۰۱ با job در وضعیت working.
GET /jobsفهرست سفارش‌های همین سایت: ?status=&limit=(۱ تا ۱۰۰)&before=(createdAt برای صفحه بعد).
GET /jobs/{id}یک سفارش به‌همراه document و آدرس تصاویر (وقتی آماده است).
POST /jobs/{id}/regenerateتولید دوباره با { notes } اختیاری. اولین بار رایگان، بعد از آن به قیمت سفارش.
POST /jobs/{id}/imageساخت یا ساخت دوباره تصویر شاخص با { prompt } اختیاری. همزمان؛ تا دو دقیقه طول می‌کشد.
POST /jobs/{id}/publishارسال فوری به سایت با { mode: "publish" | "draft" }. نیوارا در همین لحظه POST /publish شما را صدا می‌زند.
POST /topupشروع شارژ کیف پول: { amount, returnUrl } ← { payUrl, paymentId }.
چون POST /jobs/{id}/publish در حین پاسخ‌دادن، endpoint انتشار خود CMS را صدا می‌زند، سرور CMS باید بتواند همزمان چند درخواست را پاسخ دهد.

ثبت و پیگیری سفارش

POST /api/v1/jobs
{
  "type": "article",                     // article | product | landing | rewrite
  "title": "راهنمای انتخاب هاست وردپرس",  // title یا keyword (حداقل یکی)
  "keyword": "هاست وردپرس",
  "audience": "صاحبان فروشگاه‌های آنلاین",
  "notes": "به پشتیبانی فارسی اشاره شود",
  "tone": "professional",                // professional | friendly | expert | sales
  "autoImage": true,                     // فقط اگر تولید تصویر فعال باشد هزینه دارد
  "autoSeo": true,
  "internalLinks": true,
  "autoPublish": false,                  // false: پیش‌نویس به سایت فرستاده می‌شود
  "scheduledAt": null                    // یا "2026-11-01T09:00:00+03:30"
}
statusمعنی
workingدر صف تولید.
generatingدر حال نوشتن متن و ساخت تصویر.
draftآماده؛ اگر سایت متصل باشد به‌صورت پیش‌نویس (یا منتشرشده، با autoPublish) فرستاده شده است.
scheduledآماده و منتظر زمان انتشار.
doneدر سایت منتشر شده است.
failedتولید ناموفق بود؛ هزینه کامل به کیف پول برگشته است. علت در error.

برای پیگیری، هر چند ثانیه GET /jobs/{id} را بخوانید یا منتظر رسیدن محتوا به POST /publish خودتان بمانید. فیلد publish وضعیت تحویل به سایت و image.featuredUrl آدرس تصویر شاخص است.

شارژ کیف پول

POST /topup با { "amount": 500000, "returnUrl": "https://example.com/admin/nivara" } (تومان) یک پرداخت زرین‌پال می‌سازد. کاربر را به payUrl بفرستید؛ بعد از پرداخت، صفحه نتیجه نیوارا (بدون نیاز به ورود) او را به returnUrl برمی‌گرداند با:

?nivara_payment=paid|canceled|failed|pending&payment_id=…

returnUrl باید روی دامنه همین سایت باشد. مبلغ باید بین حداقل و حداکثر اعلام‌شده در GET /account (بخش topUp) باشد. برای اطمینان، بعد از بازگشت موجودی را دوباره از GET /account بخوانید.

کدهای خطا

HTTP/1.1 402 Payment Required
{ "ok": false, "error": { "code": "insufficient_balance", "message": "موجودی کیف پول کافی نیست." } }
codeHTTPمعنی
unauthorized401امضا، زمان یا هدرها نامعتبر است یا سایت متصل نیست.
duplicate_request409همین درخواست POST (با همین امضا) قبلاً اجرا شده است.
forbidden403حساب صاحب سایت غیرفعال است.
orders_disabled403سفارش از CMS در پنل نیوارا خاموش است.
invalid_request400بدنه یا پارامترها نامعتبر است؛ message دلیل را می‌گوید.
insufficient_balance402موجودی کیف پول کافی نیست؛ کاربر را به شارژ هدایت کنید.
monthly_limit_reached402سقف هزینه ماهانه این سایت پر شده است.
email_unverified403صاحب حساب هنوز ایمیلش را تأیید نکرده؛ سفارش و شارژ تا تأیید ممکن نیست.
not_found404سفارش وجود ندارد یا متعلق به این سایت نیست.
not_ready409محتوا هنوز آماده نیست (برای انتشار، تولید دوباره یا تصویر).
images_unavailable503تولید تصویر در نیوارا فعال نیست.
image_failed502ساخت تصویر ناموفق بود؛ هزینه آن برگشت داده شده.
publish_failed502ارسال به سایت ناموفق بود (endpoint انتشار شما خطا داد یا در دسترس نبود).
payment_unavailable503درگاه پرداخت فعال نیست یا مبلغ خارج از بازه مجاز است.
rate_limited429بیش از ۱۲۰ درخواست در دقیقه یا ۲۰ سفارش در دقیقه.
internal_error500خطای داخلی؛ کمی بعد دوباره تلاش کنید.

چک‌لیست امنیتی

  • کلید مخفی را رمزنگاری‌شده یا در محل امن ذخیره کنید و هرگز در کد سمت مرورگر نگذارید.
  • امضا را روی بدنه خام و با مقایسه زمان‌ثابت بررسی کنید.
  • درخواست‌های قدیمی‌تر از ۵ دقیقه و امضاهای تکراری را رد کنید.
  • endpoint را فقط روی HTTPS در دسترس بگذارید.
  • HTML دریافتی از نیوارا امضاشده و قابل اعتماد است، اما اگر CMS شما محتوای کاربران دیگر را هم نمایش می‌دهد، سیاست پاک‌سازی خودتان را اعمال کنید.
  • تصاویر را دانلود و روی سرور خودتان ذخیره کنید؛ به لینک‌های نیوارا به‌عنوان میزبان دائمی تکیه نکنید.

نسخه متنی این مستندات: publish-protocol.md