Citely Docs
Nhà phát triển

Envelope (CPP v1)

Cấu trúc JSON canonical của một bài viết mà Citely gửi ra khi publish.

Mỗi lần publish, Citely gửi một JSON body mô tả trọn vẹn một bài viết. Envelope là superset & self-sufficient: mọi thứ để render một bài đều nằm trong một POST — website không bao giờ phải gọi ngược Citely để lấy thêm.

Quy tắc bất biến

  1. Forward-compatible. Citely có thể thêm field mới bất cứ lúc nào trong cùng version. Receiver PHẢI bỏ qua field lạ, không được lỗi vì gặp field chưa biết.
  2. Optional trừ khi ghi "bắt buộc". Field vắng mặt = không có dữ liệu đó; đừng suy diễn. Chỉ version, event, title, slug, status là luôn có.
  3. version = phiên bản envelope. CPP v1 dùng version: 1. Chỉ bump khi có breaking change (đổi nghĩa/xoá field) — thêm field KHÔNG bump.
  4. HTML sạch, JSON-LD tách riêng. html là thân bài ngữ nghĩa, không chứa <script type="application/ld+json">. Schema.org nằm ở mảng jsonLd[] để receiver tự đặt vào <head>.

Body (JSON)

{
  "version": 1,
  "event": "article.publish",

  // ── Định danh & upsert ──────────────────────────────────────────────
  "externalId": "string | null", // id RECEIVER đã trả lần trước → UPDATE bản đó. null → CREATE mới.
  "title": "string",             // bắt buộc
  "slug": "string",              // bắt buộc, đã kebab-case/ASCII-hoá

  // ── Nội dung (receiver chọn định dạng nó "accepts") ─────────────────
  "html": "string",              // HTML ngữ nghĩa, render as-is (đã sạch Gutenberg)
  "markdown": "string",          // cùng bài, dạng Markdown (tiện cho SSG)

  // ── Meta SEO ────────────────────────────────────────────────────────
  "excerpt": "string",           // meta description (~150–160 ký tự)
  "focusKeyword": "string",
  "tags": ["string"],            // topic cluster / chủ đề
  "author": { "name": "string", "url": "string | null", "avatar": "string(url) | null" },
  "canonicalUrl": "string | null",
  "noindex": false,              // true → receiver set noindex + KHÔNG lên sitemap

  // ── Đa ngôn ngữ ─────────────────────────────────────────────────────
  "locale": "vi",                // BCP-47 gọn: "vi" | "en" | "vi-VN"
  "translationKey": "string|null", // nối hreflang giữa các bản dịch cùng 1 bài gốc

  // ── Media (URL tuyệt đối trên CDN Citely) ───────────────────────────
  "cover": "string(url) | null", // ảnh đại diện / OG
  "images": [{ "url": "string(url)", "alt": "string", "caption": "string" }],

  // ── JSON-LD (Schema.org, đã tách khỏi html) ─────────────────────────
  "jsonLd": [{ "@context": "https://schema.org", "@type": "Article" }],

  // ── Trạng thái & thời gian ──────────────────────────────────────────
  "status": "publish",           // "publish" | "draft" | "future"
  "publishedAt": "ISO-8601 | undefined",
  "modifiedAt": "ISO-8601 | undefined",

  "test": false                  // true = ping "Test Connection". Verify rồi trả 200, KHÔNG lưu.
}

Ghi chú field

FieldGhi chú cho receiver
externalIdKhoá upsert số 1. Khác null → cập nhật đúng bản ghi đó. null → tạo mới. Không lưu id thì upsert theo slug.
html vs markdownChọn theo accepts bạn khai ở handshake. Site tĩnh thường lấy markdown; site động thường lưu html.
tagsChuỗi tự do (không phải id). Map vào taxonomy của bạn; bỏ qua nếu schema không có.
authorRender byline + Person trong JSON-LD. jsonLd[] đã chứa Article/Person, nên author chỉ là tiện lợi.
locale / translationKeySite song ngữ: locale → trường lang; các bài cùng translationKey là bản dịch của nhau → nối <link rel="alternate" hreflang>.
cover / images[].urlURL tuyệt đối trên CDN Citely. Mặc định hot-link (0 việc). Muốn bền thì tải về R2/repo của bạn.
jsonLd[]Emit mỗi phần tử thành một <script type="application/ld+json"> trong <head>. Đừng nhét lại vào html.
status: "future"Bài hẹn giờ. publishedAt là thời điểm tương lai → giữ nháp tới giờ đó.

Field-mapping (bước AI của website làm)

AI của repo phải đọc schema thật (vd src/content.config.ts Zod của Astro, hay bảng DB) rồi map field CPP → field local. Ví dụ:

CPP fieldFrontmatter Astro (Zod)
titletitle
excerptdescription
publishedAtpubDate
modifiedAtupdatedDate
coverheroImage
localelang
tagstags
author.nameauthor

Field CPP không có trường tương ứng → bỏ qua. Trường Zod bắt buộc mà CPP không cấp → điền default hợp lý (vd pubDate = publishedAt ?? now).

Bước tiếp theo: Receiver endpoint — endpoint website phải dựng để nhận envelope này.

On this page