---
title: Articles
description: 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](/concepts/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](/concepts/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. |

<Note>

`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](/concepts/sections).

</Note>

## Lifecycle

<Steps>

<Step title="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.

</Step>

<Step title="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.

</Step>

</Steps>

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

<Tabs>

<Tab label="CLI">

```bash
scripto articles create --file draft.md
```

The server splits the Markdown into sections, renders each, and derives the title from the first heading.

</Tab>

<Tab label="HTTP">

```bash
# 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": "…" } } }
```

See [POST /api/articles](/api-reference/create-article) and [PATCH /api/articles/[id]](/api-reference/update-article).

</Tab>

</Tabs>

## Next

- **[The ordered-sections model](/concepts/sections)** — how an article's body is structured.
- **[The Brief](/concepts/brief)** — the unpublished context behind an article.
- **[Import, don't paste](/concepts/import-model)** — the publish-and-import promise.
