# API Spec

> Author the custom-YAML API spec at docs/api/ — the spec-first source of truth for every HTTP endpoint; producer at the head of the api-doc chain. Create, update, or validate _meta.yaml plus one <domain>/<endpoint>.yaml per endpoint (method/path/auth, request/response fields with M/O + Remark, multi-flow business_logic, errors, covers_ac); three-layer verify (apispeccheck.py + fresh-eyes + completeness sweep) + generated index.md. Not OpenAPI. Use when designing an API contract spec-first before code exists (Draft, API step), authoring or adding endpoints, updating the spec from built code (structural sync-back, DOC step), or validating it. Triggers: author/create/write/gen/update api spec, update api spec from code, add an endpoint, validate api spec, ออกแบบ/อัปเดต api spec จาก code. Route elsewhere (read-only consumers): Go-vs-spec drift → openapi-doc; Bruno collection → open-collection; Confluence → confluence-api-doc. Authoring never scans Go — only the from-code sync-back reads it.

- Skill: `witooh/api-spec` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add witooh/api-spec`
- Raw SKILL.md: https://api.skillmd.com/api/skills/witooh/api-spec/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: witooh (https://skillmd.com/u/witooh)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/witooh/api-spec

---


# API Spec (producer)

Author the custom-YAML **API spec** at `docs/api/` — the spec-first **source of truth** for every HTTP endpoint, from which the whole api-doc chain derives (`openapi-doc` drift-checks Go against it; `open-collection` renders a runnable Bruno collection; `confluence-api-doc` publishes Confluence pages). This skill is the **producer** — it writes `docs/api/*.yaml`; the three consumers only read it. Every spec rests on **evidence (a deterministic script) + an independent fresh-eyes pass + a completeness sweep**, never on the running agent's confidence.

`ASSET_DIR` = `<skill base dir>/assets`, `SKILL_DIR` = `<skill base dir>` (the skill-load message gives the "Base directory for this skill"). Needs PyYAML (same dependency as the rest of the api-doc chain).

## Output location

```
docs/api/
  _meta.yaml                   # global: title · version · base_url · overview · field_info · common_errors · domains
  <domain>/<endpoint>.yaml     # one endpoint per file (domain = the resource group, e.g. account/, balance/)
  index.md                     # navigation INDEX — GENERATED by apispeccheck.py (never hand-edit)
  VERSION.md                   # changelog (hand-maintained markdown, newest section first)
```

The full schema (every key, field-object rules, error rules, notes discipline) lives in [`references/api-spec-template.md`](references/api-spec-template.md) — read it before authoring. The api-spec is **YAML/markdown, never HTML**, and is **not** OpenAPI.

## Mode

Four modes. **Draft** and **Generate** both author a spec where none exists yet; the difference is intent and rigor — Draft is a *provisional pre-plan design pass*, Generate is the *authoritative author*. Auto-detect (user can override):

- **Draft** (design-first) — signalled by using-neo's API step routing here, or a "draft / ออกแบบ … ก่อน" request: author a **provisional** contract from intent **only** (acceptance criteria / requirements / `docs/knowledge/`), **before any code exists**, so a feature has a contract to design against from the start. It writes the same `docs/api/*.yaml` as Generate — **single producer, single location** (not a separate artifact); its provisional nature is contextual — it is drafted before the code exists — with **no marker** in the spec. Draft authors the **structural contract only** — method/path/auth, request/response fields, `errors`, and `covers_ac` — and **omits `business_logic`**: the per-endpoint flow is a design inference that must not be pinned before the code exists, so it is left out and added later (once the real implementation flow is known, e.g. during BUILD or the DOC-step sync-back), then preserved by Update-from-code. **Lighter verify** (L1 + L3 only — see Step 3). The DOC-step **Update-from-code** later reconciles this draft against the built code.
- **Generate** — no `docs/api/` (or no `_meta.yaml`) at the target, and not a Draft request → author the spec from intent (full three-layer verify).
- **Update** — a spec already present → apply the delta from intent (a new endpoint, a changed field), **or** reconcile the spec against built code. The DOC-step **Update-from-code** (sync-back) reads the Go source and syncs only the **structural surface** — routes, request/response fields, types — into the spec, **preserving** all hand-authored M/O, `business_logic`, `remark`, and `errors` (a structural sync, never a regenerate-over-top).
- **Validate** — request says "validate/check/เช็ค/ตรงกับ … ไหม" with a spec present → no writes.

## Step 1 · Locate the source-of-intent + the spec root

- **Spec root** — default `docs/api/` at the repo root; in a monorepo scope it to the chosen service (e.g. `services/<name>/docs/api/`). Ask if ambiguous.
- **Source-of-intent** — what the spec must reflect (the input the spec is authored *from*):
  - **Draft** — intent **only**: acceptance-criteria (`docs/tasks/<card>/spec.md`) / requirements / a JIRA card / `docs/knowledge/` / the user's description. **Exclude code** — the code does not exist yet (and is not consulted even if some does). No locatable AC/requirements → STOP and ask; never invent an endpoint surface.
  - **Update / Validate** — the existing `docs/api/*.yaml` is the baseline; the delta is the change request (a new endpoint, a changed field), or — for **Update-from-code** (sync-back) — the built Go source being reconciled back into the spec.
  - **Generate** — gather the intent from whatever exists: a requirements doc / acceptance-criteria (`docs/tasks/<card>/spec.md`) / a JIRA card / `docs/knowledge/` / the user's description / existing handler+struct code to document. **No locatable intent at all → STOP and ask** for the requirements; never invent an endpoint surface.
- Read `CLAUDE.md` / `AGENTS.md` / `README` for the service name, base URL, and domain grouping.
- **When driven from a task spec** (using-neo) the intent is the spec's acceptance criteria; record the AC-IDs each endpoint satisfies in `covers_ac`. **Standalone**, omit `covers_ac`.

## Step 2 · Author / update the endpoint YAML

Write per [`references/api-spec-template.md`](references/api-spec-template.md):

- **`_meta.yaml`** — service title/version/base_url/overview, shared `field_info` enums, `common_errors` (errors every endpoint may return), per-domain `seq`/title in `domains`, and `extra_endpoints` for index-only rows (e.g. a health probe).
- **`<domain>/<endpoint>.yaml`** — one file per endpoint: method/path/auth, `path_params`/`query_params`/`request_body` field tables, `responses` (with nested `objects:`), `business_logic` (preserve multi-flow as bold-subheaded numbered lists — **Generate/Update only; Draft omits it**, see Mode), endpoint-specific `errors`, and `covers_ac` when driven from a spec. A field's element-specific note → its `remark`; only a genuinely cross-cutting note → `notes:` (omit when empty).
- **Update** — touch the minimum; preserve hand-authored prose; re-run the L1 check after. For **Update-from-code** (DOC-step sync-back): read the Go source and sync only the structural surface (routes / fields / types); **preserve** M/O, `business_logic`, `remark`, and `errors` — never regenerate over hand-authored semantics.
- **VERSION.md** — append a `## v<N> — <date>` section (newest first) per change.

Do **not** scan Go to **author** (Draft / Generate / intent-only Update) — those are authored from intent. **Only Update-from-code** reads Go, and only to reconcile the structural surface against a built implementation (sync-back); the read-only drift *report* remains `openapi-doc`.

## Step 3 · Three-layer verify

**Draft mode runs a lighter verify — L1 + L3 only; skip L1.5/L2.** A draft is a provisional design pass, so it must be structurally valid (L1) and cover every AC that needs an endpoint (L3), but the independent fresh-eyes semantic pass (L2) is **deferred to the DOC-step Update** that reconciles the draft against built code — the point at which the contract is finalized and worth an independent semantic review. Generate / Update run all three layers below.

### verify-L1 · Script tripwire (always)

```
python3 <ASSET_DIR>/apispeccheck.py docs/api      # validates every endpoint YAML + regenerates index.md
```

It checks each endpoint parses + has the required keys; `mandatory ∈ {M,O}`; every `example` JSON parses; every `object:` reference resolves; `_meta.yaml` is well-formed; and it rewrites `index.md` so it can never drift. **Tripwire, not ground truth.**

- **exit 0** → `PASS — 0 error(s)` → go to L1.5.
- **exit 1** → for each ERROR, open the actual YAML, fix the real defect, re-run. **Loop until exit 0, OR ~3 rounds with no progress → STOP and escalate.** Never fake a green run.

### verify-L1.5 · Offer fresh-eyes (default yes)

Ask once via `AskUserQuestion`: *"Run an independent fresh-eyes verify of the authored api-spec? (default: yes)"* — **no** → skip L2 (mark "skipped by user"); **yes** → L2.

### verify-L2 · Fresh-eyes semantic verifier (independent agent)

Dispatch a verifier that did **not** author the spec — it re-reads the **source-of-intent** and the YAML independently and judges the semantic fidelity the script cannot (does the contract reflect the intent, M/O vs business rules, errors covering failure paths, business_logic matching the real flow, example consistency):

```
Agent(subagent_type: "fresh-eyes", description: "verify api-spec", prompt: """
# Role: API-Spec Semantic Verifier
Read first: <SKILL_DIR>/references/api-spec-verifier.md
SKILL_DIR = <skill base dir>

## Task
Independently verify the api-spec just authored against its source-of-intent. Check ONLY
semantic fidelity (not apispeccheck.py's structural checks). Read the intent AND docs/api/*.yaml yourself.

## Under review
docs/api/<domain>/*.yaml (+ docs/api/_meta.yaml)

## Source-of-intent
<paste the intent paths: requirements / docs/tasks/<card>/spec.md / JIRA card / docs/knowledge/ / code>

End with Status: DONE | DONE_WITH_CONCERNS | BLOCKED
""")
```

`SKILL_DIR` is mandatory — without it the verifier cannot read its role file and fails silently. The verifier is read-only by tool grant — `fresh-eyes` holds no write/edit (harness without that type → `general-purpose`, read-only by instruction only) → **you** reconcile the YAML → re-run `apispeccheck.py`. Do not auto-redispatch; offer a second round (default yes), then escalate.

### verify-L3 · Completeness sweep (omission critic)

L1/L2 inspect what is present; L3 catches what is **missing entirely**. Re-enumerate the **full intended endpoint inventory** from the source-of-intent (every endpoint the requirements/AC call for, or — in sync-back — every route an `openapi-doc` drift report flagged) and confirm: every intended endpoint has a `docs/api/<domain>/*.yaml`, every endpoint is reachable from `index.md`, every `covers_ac` AC-ID (when driven from a spec) is real, and no endpoint file is an orphan with no intent behind it. Report any whole endpoint the pipeline silently dropped; fix → re-run L1.

### Output

```
## API Spec — <Generate / Update / Validate>
**Spec:** docs/api/ (N endpoints)   **Source-of-intent:** <what it was authored from>
**Changes:** Created … / Updated … / Removed …
**Verification (three-layer):**
- L1 apispeccheck.py: ✅ PASS (0 error) / ❌ ESCALATED (N error after ~3 rounds) · loop rounds: 0-3
- L2 fresh-eyes: ✅ Faithful / ⚠️ N gaps fixed / ⏭ Skipped / ⏸ Not run
- L3 completeness sweep: ✅ all intended endpoints covered / ⚠️ N silent omissions fixed
- Verdict: ✅ in sync / ⚠️ gaps to reconcile / ⏸ escalated
**Warnings:** spec-first endpoints not yet built, open questions surfaced for a human
```

---

## What this skill is NOT

- **Not** a read-only drift *reporter* — producing a report of whether the Go code matches the spec **without writing** is **`openapi-doc`**. This skill *reconciles* the spec to code (Update-from-code, which writes); the pure drift report stays with `openapi-doc`.
- **Not** a Bruno OpenCollection generator (**`open-collection`**) or a Confluence publisher (**`confluence-api-doc`**) — both also read `docs/api/*.yaml`. This skill is the single **producer**; those three are **consumers**.
- **Not** an OpenAPI / Swagger / Postman converter — the custom-YAML api-spec is the single source of truth (it carries M/O + Remark + multi-flow business logic that OpenAPI cannot without `x-` hacks). There is no OpenAPI intermediate in this chain.
- **Authoring never scans Go** — Draft / Generate / intent-only Update are authored from **intent**, before the code. The one exception is **Update-from-code** (DOC-step sync-back), which reads Go to reconcile the *structural* surface (routes / fields / types) into an existing spec while preserving hand-authored semantics.

