Lewati ke dokumentasi
Insight API / v1.0.0

Dokumentasi API

Hubungkan aplikasi dan agent ke Insight LunaBiner. Kelola artikel bilingual, upload cover, dan publikasikan konten dari workflow Anda.

REST + JSONBearer authenticationID / EN

01 / Getting started

Satu kontrak untuk integrasi Anda.

Dokumentasi ini dapat dibaca publik. Untuk mengakses data, minta credential integrasi dari tim LunaBiner dan simpan secret di environment privat agent Anda. Gunakan origin HTTPS deployment yang telah dikonfirmasi.

01

Dapatkan akses

Token dengan scopes dan expiry sesuai kebutuhan.

02

Pilih kategori

Baca kategori yang sudah dikelola di admin.

03

Kirim artikel

Mulai dari draft dan simpan UUID serta version.

Base URL url
https://lunabiner.com/api/v1

02 / Authentication

Token khusus. Scope yang jelas.

Setiap request data memakai header Authorization: Bearer. Integrasi tidak perlu login admin. Scopes bersifat terpisah: publish tidak otomatis memberikan read atau write.

insights:read

Baca kategori, daftar artikel, dan version terbaru.

insights:write

Buat draft, ubah artikel, dan upload cover.

insights:publish

Tambahan untuk publish, menarik publikasi, atau mengedit artikel terbit/terjadwal.

Inject secret sebagai HERMES_INSIGHTS_TOKEN. Jangan taruh token di URL, browser storage, repository, atau log. Contoh curl membaca environment lewat stdin config, tanpa menaruh token di argument curl. Gunakan HTTPS langsung tanpa mengikuti redirect.

03 / Hermes

Draft → cover → publish.

  1. 1

    Buat draft

    POST /insights dengan Idempotency-Key stabil. Simpan UUID dan payload/key untuk retry.

  2. 2

    Ambil state terbaru

    GET /insights/{id}. Replay create mengembalikan snapshot awal, bukan version terbaru.

  3. 3

    Upload cover jika diperlukan

    PUT /insights/{id}/cover dengan file dan version. Simpan version dari respons sukses.

  4. 4

    Publish pada jadwal Hermes

    PATCH /insights/{id} dengan status PUBLISHED dan version terbaru yang telah direview.

Generation dan scheduler dijalankan Hermes. API memproses request saat diterima. Jika kategori masih kosong, tambahkan kategori melalui admin terlebih dahulu.

04 / Content

Konten bilingual, rich JSON.

Kirim translations.id dan translations.en dengan title serta richBody. Untuk publish, setiap bahasa memerlukan excerpt minimal 10 karakter dan isi teks minimal 30 karakter. Server menghitung plain body; jangan kirim body, details, actorId, atau path cover dalam create/PATCH.

Rich text: doc, paragraph, heading H2–H4, text, bulletList, orderedList, listItem, blockquote, codeBlock, hardBreak, horizontalRule.

Marks: bold, italic, strike, code, dan link aman. Tidak menerima HTML/Markdown string, gambar inline, atau custom node.

Batas per bahasa: 2.000 nodes, depth 12, 30.000 karakter teks; maksimum 1.000 children/node dan 5 marks/text.

Cover: JPG/PNG/WebP statis maksimal 5 MiB. Upload sebagai multipart file dan version; hasil dinormalisasi WebP. Preview draft memerlukan session admin, bukan Bearer token.

Contoh payload draft ID / EN Buka JSON +

Ganti categoryId dengan UUID dari GET kategori dan gunakan slug unik. Simpan payload sebagai article.json untuk contoh curl create.

Payload draft json
{
  "slug": "panduan-otomasi-bisnis",
  "categoryId": "00000000-0000-4000-8000-000000000001",
  "status": "DRAFT",
  "authorName": "LunaBiner Editorial",
  "tags": [
    "Otomasi",
    "Bisnis"
  ],
  "translations": {
    "id": {
      "title": "Panduan otomasi bisnis",
      "excerpt": "Langkah awal memilih proses bisnis yang tepat untuk diotomasi.",
      "richBody": {
        "type": "doc",
        "content": [
          {
            "type": "heading",
            "attrs": {
              "level": 2
            },
            "content": [
              {
                "type": "text",
                "text": "Mulai dari proses berulang"
              }
            ]
          },
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "text": "Petakan pekerjaan manual yang berulang sebelum memilih solusi. ",
                "marks": [
                  {
                    "type": "bold"
                  }
                ]
              },
              {
                "type": "text",
                "text": "Diskusikan kebutuhan Anda.",
                "marks": [
                  {
                    "type": "link",
                    "attrs": {
                      "href": "/id/contact",
                      "target": "_blank"
                    }
                  }
                ]
              }
            ]
          },
          {
            "type": "bulletList",
            "content": [
              {
                "type": "listItem",
                "content": [
                  {
                    "type": "paragraph",
                    "content": [
                      {
                        "type": "text",
                        "text": "Catat volume pekerjaan dan waktu yang diperlukan."
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            "type": "orderedList",
            "attrs": {
              "start": 1,
              "type": "1"
            },
            "content": [
              {
                "type": "listItem",
                "content": [
                  {
                    "type": "paragraph",
                    "content": [
                      {
                        "type": "text",
                        "text": "Pilih satu proses untuk percobaan."
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            "type": "blockquote",
            "content": [
              {
                "type": "paragraph",
                "content": [
                  {
                    "type": "text",
                    "text": "Ukur hasil sebelum memperluas penerapan.",
                    "marks": [
                      {
                        "type": "italic"
                      }
                    ]
                  }
                ]
              }
            ]
          },
          {
            "type": "codeBlock",
            "attrs": {
              "language": "text"
            },
            "content": [
              {
                "type": "text",
                "text": "proses → percobaan → evaluasi"
              }
            ]
          },
          {
            "type": "horizontalRule"
          },
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "text": "Tetapkan pemilik proses."
              },
              {
                "type": "hardBreak"
              },
              {
                "type": "text",
                "text": "Tinjau hasil secara berkala."
              }
            ]
          }
        ]
      },
      "seoTitle": "Panduan otomasi bisnis | LunaBiner",
      "seoDescription": "Pelajari cara memetakan proses berulang, menentukan prioritas, dan mengevaluasi hasil otomasi bisnis."
    },
    "en": {
      "title": "Business automation guide",
      "excerpt": "How to choose a suitable business process for your first automation project.",
      "richBody": {
        "type": "doc",
        "content": [
          {
            "type": "paragraph",
            "content": [
              {
                "type": "text",
                "text": "Start by mapping repetitive manual processes. Choose one process for a small pilot, measure its results, and review the outcome with the process owner before expanding."
              }
            ]
          }
        ]
      },
      "seoTitle": "Business automation guide | LunaBiner",
      "seoDescription": "Learn how to map repetitive work, set priorities, and evaluate the results of business process automation."
    }
  }
}

05 / Reference

Endpoint Insight API.

Seluruh endpoint data memerlukan Bearer token. Daftar berikut mengikuti kontrak OpenAPI v1.0.0. UUID pada path adalah UUID artikel, bukan slug. Query selain yang didokumentasikan ditolak.

GET/api/v1/insight-categories

Read admin-managed categories

insights:read

HEAD tersedia dengan autentikasi yang sama, tanpa body respons.

page

query · optional · default 1

Clamped to last page; at least one page even if empty.

integer
default: 1minimum: 1maximum: 10000
Response body & headers

200 · Sorted nameId asc/id asc,100 per page.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

401 · UNAUTHORIZED. Missing/invalid/expired/revoked credential.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
WWW-Authenticate

Bearer realm="insights"

string

403 · FORBIDDEN / PERMISSION. Insufficient scope.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

422 · VALIDATION / QUERY_INVALID / CATEGORY / INVALID_COVER / SCHEDULE. Invalid fields/query/rich body/category or decoded cover.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

429 · RATE_LIMITED. Persistent fixed-minute quota60/credential exceeded.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
Retry-After

Seconds until next minute; wait before retry.

string
pattern: "^[1-9][0-9]*$"

503 · UNAVAILABLE. Sanitized auth/storage/database failure. Mutation commit may be ambiguous; fetch current state.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
200401403422429503
curl GET /api/v1/insight-categories bash
printf 'header = "Authorization: Bearer %s"\n' "$HERMES_INSIGHTS_TOKEN" |
  curl --config - --proto '=https' --silent --show-error --fail-with-body \
  --request GET \
  "https://lunabiner.com/api/v1/insight-categories"

GET/api/v1/insights

List active articles, including private drafts

insights:read

HEAD tersedia dengan autentikasi yang sama, tanpa body respons.

page

query · optional · default 1

Clamped to last page; at least one page even if empty.

integer
default: 1minimum: 1maximum: 10000
locale

query · optional · default id

stringid / en

string
enum: ["id","en"]default: "id"
q

query · optional

Trimmed case-insensitive title/excerpt search in selected locale.

string
maxLength: 120
category

query · optional

Category slug (empty means any). Nonempty minimum2 chars.

anyOf

const: ""
string
minLength: 2maxLength: 120pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"
status

query · optional

stringDRAFT / REVIEW / SCHEDULED / PUBLISHED

string
enum: ["DRAFT","REVIEW","SCHEDULED","PUBLISHED"]
Response body & headers

200 · 6 per page, publishedAt desc/id asc. Archived/deleted excluded.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

401 · UNAUTHORIZED. Missing/invalid/expired/revoked credential.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
WWW-Authenticate

Bearer realm="insights"

string

403 · FORBIDDEN / PERMISSION. Insufficient scope.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

422 · VALIDATION / QUERY_INVALID / CATEGORY / INVALID_COVER / SCHEDULE. Invalid fields/query/rich body/category or decoded cover.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

429 · RATE_LIMITED. Persistent fixed-minute quota60/credential exceeded.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
Retry-After

Seconds until next minute; wait before retry.

string
pattern: "^[1-9][0-9]*$"

503 · UNAVAILABLE. Sanitized auth/storage/database failure. Mutation commit may be ambiguous; fetch current state.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
200401403422429503
curl GET /api/v1/insights bash
printf 'header = "Authorization: Bearer %s"\n' "$HERMES_INSIGHTS_TOKEN" |
  curl --config - --proto '=https' --silent --show-error --fail-with-body \
  --request GET \
  "https://lunabiner.com/api/v1/insights"

POST/api/v1/insights

Create DRAFT or PUBLISHED article with atomic replay

insights:write+ publish untuk artikel terbit
Idempotency-Key

header · required

Stable logical create key. Same normalized payload replays original snapshot201 for7days; mismatch/expired409. Scoped credential/operation. Rotation preserves identity; new credential does not. After cleanup key is new: do not reuse old keys.

string
minLength: 16maxLength: 128pattern: "^[A-Za-z0-9_-]+$"

PUBLISHED additionally requires insights:publish. Payload, assignment, audit and replay snapshot commit atomically. Replay returns ORIGINAL data, not current state; never republishes withdrawn content. No query parameters.

Request body · required

application/json
Response body & headers

201 · Created or replayed original snapshot.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
Location

Relative /api/v1/insights/{id}.

string
Idempotency-Replayed

true on replay; false on fresh create.

enum: ["true","false"]
X-Insight-Cache-Warning

Optional. Commit succeeded; cache invalidation failed. Fetch current state before any retry.

enum: ["CACHE_INVALIDATION_PENDING"]

400 · INVALID_JSON / INVALID_LENGTH / INVALID_MULTIPART. Malformed body or length.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

401 · UNAUTHORIZED. Missing/invalid/expired/revoked credential.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
WWW-Authenticate

Bearer realm="insights"

string

403 · FORBIDDEN / PERMISSION. Insufficient scope.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

409 · VERSION / SLUG / ARCHIVED / CONFLICT / IN_USE / IDEMPOTENCY / IDEMPOTENCY_EXPIRED. Version/race/slug/archive/replay conflict.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

413 · PAYLOAD_TOO_LARGE. JSON>1MiB, file>5MiB or multipart envelope>5MiB+16KiB.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

415 · UNSUPPORTED_MEDIA. Wrong Content-Type/encoding or unsupported cover MIME.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

422 · VALIDATION / QUERY_INVALID / CATEGORY / INVALID_COVER / SCHEDULE. Invalid fields/query/rich body/category or decoded cover.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

429 · RATE_LIMITED. Persistent fixed-minute quota60/credential exceeded.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
Retry-After

Seconds until next minute; wait before retry.

string
pattern: "^[1-9][0-9]*$"

503 · UNAVAILABLE. Sanitized auth/storage/database failure. Mutation commit may be ambiguous; fetch current state.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
201400401403409413415422429503
curl POST /api/v1/insights bash
printf 'header = "Authorization: Bearer %s"\n' "$HERMES_INSIGHTS_TOKEN" |
  curl --config - --proto '=https' --silent --show-error --fail-with-body \
  --request POST \
  --header "Content-Type: application/json" \
  --header "Idempotency-Key: article-create-key-0001" \
  --data-binary @article.json \
  "https://lunabiner.com/api/v1/insights"

GET/api/v1/insights/{id}

Read current article/version including archived

insights:read

HEAD tersedia dengan autentikasi yang sama, tanpa body respons.

id

path · required

ARTICLE UUID, never slug.

string
format: "uuid"

No query parameters. DTO hides authorId, credential hash and arbitrary legacy metadata.

Response body & headers

200 · Current state, including private/archived articles.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

401 · UNAUTHORIZED. Missing/invalid/expired/revoked credential.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
WWW-Authenticate

Bearer realm="insights"

string

403 · FORBIDDEN / PERMISSION. Insufficient scope.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

404 · NOT_FOUND / KIND. Article not found, including non-ARTICLE UUID.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

422 · VALIDATION / QUERY_INVALID / CATEGORY / INVALID_COVER / SCHEDULE. Invalid fields/query/rich body/category or decoded cover.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

429 · RATE_LIMITED. Persistent fixed-minute quota60/credential exceeded.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
Retry-After

Seconds until next minute; wait before retry.

string
pattern: "^[1-9][0-9]*$"

503 · UNAVAILABLE. Sanitized auth/storage/database failure. Mutation commit may be ambiguous; fetch current state.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
200401403404422429503
curl GET /api/v1/insights/{id} bash
printf 'header = "Authorization: Bearer %s"\n' "$HERMES_INSIGHTS_TOKEN" |
  curl --config - --proto '=https' --silent --show-error --fail-with-body \
  --request GET \
  "https://lunabiner.com/api/v1/insights/ARTICLE_UUID"

PATCH/api/v1/insights/{id}

Update allowed fields using latest version

insights:write+ publish untuk artikel terbit
id

path · required

ARTICLE UUID, never slug.

string
format: "uuid"

Publish or editing/withdrawing PUBLISHED/SCHEDULED requires insights:publish. Full merged validation; no query parameters. Version conflict409: re-read and reconcile, do not blindly overwrite.

Request body · required

application/json
Response body & headers

200 · Current state after successful update.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
X-Insight-Cache-Warning

Optional. Commit succeeded; cache invalidation failed. Fetch current state before any retry.

enum: ["CACHE_INVALIDATION_PENDING"]

400 · INVALID_JSON / INVALID_LENGTH / INVALID_MULTIPART. Malformed body or length.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

401 · UNAUTHORIZED. Missing/invalid/expired/revoked credential.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
WWW-Authenticate

Bearer realm="insights"

string

403 · FORBIDDEN / PERMISSION. Insufficient scope.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

404 · NOT_FOUND / KIND. Article not found, including non-ARTICLE UUID.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

409 · VERSION / SLUG / ARCHIVED / CONFLICT / IN_USE / IDEMPOTENCY / IDEMPOTENCY_EXPIRED. Version/race/slug/archive/replay conflict.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

413 · PAYLOAD_TOO_LARGE. JSON>1MiB, file>5MiB or multipart envelope>5MiB+16KiB.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

415 · UNSUPPORTED_MEDIA. Wrong Content-Type/encoding or unsupported cover MIME.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

422 · VALIDATION / QUERY_INVALID / CATEGORY / INVALID_COVER / SCHEDULE. Invalid fields/query/rich body/category or decoded cover.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

429 · RATE_LIMITED. Persistent fixed-minute quota60/credential exceeded.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
Retry-After

Seconds until next minute; wait before retry.

string
pattern: "^[1-9][0-9]*$"

503 · UNAVAILABLE. Sanitized auth/storage/database failure. Mutation commit may be ambiguous; fetch current state.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
200400401403404409413415422429503
curl PATCH /api/v1/insights/{id} bash
printf 'header = "Authorization: Bearer %s"\n' "$HERMES_INSIGHTS_TOKEN" |
  curl --config - --proto '=https' --silent --show-error --fail-with-body \
  --request PATCH \
  --header "Content-Type: application/json" \
  --data '{"version":2,"status":"PUBLISHED"}' \
  "https://lunabiner.com/api/v1/insights/ARTICLE_UUID"

PUT/api/v1/insights/{id}/cover

Replace only cover using multipart bytes and latest version

insights:write+ publish untuk artikel terbit
id

path · required

ARTICLE UUID, never slug.

string
format: "uuid"

Published/scheduled requires insights:publish. No query/duplicate/extra fields, URL/path/compression. File5MiB, total envelope5259264bytes, bounded before multipart parsing. No Idempotency-Key semantics: after lost response GET current version/reference before retry. Current title/body/category/publication date preserved. Failed new assets cleaned only if DB confirms unattached; old covers retained privately and old URLs404. Only PUT supported (GET/HEAD/OPTIONS/etc405).

Request body · required

multipart/form-data
Response body & headers

200 · Current article after cover commit; version increments.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
X-Insight-Cache-Warning

Optional. Commit succeeded; cache invalidation failed. Fetch current state before any retry.

enum: ["CACHE_INVALIDATION_PENDING"]

400 · INVALID_JSON / INVALID_LENGTH / INVALID_MULTIPART. Malformed body or length.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

401 · UNAUTHORIZED. Missing/invalid/expired/revoked credential.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
WWW-Authenticate

Bearer realm="insights"

string

403 · FORBIDDEN / PERMISSION. Insufficient scope.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

404 · NOT_FOUND / KIND. Article not found, including non-ARTICLE UUID.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

409 · VERSION / SLUG / ARCHIVED / CONFLICT / IN_USE / IDEMPOTENCY / IDEMPOTENCY_EXPIRED. Version/race/slug/archive/replay conflict.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

413 · PAYLOAD_TOO_LARGE. JSON>1MiB, file>5MiB or multipart envelope>5MiB+16KiB.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

415 · UNSUPPORTED_MEDIA. Wrong Content-Type/encoding or unsupported cover MIME.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

422 · VALIDATION / QUERY_INVALID / CATEGORY / INVALID_COVER / SCHEDULE. Invalid fields/query/rich body/category or decoded cover.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"

429 · RATE_LIMITED. Persistent fixed-minute quota60/credential exceeded.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
Retry-After

Seconds until next minute; wait before retry.

string
pattern: "^[1-9][0-9]*$"

503 · UNAVAILABLE. Sanitized auth/storage/database failure. Mutation commit may be ambiguous; fetch current state.

application/json

Cache-Control

Authenticated and error responses are never public cached.

const: "private, no-store"
X-Content-Type-Options

Do not sniff.

const: "nosniff"
X-Robots-Tag

Private integration responses.

const: "noindex, nofollow"
200400401403404409413415422429503
curl PUT /api/v1/insights/{id}/cover bash
printf 'header = "Authorization: Bearer %s"\n' "$HERMES_INSIGHTS_TOKEN" |
  curl --config - --proto '=https' --silent --show-error --fail-with-body \
  --request PUT \
  --form "version=1" \
  --form "file=@cover.png;type=image/png" \
  "https://lunabiner.com/api/v1/insights/ARTICLE_UUID/cover"

06 / Schemas

Field, tipe, dan batas payload.

Field required, enum, nullable, default, dan batas mengikuti OpenAPI. Klik nama schema untuk relasi nested. Referensi rich text yang berulang ditampilkan sebagai tautan; JSON lengkap tersedia di setiap schema. Validasi lintas field dan syarat publish dijelaskan pada deskripsi kontrak.

RichMark

oneOf

object
additionalProperties: false

type · required

enum: ["bold","italic","strike","code"]
object
additionalProperties: false

type · required

const: "link"

attrs · required

object
additionalProperties: false

href · required

string

Safe http/https/mailto/tel without credentials, internal /path (not //), or #fragment. No whitespace, backslash or control characters. URL validity and credentials checked server-side.

maxLength: 2000

target · optional

anyOf

enum: ["_blank"]

Detail lanjutan tersedia pada JSON schema lengkap di bawah atau download OpenAPI.

null

Detail lanjutan tersedia pada JSON schema lengkap di bawah atau download OpenAPI.

rel · optional

anyOf

string
maxLength: 100

Detail lanjutan tersedia pada JSON schema lengkap di bawah atau download OpenAPI.

null

Detail lanjutan tersedia pada JSON schema lengkap di bawah atau download OpenAPI.

class · optional

null

title · optional

anyOf

string
maxLength: 180

Detail lanjutan tersedia pada JSON schema lengkap di bawah atau download OpenAPI.

null

Detail lanjutan tersedia pada JSON schema lengkap di bawah atau download OpenAPI.

JSON schema lengkap
{
  "oneOf": [
    {
      "type": "object",
      "properties": {
        "type": {
          "enum": [
            "bold",
            "italic",
            "strike",
            "code"
          ]
        }
      },
      "required": [
        "type"
      ],
      "additionalProperties": false
    },
    {
      "type": "object",
      "properties": {
        "type": {
          "const": "link"
        },
        "attrs": {
          "type": "object",
          "properties": {
            "href": {
              "type": "string",
              "description": "Safe http/https/mailto/tel without credentials, internal /path (not //), or #fragment. No whitespace, backslash or control characters. URL validity and credentials checked server-side.",
              "maxLength": 2000
            },
            "target": {
              "anyOf": [
                {
                  "enum": [
                    "_blank"
                  ]
                },
                {
                  "type": "null"
                }
              ]
            },
            "rel": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 100
                },
                {
                  "type": "null"
                }
              ]
            },
            "class": {
              "type": "null"
            },
            "title": {
              "anyOf": [
                {
                  "type": "string",
                  "maxLength": 180
                },
                {
                  "type": "null"
                }
              ]
            }
          },
          "required": [
            "href"
          ],
          "additionalProperties": false
        }
      },
      "required": [
        "type",
        "attrs"
      ],
      "additionalProperties": false
    }
  ]
}
RichDoc
object
additionalProperties: false

type · required

const: "doc"

attrs · optional

object
additionalProperties: false

marks · optional

array
maxItems: 0
JSON schema lengkap
{
  "type": "object",
  "properties": {
    "type": {
      "const": "doc"
    },
    "attrs": {
      "type": "object",
      "properties": {},
      "required": [],
      "additionalProperties": false
    },
    "marks": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/RichMark"
      },
      "maxItems": 0
    },
    "content": {
      "type": "array",
      "items": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/RichParagraph"
          },
          {
            "$ref": "#/components/schemas/RichHeading"
          },
          {
            "$ref": "#/components/schemas/RichBulletList"
          },
          {
            "$ref": "#/components/schemas/RichOrderedList"
          },
          {
            "$ref": "#/components/schemas/RichBlockquote"
          },
          {
            "$ref": "#/components/schemas/RichHorizontalRule"
          },
          {
            "$ref": "#/components/schemas/RichCodeBlock"
          }
        ]
      },
      "maxItems": 1000,
      "minItems": 1
    }
  },
  "required": [
    "type",
    "content"
  ],
  "additionalProperties": false
}
RichParagraph
object
additionalProperties: false

type · required

const: "paragraph"

attrs · optional

object
additionalProperties: false

marks · optional

array
maxItems: 0

content · optional

array
maxItems: 1000
JSON schema lengkap
{
  "type": "object",
  "properties": {
    "type": {
      "const": "paragraph"
    },
    "attrs": {
      "type": "object",
      "properties": {},
      "required": [],
      "additionalProperties": false
    },
    "marks": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/RichMark"
      },
      "maxItems": 0
    },
    "content": {
      "type": "array",
      "items": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/RichText"
          },
          {
            "$ref": "#/components/schemas/RichHardBreak"
          }
        ]
      },
      "maxItems": 1000
    }
  },
  "required": [
    "type"
  ],
  "additionalProperties": false
}
RichHeading
object
additionalProperties: false

type · required

const: "heading"

attrs · required

object
additionalProperties: false

level · required

integer
minimum: 2maximum: 4

marks · optional

array
maxItems: 0

content · optional

array
maxItems: 1000
JSON schema lengkap
{
  "type": "object",
  "properties": {
    "type": {
      "const": "heading"
    },
    "attrs": {
      "type": "object",
      "properties": {
        "level": {
          "type": "integer",
          "minimum": 2,
          "maximum": 4
        }
      },
      "required": [
        "level"
      ],
      "additionalProperties": false
    },
    "marks": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/RichMark"
      },
      "maxItems": 0
    },
    "content": {
      "type": "array",
      "items": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/RichText"
          },
          {
            "$ref": "#/components/schemas/RichHardBreak"
          }
        ]
      },
      "maxItems": 1000
    }
  },
  "required": [
    "type",
    "attrs"
  ],
  "additionalProperties": false
}
RichCodeBlock
object
additionalProperties: false

type · required

const: "codeBlock"

attrs · optional

object
additionalProperties: false

language · optional

anyOf

string
maxLength: 40
null

marks · optional

array
maxItems: 0

content · optional

array
maxItems: 1000

items

JSON schema lengkap
{
  "type": "object",
  "properties": {
    "type": {
      "const": "codeBlock"
    },
    "attrs": {
      "type": "object",
      "properties": {
        "language": {
          "anyOf": [
            {
              "type": "string",
              "maxLength": 40
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "required": [],
      "additionalProperties": false
    },
    "marks": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/RichMark"
      },
      "maxItems": 0
    },
    "content": {
      "type": "array",
      "items": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/RichText"
          }
        ]
      },
      "maxItems": 1000
    }
  },
  "required": [
    "type"
  ],
  "additionalProperties": false
}
RichBulletList
object
additionalProperties: false

type · required

const: "bulletList"

attrs · optional

object
additionalProperties: false

marks · optional

array
maxItems: 0

content · required

array
minItems: 1maxItems: 1000
JSON schema lengkap
{
  "type": "object",
  "properties": {
    "type": {
      "const": "bulletList"
    },
    "attrs": {
      "type": "object",
      "properties": {},
      "required": [],
      "additionalProperties": false
    },
    "marks": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/RichMark"
      },
      "maxItems": 0
    },
    "content": {
      "type": "array",
      "items": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/RichListItem"
          }
        ]
      },
      "maxItems": 1000,
      "minItems": 1
    }
  },
  "required": [
    "type",
    "content"
  ],
  "additionalProperties": false
}
RichOrderedList
object
additionalProperties: false

type · required

const: "orderedList"

attrs · optional

object
additionalProperties: false

start · optional

integer
minimum: 1maximum: 10000

type · optional

anyOf

enum: ["1","a","A","i","I"]
null

marks · optional

array
maxItems: 0

content · required

array
minItems: 1maxItems: 1000
JSON schema lengkap
{
  "type": "object",
  "properties": {
    "type": {
      "const": "orderedList"
    },
    "attrs": {
      "type": "object",
      "properties": {
        "start": {
          "type": "integer",
          "minimum": 1,
          "maximum": 10000
        },
        "type": {
          "anyOf": [
            {
              "enum": [
                "1",
                "a",
                "A",
                "i",
                "I"
              ]
            },
            {
              "type": "null"
            }
          ]
        }
      },
      "required": [],
      "additionalProperties": false
    },
    "marks": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/RichMark"
      },
      "maxItems": 0
    },
    "content": {
      "type": "array",
      "items": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/RichListItem"
          }
        ]
      },
      "maxItems": 1000,
      "minItems": 1
    }
  },
  "required": [
    "type",
    "content"
  ],
  "additionalProperties": false
}
RichListItem
object
additionalProperties: false

type · required

const: "listItem"

attrs · optional

object
additionalProperties: false

marks · optional

array
maxItems: 0

content · required

array
minItems: 1maxItems: 1000
JSON schema lengkap
{
  "type": "object",
  "properties": {
    "type": {
      "const": "listItem"
    },
    "attrs": {
      "type": "object",
      "properties": {},
      "required": [],
      "additionalProperties": false
    },
    "marks": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/RichMark"
      },
      "maxItems": 0
    },
    "content": {
      "type": "array",
      "items": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/RichParagraph"
          },
          {
            "$ref": "#/components/schemas/RichBulletList"
          },
          {
            "$ref": "#/components/schemas/RichOrderedList"
          }
        ]
      },
      "maxItems": 1000,
      "minItems": 1
    }
  },
  "required": [
    "type",
    "content"
  ],
  "additionalProperties": false
}
RichBlockquote
object
additionalProperties: false

type · required

const: "blockquote"

attrs · optional

object
additionalProperties: false

marks · optional

array
maxItems: 0

content · required

JSON schema lengkap
{
  "type": "object",
  "properties": {
    "type": {
      "const": "blockquote"
    },
    "attrs": {
      "type": "object",
      "properties": {},
      "required": [],
      "additionalProperties": false
    },
    "marks": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/RichMark"
      },
      "maxItems": 0
    },
    "content": {
      "type": "array",
      "items": {
        "oneOf": [
          {
            "$ref": "#/components/schemas/RichParagraph"
          },
          {
            "$ref": "#/components/schemas/RichHeading"
          },
          {
            "$ref": "#/components/schemas/RichBulletList"
          },
          {
            "$ref": "#/components/schemas/RichOrderedList"
          },
          {
            "$ref": "#/components/schemas/RichCodeBlock"
          }
        ]
      },
      "maxItems": 1000,
      "minItems": 1
    }
  },
  "required": [
    "type",
    "content"
  ],
  "additionalProperties": false
}
RichText
object
additionalProperties: false

type · required

const: "text"

attrs · optional

object
additionalProperties: false

marks · optional

array
maxItems: 5

content · optional

array
maxItems: 0

items

text · required

string
minLength: 1maxLength: 30000
JSON schema lengkap
{
  "type": "object",
  "properties": {
    "type": {
      "const": "text"
    },
    "attrs": {
      "type": "object",
      "properties": {},
      "required": [],
      "additionalProperties": false
    },
    "marks": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/RichMark"
      },
      "maxItems": 5
    },
    "content": {
      "type": "array",
      "items": {},
      "maxItems": 0
    },
    "text": {
      "type": "string",
      "minLength": 1,
      "maxLength": 30000
    }
  },
  "required": [
    "type",
    "text"
  ],
  "additionalProperties": false
}
RichHardBreak
object
additionalProperties: false

type · required

const: "hardBreak"

attrs · optional

object
additionalProperties: false

marks · optional

array
maxItems: 0

content · optional

array
maxItems: 0

items

JSON schema lengkap
{
  "type": "object",
  "properties": {
    "type": {
      "const": "hardBreak"
    },
    "attrs": {
      "type": "object",
      "properties": {},
      "required": [],
      "additionalProperties": false
    },
    "marks": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/RichMark"
      },
      "maxItems": 0
    },
    "content": {
      "type": "array",
      "items": {},
      "maxItems": 0
    }
  },
  "required": [
    "type"
  ],
  "additionalProperties": false
}
RichHorizontalRule
object
additionalProperties: false

type · required

const: "horizontalRule"

attrs · optional

object
additionalProperties: false

marks · optional

array
maxItems: 0

content · optional

array
maxItems: 0

items

JSON schema lengkap
{
  "type": "object",
  "properties": {
    "type": {
      "const": "horizontalRule"
    },
    "attrs": {
      "type": "object",
      "properties": {},
      "required": [],
      "additionalProperties": false
    },
    "marks": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/RichMark"
      },
      "maxItems": 0
    },
    "content": {
      "type": "array",
      "items": {},
      "maxItems": 0
    }
  },
  "required": [
    "type"
  ],
  "additionalProperties": false
}
RichDocument
RichDoc ↗

Tiptap-style JSON whitelist. Server also enforces <=2000 total nodes, depth<=12 (root depth0), <=30000 total text characters per locale. No H1, HTML, image, table, script or custom nodes. Cross-node totals/link semantics remain server validation.

JSON schema lengkap
{
  "$ref": "#/components/schemas/RichDoc",
  "description": "Tiptap-style JSON whitelist. Server also enforces <=2000 total nodes, depth<=12 (root depth0), <=30000 total text characters per locale. No H1, HTML, image, table, script or custom nodes. Cross-node totals/link semantics remain server validation.",
  "x-max-total-nodes": 2000,
  "x-max-depth": 12,
  "x-max-total-text-characters": 30000
}
TranslationInput
object

Text fields are trimmed server-side. PUBLISHED requires excerpt>=10 and derived plain body>=30 in each locale.

additionalProperties: false

title · required

string
minLength: 2maxLength: 180

excerpt · optional

string
default: ""maxLength: 400

richBody · required

seoTitle · optional

string
default: ""maxLength: 70

seoDescription · optional

string
default: ""maxLength: 180
JSON schema lengkap
{
  "type": "object",
  "description": "Text fields are trimmed server-side. PUBLISHED requires excerpt>=10 and derived plain body>=30 in each locale.",
  "properties": {
    "title": {
      "type": "string",
      "minLength": 2,
      "maxLength": 180
    },
    "excerpt": {
      "type": "string",
      "maxLength": 400,
      "default": ""
    },
    "richBody": {
      "$ref": "#/components/schemas/RichDocument"
    },
    "seoTitle": {
      "type": "string",
      "maxLength": 70,
      "default": ""
    },
    "seoDescription": {
      "type": "string",
      "maxLength": 180,
      "default": ""
    }
  },
  "required": [
    "title",
    "richBody"
  ],
  "additionalProperties": false
}
TranslationsInput
object
additionalProperties: false

id · required

en · required

JSON schema lengkap
{
  "type": "object",
  "properties": {
    "id": {
      "$ref": "#/components/schemas/TranslationInput"
    },
    "en": {
      "$ref": "#/components/schemas/TranslationInput"
    }
  },
  "required": [
    "id",
    "en"
  ],
  "additionalProperties": false
}
Slug
string

Trimmed lowercase descriptive slug. preview, numeric-only and UUID values are reserved.

minLength: 2maxLength: 120pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$"
JSON schema lengkap
{
  "type": "string",
  "description": "Trimmed lowercase descriptive slug. preview, numeric-only and UUID values are reserved.",
  "minLength": 2,
  "maxLength": 120,
  "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$",
  "not": {
    "anyOf": [
      {
        "const": "preview"
      },
      {
        "pattern": "^[0-9]+$"
      },
      {
        "format": "uuid"
      }
    ]
  }
}
InsightCreate
object

Strict JSON. No id/version/body/actor/role/publishedAt/details/cover fields. Category must exist. Publication minima enforced server-side.

additionalProperties: false

slug · required

categoryId · required

string
format: "uuid"

status · optional

string
enum: ["DRAFT","PUBLISHED"]default: "DRAFT"

authorName · optional

string
default: ""maxLength: 100

tags · optional

array
default: []maxItems: 30

items

string
minLength: 1maxLength: 100

translations · required

JSON schema lengkap
{
  "type": "object",
  "description": "Strict JSON. No id/version/body/actor/role/publishedAt/details/cover fields. Category must exist. Publication minima enforced server-side.",
  "properties": {
    "slug": {
      "$ref": "#/components/schemas/Slug"
    },
    "categoryId": {
      "type": "string",
      "format": "uuid"
    },
    "status": {
      "type": "string",
      "enum": [
        "DRAFT",
        "PUBLISHED"
      ],
      "default": "DRAFT"
    },
    "authorName": {
      "type": "string",
      "maxLength": 100,
      "default": ""
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 100
      },
      "maxItems": 30,
      "default": []
    },
    "translations": {
      "$ref": "#/components/schemas/TranslationsInput"
    }
  },
  "required": [
    "slug",
    "categoryId",
    "translations"
  ],
  "additionalProperties": false,
  "examples": [
    {
      "slug": "panduan-otomasi-bisnis",
      "categoryId": "00000000-0000-4000-8000-000000000001",
      "status": "DRAFT",
      "authorName": "LunaBiner Editorial",
      "tags": [
        "Otomasi",
        "Bisnis"
      ],
      "translations": {
        "id": {
          "title": "Panduan otomasi bisnis",
          "excerpt": "Langkah awal memilih proses bisnis yang tepat untuk diotomasi.",
          "seoTitle": "Panduan otomasi bisnis | LunaBiner",
          "seoDescription": "Pelajari cara memetakan proses berulang, menentukan prioritas, dan mengevaluasi hasil otomasi bisnis.",
          "richBody": {
            "type": "doc",
            "content": [
              {
                "type": "heading",
                "attrs": {
                  "level": 2
                },
                "content": [
                  {
                    "type": "text",
                    "text": "Mulai dari proses berulang"
                  }
                ]
              },
              {
                "type": "paragraph",
                "content": [
                  {
                    "type": "text",
                    "text": "Petakan pekerjaan manual yang berulang sebelum memilih solusi. ",
                    "marks": [
                      {
                        "type": "bold"
                      }
                    ]
                  },
                  {
                    "type": "text",
                    "text": "Diskusikan kebutuhan Anda.",
                    "marks": [
                      {
                        "type": "link",
                        "attrs": {
                          "href": "/id/contact",
                          "target": "_blank"
                        }
                      }
                    ]
                  }
                ]
              },
              {
                "type": "bulletList",
                "content": [
                  {
                    "type": "listItem",
                    "content": [
                      {
                        "type": "paragraph",
                        "content": [
                          {
                            "type": "text",
                            "text": "Catat volume pekerjaan dan waktu yang diperlukan."
                          }
                        ]
                      }
                    ]
                  }
                ]
              },
              {
                "type": "orderedList",
                "attrs": {
                  "start": 1,
                  "type": "1"
                },
                "content": [
                  {
                    "type": "listItem",
                    "content": [
                      {
                        "type": "paragraph",
                        "content": [
                          {
                            "type": "text",
                            "text": "Pilih satu proses untuk percobaan."
                          }
                        ]
                      }
                    ]
                  }
                ]
              },
              {
                "type": "blockquote",
                "content": [
                  {
                    "type": "paragraph",
                    "content": [
                      {
                        "type": "text",
                        "text": "Ukur hasil sebelum memperluas penerapan.",
                        "marks": [
                          {
                            "type": "italic"
                          }
                        ]
                      }
                    ]
                  }
                ]
              },
              {
                "type": "codeBlock",
                "attrs": {
                  "language": "text"
                },
                "content": [
                  {
                    "type": "text",
                    "text": "proses → percobaan → evaluasi"
                  }
                ]
              },
              {
                "type": "horizontalRule"
              },
              {
                "type": "paragraph",
                "content": [
                  {
                    "type": "text",
                    "text": "Tetapkan pemilik proses."
                  },
                  {
                    "type": "hardBreak"
                  },
                  {
                    "type": "text",
                    "text": "Tinjau hasil secara berkala."
                  }
                ]
              }
            ]
          }
        },
        "en": {
          "title": "Business automation guide",
          "excerpt": "How to choose a suitable business process for your first automation project.",
          "seoTitle": "Business automation guide | LunaBiner",
          "seoDescription": "Learn how to map repetitive work, set priorities, and evaluate the results of business process automation.",
          "richBody": {
            "type": "doc",
            "content": [
              {
                "type": "paragraph",
                "content": [
                  {
                    "type": "text",
                    "text": "Start by mapping repetitive manual processes. Choose one process for a small pilot, measure its results, and review the outcome with the process owner before expanding."
                  }
                ]
              }
            ]
          }
        }
      }
    }
  ]
}
InsightPatch
object

At least one change plus version. Omitted fields preserved. translations replaces both locales completely. Merged result validated; legacy REVIEW/SCHEDULED requires explicit DRAFT/PUBLISHED, missing category requires categoryId. Archived409. Published/scheduled edits or publishing require publish scope.

minProperties: 2additionalProperties: false

version · required

integer
minimum: 1

slug · optional

categoryId · optional

string
format: "uuid"

status · optional

string
enum: ["DRAFT","PUBLISHED"]

authorName · optional

string
maxLength: 100

tags · optional

array
maxItems: 30

items

string
minLength: 1maxLength: 100

translations · optional

JSON schema lengkap
{
  "type": "object",
  "description": "At least one change plus version. Omitted fields preserved. translations replaces both locales completely. Merged result validated; legacy REVIEW/SCHEDULED requires explicit DRAFT/PUBLISHED, missing category requires categoryId. Archived409. Published/scheduled edits or publishing require publish scope.",
  "properties": {
    "version": {
      "type": "integer",
      "minimum": 1
    },
    "slug": {
      "$ref": "#/components/schemas/Slug"
    },
    "categoryId": {
      "type": "string",
      "format": "uuid"
    },
    "status": {
      "type": "string",
      "enum": [
        "DRAFT",
        "PUBLISHED"
      ]
    },
    "authorName": {
      "type": "string",
      "maxLength": 100
    },
    "tags": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1,
        "maxLength": 100
      },
      "maxItems": 30
    },
    "translations": {
      "$ref": "#/components/schemas/TranslationsInput"
    }
  },
  "required": [
    "version"
  ],
  "additionalProperties": false,
  "minProperties": 2,
  "examples": [
    {
      "version": 2,
      "status": "PUBLISHED"
    }
  ]
}
Category
object
additionalProperties: false

id · required

string
format: "uuid"

slug · required

string

nameId · required

string

nameEn · required

string

version · required

integer
minimum: 1
JSON schema lengkap
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "slug": {
      "type": "string"
    },
    "nameId": {
      "type": "string"
    },
    "nameEn": {
      "type": "string"
    },
    "version": {
      "type": "integer",
      "minimum": 1
    }
  },
  "required": [
    "id",
    "slug",
    "nameId",
    "nameEn",
    "version"
  ],
  "additionalProperties": false
}
Translation
object
additionalProperties: false

title · required

string

excerpt · required

string

body · required

string

Derived plain text; response-only.

richBody · required

seoTitle · required

string

seoDescription · required

string
JSON schema lengkap
{
  "type": "object",
  "properties": {
    "title": {
      "type": "string"
    },
    "excerpt": {
      "type": "string"
    },
    "body": {
      "type": "string",
      "description": "Derived plain text; response-only."
    },
    "richBody": {
      "$ref": "#/components/schemas/RichDocument"
    },
    "seoTitle": {
      "type": "string"
    },
    "seoDescription": {
      "type": "string"
    }
  },
  "required": [
    "title",
    "excerpt",
    "body",
    "richBody",
    "seoTitle",
    "seoDescription"
  ],
  "additionalProperties": false
}
Insight
object
additionalProperties: false

id · required

string
format: "uuid"

slug · required

string

status · required

enum: ["DRAFT","REVIEW","SCHEDULED","PUBLISHED","ARCHIVED"]

version · required

integer
minimum: 1

publishedAt · required

anyOf

string
format: "date-time"
null

updatedAt · required

string
format: "date-time"

deletedAt · required

anyOf

string
format: "date-time"
null

translations · required

object
additionalProperties: false

id · required

en · required

details · required

object
additionalProperties: false

category · required

string

authorName · required

string

tags · required

array

items

string

image · required

string

Current internal cover reference or empty/legacy image. Draft preview requires existing admin content:read session, not API Bearer.

category · required

anyOf

null
JSON schema lengkap
{
  "type": "object",
  "properties": {
    "id": {
      "type": "string",
      "format": "uuid"
    },
    "slug": {
      "type": "string"
    },
    "status": {
      "enum": [
        "DRAFT",
        "REVIEW",
        "SCHEDULED",
        "PUBLISHED",
        "ARCHIVED"
      ]
    },
    "version": {
      "type": "integer",
      "minimum": 1
    },
    "publishedAt": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time"
        },
        {
          "type": "null"
        }
      ]
    },
    "updatedAt": {
      "type": "string",
      "format": "date-time"
    },
    "deletedAt": {
      "anyOf": [
        {
          "type": "string",
          "format": "date-time"
        },
        {
          "type": "null"
        }
      ]
    },
    "translations": {
      "type": "object",
      "properties": {
        "id": {
          "$ref": "#/components/schemas/Translation"
        },
        "en": {
          "$ref": "#/components/schemas/Translation"
        }
      },
      "required": [
        "id",
        "en"
      ],
      "additionalProperties": false
    },
    "details": {
      "type": "object",
      "properties": {
        "category": {
          "type": "string"
        },
        "authorName": {
          "type": "string"
        },
        "tags": {
          "type": "array",
          "items": {
            "type": "string"
          }
        },
        "image": {
          "type": "string",
          "description": "Current internal cover reference or empty/legacy image. Draft preview requires existing admin content:read session, not API Bearer."
        }
      },
      "required": [
        "category",
        "authorName",
        "tags",
        "image"
      ],
      "additionalProperties": false
    },
    "category": {
      "anyOf": [
        {
          "$ref": "#/components/schemas/Category"
        },
        {
          "type": "null"
        }
      ]
    }
  },
  "required": [
    "id",
    "slug",
    "status",
    "version",
    "publishedAt",
    "updatedAt",
    "deletedAt",
    "translations",
    "details",
    "category"
  ],
  "additionalProperties": false
}
InsightResult
object
additionalProperties: false

data · required

JSON schema lengkap
{
  "type": "object",
  "properties": {
    "data": {
      "$ref": "#/components/schemas/Insight"
    }
  },
  "required": [
    "data"
  ],
  "additionalProperties": false
}
InsightList
object
additionalProperties: false

data · required

array
maxItems: 6

pagination · required

object
additionalProperties: false

total · required

integer
minimum: 0

pages · required

integer
minimum: 1

page · required

integer
minimum: 1

pageSize · required

const: 6
JSON schema lengkap
{
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Insight"
      },
      "maxItems": 6
    },
    "pagination": {
      "type": "object",
      "properties": {
        "total": {
          "type": "integer",
          "minimum": 0
        },
        "pages": {
          "type": "integer",
          "minimum": 1
        },
        "page": {
          "type": "integer",
          "minimum": 1
        },
        "pageSize": {
          "const": 6
        }
      },
      "required": [
        "total",
        "pages",
        "page",
        "pageSize"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "data",
    "pagination"
  ],
  "additionalProperties": false
}
CategoryList
object
additionalProperties: false

data · required

array
maxItems: 100

pagination · required

object
additionalProperties: false

total · required

integer
minimum: 0

pages · required

integer
minimum: 1

page · required

integer
minimum: 1

pageSize · required

const: 100
JSON schema lengkap
{
  "type": "object",
  "properties": {
    "data": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Category"
      },
      "maxItems": 100
    },
    "pagination": {
      "type": "object",
      "properties": {
        "total": {
          "type": "integer",
          "minimum": 0
        },
        "pages": {
          "type": "integer",
          "minimum": 1
        },
        "page": {
          "type": "integer",
          "minimum": 1
        },
        "pageSize": {
          "const": 100
        }
      },
      "required": [
        "total",
        "pages",
        "page",
        "pageSize"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "data",
    "pagination"
  ],
  "additionalProperties": false
}
Error
object
additionalProperties: false

error · required

object
additionalProperties: false

code · required

string

message · required

string

fields · optional

array
maxItems: 20

items

object
additionalProperties: false

path · required

string

Detail lanjutan tersedia pada JSON schema lengkap di bawah atau download OpenAPI.

message · required

string

Detail lanjutan tersedia pada JSON schema lengkap di bawah atau download OpenAPI.

JSON schema lengkap
{
  "type": "object",
  "properties": {
    "error": {
      "type": "object",
      "properties": {
        "code": {
          "type": "string"
        },
        "message": {
          "type": "string"
        },
        "fields": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "path": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "path",
              "message"
            ],
            "additionalProperties": false
          },
          "maxItems": 20
        }
      },
      "required": [
        "code",
        "message"
      ],
      "additionalProperties": false
    }
  },
  "required": [
    "error"
  ],
  "additionalProperties": false
}
CoverUpload
object
additionalProperties: false

file · required

string

Static JPEG/PNG/WebP bytes, 1..5242880 bytes. Filename ignored; no remote URL. Re-encoded WebP, max1600x1600, source<=16MP and dimensions<=8000. Animated/corrupt/mismatched MIME422.

format: "binary"

version · required

string

Decimal positive int <=2147483647. Use latest article version.

pattern: "^[1-9][0-9]{0,9}$"
JSON schema lengkap
{
  "type": "object",
  "properties": {
    "file": {
      "type": "string",
      "description": "Static JPEG/PNG/WebP bytes, 1..5242880 bytes. Filename ignored; no remote URL. Re-encoded WebP, max1600x1600, source<=16MP and dimensions<=8000. Animated/corrupt/mismatched MIME422.",
      "format": "binary"
    },
    "version": {
      "type": "string",
      "description": "Decimal positive int <=2147483647. Use latest article version.",
      "pattern": "^[1-9][0-9]{0,9}$"
    }
  },
  "required": [
    "file",
    "version"
  ],
  "additionalProperties": false
}

07 / Reliability

Retry yang menjaga perubahan Anda.

Create: satu key, satu operasi.

Idempotency-Key wajib 16–128 karakter huruf/angka, underscore, atau hyphen. Payload sama mereplay snapshot awal selama 7 hari. Key sama dengan payload berbeda menghasilkan 409.

Rotation mempertahankan identity. Credential baru atau key yang sudah dibersihkan setelah expiry tidak mempunyai jaminan replay lama. Simpan logical history sendiri.

Update: gunakan latest version.

PATCH dan cover wajib version. Pada 409, GET current state dan review perubahan sebelum retry. Jangan menimpa perubahan admin otomatis.

Sesudah timeout/503, commit bisa sudah terjadi. Periksa version/status/referensi terbaru dahulu. Cache warning pada respons sukses tidak membatalkan commit.

PATCH mempertahankan field yang tidak dikirim. Jika translations dikirim, ID dan EN lengkap diganti. Publish atau perubahan artikel terbit/terjadwal memerlukan scope publish tambahan.

07 / Responses

Batas yang bisa diprediksi.

60 / menit

per credential

1 MiB

JSON body

5 MiB

file cover

5 MiB + 16 KiB

multipart envelope

400

Body tidak valid

Perbaiki format JSON, multipart, atau Content-Length.

401

Credential tidak valid

Periksa token, expiry, revocation, atau rotation bersama operator.

403

Scope tidak cukup

Periksa izin. Login admin bukan pengganti token API.

404

Artikel tidak ditemukan

Gunakan UUID artikel, bukan slug atau UUID jenis konten lain.

405

Method tidak didukung

Ikuti method endpoint. OPTIONS tidak membuka CORS.

409

Konflik

Periksa code: version, slug, arsip, atau idempotency. Rekonsiliasi sebelum retry.

413 / 415 / 422

Request perlu diperbaiki

Periksa ukuran, MIME, field, rich text, dan kategori.

429

Rate limit

Tunggu sesuai Retry-After, lalu gunakan backoff dengan batas percobaan.

503

Layanan belum tersedia

Create: retry key yang sama. Update/cover: GET state terbaru dahulu.

Contoh error json
{
  "error": {
    "code": "VERSION",
    "message": "Artikel berubah. Ambil version terbaru."
  }
}

List artikel: 6 per halaman. Kategori: 100 per halaman. Pagination memakai total, pages, page, dan pageSize. Respons authenticated bersifat private, no-store. Query maksimal 2.048 byte dan compression tidak didukung.