Articles
What an article is in Scripto — its fields, its draft/published lifecycle, per-user isolation, and the derived content invariant.
An article is one story. It is the top-level unit you create, edit, publish, and import. Under the hood it is a database row plus an ordered list of sections.
Fields
| Field | Type | Notes |
|---|---|---|
id | UUID | The article’s stable id. Used in every CLI command and API route. |
title | string | Derived server-side from the first heading of the body. |
subtitle | string | null | The dek shown under the title; Medium reads it as the article subtitle. |
premise | string | null | Internal orientation paragraph — the prose half of the Brief. Never published. |
content | string | Semantic HTML. The derived concatenation of the sections’ HTML in order. |
coverImage | string | null | First image in the body (denormalized for the list card). |
wordCount | integer | Recomputed whenever the body changes. |
slug | string | null | Assigned at publish; the public URL segment. Null while a draft. Unique. |
status | draft | published | A new article starts as draft. |
createdAt / updatedAt / publishedAt | timestamps | publishedAt is stamped on first publish. |
content is derived, never authored directly through the section path. The invariant content === concat(sections.html) holds on every write, so the editor and the public story page can keep reading the single content blob while the agent edits sections. See the sections model.
Lifecycle
Draft
scripto articles create --file draft.md (or POST /api/articles + a body update) creates a draft. Drafts have no slug and aren’t publicly reachable.
Published
scripto articles publish <id> assigns a slug once, sets status: published, stamps publishedAt, and returns the public /story/<slug> URL. Idempotent — re-publishing keeps the slug and original date.
There is no separate “archived” state; an article is a draft or published, and scripto articles (via DELETE /api/articles/[id]) removes it outright (its sections and notes cascade away).
Per-user isolation
Every article, section, and note row is scoped by userId. A request for an article you don’t own returns 404, never 403 — Scripto never leaks that an id exists. This holds identically whether you authenticate with a session cookie (web), a scripto_ Bearer key (CLI), or an OAuth token (MCP).
Create an article
scripto articles create --file draft.md The server splits the Markdown into sections, renders each, and derives the title from the first heading.
# Create an empty draft, then set its body.
curl -sS -X POST https://scripto.codika.io/api/articles \
-H "Authorization: Bearer $SCRIPTO_API_KEY"
# → { "success": true, "data": { "article": { "id": "…" } } } Next
- The ordered-sections model — how an article’s body is structured.
- The Brief — the unpublished context behind an article.
- Import, don’t paste — the publish-and-import promise.