diff --git a/.charter/telemetry/events.ndjson b/.charter/telemetry/events.ndjson
new file mode 100644
index 0000000..692bc37
--- /dev/null
+++ b/.charter/telemetry/events.ndjson
@@ -0,0 +1,9 @@
+{"version":1,"timestamp":"2026-03-04T11:38:44.194Z","commandPath":"quickstart","flags":["--version"],"format":"text","ciMode":false,"durationMs":1,"exitCode":0,"success":true}
+{"version":1,"timestamp":"2026-03-04T11:38:48.692Z","commandPath":"setup","flags":["--detect-only","--format"],"format":"json","ciMode":false,"durationMs":8,"exitCode":0,"success":true}
+{"version":1,"timestamp":"2026-03-04T11:38:52.119Z","commandPath":"setup","flags":["--preset","--yes"],"format":"text","ciMode":false,"durationMs":22,"exitCode":0,"success":true}
+{"version":1,"timestamp":"2026-03-04T11:38:56.228Z","commandPath":"doctor","flags":[],"format":"text","ciMode":false,"durationMs":35,"exitCode":0,"success":true}
+{"version":1,"timestamp":"2026-03-04T11:39:00.252Z","commandPath":"adf.init","flags":[],"format":"text","ciMode":false,"durationMs":14,"exitCode":0,"success":true}
+{"version":1,"timestamp":"2026-03-04T11:39:04.156Z","commandPath":"doctor","flags":[],"format":"text","ciMode":false,"durationMs":49,"exitCode":0,"success":true}
+{"version":1,"timestamp":"2026-03-04T11:39:08.702Z","commandPath":"adf.migrate","flags":["--dry-run"],"format":"text","ciMode":false,"durationMs":22,"exitCode":0,"success":true}
+{"version":1,"timestamp":"2026-03-04T11:39:50.202Z","commandPath":"adf.migrate","flags":[],"format":"text","ciMode":false,"durationMs":45,"exitCode":0,"success":true}
+{"version":1,"timestamp":"2026-03-04T11:39:54.319Z","commandPath":"doctor","flags":[],"format":"text","ciMode":false,"durationMs":54,"exitCode":0,"success":true}
diff --git a/ADF_1.png b/ADF_1.png
new file mode 100644
index 0000000..83bc7cf
Binary files /dev/null and b/ADF_1.png differ
diff --git a/src/content/docs/getting-started.md b/src/content/docs/getting-started.md
index 6f5ea94..fa377e6 100644
--- a/src/content/docs/getting-started.md
+++ b/src/content/docs/getting-started.md
@@ -1,108 +1,345 @@
----
-title: "Getting Started"
-description: "Install Charter CLI, configure governance presets, and enforce compliance in your first project in under five minutes."
-section: "charter"
-order: 1
-color: "#2ea043"
-tag: "01"
----
-
-# Getting Started
-
-Charter is a local-first governance toolkit with a built-in AI context compiler. It validates commit governance, detects stack drift, scores risk, and ships **ADF (Attention-Directed Format)** — a modular, AST-backed context system that replaces monolithic `.cursorrules` and `claude.md` files. Everything runs locally and in CI, with no SaaS dependency.
-
-## Install
-
-Recommended: local dev dependency per repo.
-
-```bash
-npm install --save-dev @stackbilt/cli
-```
-
-pnpm workspace root:
-
-```bash
-pnpm add -Dw @stackbilt/cli
-```
-
-Global (optional, puts `charter` on your PATH):
-
-```bash
-npm install -g @stackbilt/cli
-```
-
-## Bootstrap a Repo
-
-```bash
-# Preview what charter detects — no files written
-npx charter setup --detect-only --format json
-
-# Write governance baseline + optional CI workflow
-npx charter setup --ci github --yes
-
-# Mixed repos (frontend + backend): choose preset explicitly
-npx charter setup --detect-only
-npx charter setup --preset fullstack --ci github --yes
-```
-
-`setup` writes:
-- `.charter/config.json` — governance baseline config
-- `.charter/patterns/*.json` — blessed-stack pattern definitions
-- `.charter/policies/*.md` — human-readable policy summary
-- `.github/workflows/charter-governance.yml` — CI workflow (if `--ci github`)
-
-## Set Up ADF Context
-
-ADF turns your LLM context into a compiled, modular system. Scaffold the `.ai/` directory:
-
-```bash
-# Scaffold .ai/ with manifest, core, and state modules
-npx charter adf init
-
-# Verify everything parses and syncs
-npx charter doctor --format json
-```
-
-This creates (for default presets):
-
-```text
-.ai/
- manifest.adf # Module registry: default-load vs on-demand with triggers
- core.adf # Always-loaded: role, constraints, metric ceilings
- state.adf # Session state: current task, decisions, blockers
- frontend.adf # Frontend module scaffold (on-demand, triggers: React, CSS, UI)
- backend.adf # Backend module scaffold (on-demand, triggers: API, Node, DB)
-```
-
-For documentation-heavy repos, use `--preset docs` to get `decisions.adf` and `planning.adf` instead of frontend/backend modules.
-
-Edit `.ai/core.adf` to define your project constraints and LOC ceilings. The `METRICS [load-bearing]` section enforces hard limits that CI can gate on.
-
-## Verify the Setup
-
-```bash
-npx charter doctor --format json
-npx charter
-```
-
-`charter` with no arguments prints a live governance snapshot: risk score, governed commit ratio, drift status.
-
-`doctor` validates environment health including ADF readiness: manifest existence, module parse status, and sync lock integrity.
-
-## Run an Evidence Check
-
-If you have ADF set up with metric ceilings, run the evidence pipeline:
-
-```bash
-# Validate all metric ceilings and produce a structured report
-npx charter adf evidence --auto-measure
-
-# CI mode: exits 1 if any ceiling is breached
-npx charter adf evidence --auto-measure --ci --format json
-```
-
-## What's Next
-
-- [CLI Reference](/cli-reference) — full command surface
-- [CI Integration](/ci-integration) — GitHub Actions workflow with evidence gating
-- [Ecosystem](/ecosystem) — how Charter fits into the StackBilt platform
+---
+title: "Getting Started"
+description: "Install Charter CLI, configure governance presets, and enforce compliance in your first project in under five minutes."
+section: "charter"
+order: 1
+color: "#2ea043"
+tag: "01"
+---
+
+# Charter Kit
+
+## The Problem
+
+You write a CLAUDE.md. You add a `.cursorrules`. You paste instructions into GEMINI.md. Your AI agent loads all of it into the context window -- 10,000 tokens of flat, unstructured rules competing with the actual work.
+
+Half get ignored. You don't know which half.
+
+## The Solution
+
+Charter is an open-source CLI that replaces monolithic agent config files with **ADF (Attention-Directed Format)** -- a modular context system where agents load only the rules they need for the current task.
+
+Instead of one big file, you get a manifest. The manifest declares modules, trigger keywords that load them on demand, token budgets, and weighted sections that tell the agent what's load-bearing vs. advisory.
+
+```bash
+npm install --save-dev @stackbilt/cli
+npx charter bootstrap --yes # detect stack, scaffold .ai/, migrate existing rules
+```
+
+## Five-Minute Adoption
+
+Already have agent config files? Charter migrates them:
+
+```bash
+# See what would happen (dry run)
+charter adf migrate --dry-run
+
+# Migrate: classifies rules by strength, routes to ADF modules, replaces originals with thin pointers
+charter adf migrate
+```
+
+Your `CLAUDE.md` / `.cursorrules` / `GEMINI.md` content gets classified (imperative vs. advisory vs. neutral), routed to the right module (frontend rules to `frontend.adf`, backend rules to `backend.adf`), and your originals become one-line pointers to `.ai/`. No content lost, no rewrite needed.
+
+## How ADF Works
+
+Charter manages context through the `.ai/` directory:
+
+```text
+.ai/
+ manifest.adf # Module registry: default-load vs on-demand with trigger keywords
+ core.adf # Always-loaded: role, constraints, output format, metric ceilings
+ state.adf # Session state: current task, decisions, blockers
+ frontend.adf # On-demand: loaded when task mentions "react", "css", etc.
+ backend.adf # On-demand: loaded when task mentions "endpoint", "REST", etc.
+```
+
+When you run `charter adf bundle --task "Fix the React login component"`, Charter:
+1. Reads the manifest
+2. Loads `core.adf` and `state.adf` (always loaded)
+3. Sees "React" matches a trigger keyword -- loads `frontend.adf`
+4. Skips `backend.adf` (no matching triggers)
+5. Merges the loaded modules into a single context payload with token budget tracking
+
+The agent gets exactly the rules it needs. Nothing more.
+
+### Format Example
+
+```text
+ADF: 0.1
+
+TASK:
+ Build the user dashboard
+
+CONTEXT:
+ - React 18 with TypeScript
+ - TailwindCSS for styling
+ - REST API at /api/v2
+
+CONSTRAINTS [load-bearing]:
+ - No external state libraries
+ - Must support SSR
+
+METRICS [load-bearing]:
+ entry_loc: 142 / 500 [lines]
+ handler_loc: 88 / 300 [lines]
+
+STATE:
+ CURRENT: Implementing layout grid
+ NEXT: Add data fetching
+ BLOCKED: Waiting on API schema
+```
+
+Sections use emoji decorations for attention signaling, support four content types (text, list, key-value map, and metric with value/ceiling/unit), and follow a canonical ordering the formatter enforces. `[load-bearing]` vs `[advisory]` weight annotations distinguish measurable constraints from preferences. Metric entries (`key: value / ceiling [unit]`) define hard ceilings that the `evidence` command validates automatically.
+
+## Self-Governance: Charter Enforces Its Own Rules
+
+This isn't theoretical. Charter uses ADF to govern its own codebase. The `.ai/` directory in this repository contains the same modules and metric ceilings that any adopting repo would use.
+
+Every commit runs through a pre-commit hook that executes `charter adf evidence --auto-measure`. If a source file exceeds its declared LOC ceiling, the commit is rejected. We can't ship code that violates our own governance rules -- even by accident, even at 2am.
+
+Here is the actual output from Charter's own evidence check (v0.7.0):
+
+```text
+ ADF Evidence Report
+ ===================
+ Modules loaded: core.adf, state.adf
+ Token estimate: ~494
+ Token budget: 4000 (12%)
+
+ Auto-measured:
+ adf_commands_loc: 618 lines (packages/cli/src/commands/adf.ts)
+ adf_bundle_loc: 175 lines (packages/cli/src/commands/adf-bundle.ts)
+ adf_sync_loc: 213 lines (packages/cli/src/commands/adf-sync.ts)
+ adf_evidence_loc: 272 lines (packages/cli/src/commands/adf-evidence.ts)
+ adf_migrate_loc: 474 lines (packages/cli/src/commands/adf-migrate.ts)
+ bundler_loc: 125 lines (packages/adf/src/bundler.ts)
+ parser_loc: 214 lines (packages/adf/src/parser.ts)
+ cli_entry_loc: 191 lines (packages/cli/src/index.ts)
+
+ Constraints:
+ [ok] adf_commands_loc: 618 / 650 [lines] -- PASS
+ [ok] adf_bundle_loc: 175 / 200 [lines] -- PASS
+ [ok] adf_sync_loc: 213 / 250 [lines] -- PASS
+ [ok] adf_evidence_loc: 272 / 380 [lines] -- PASS
+ [ok] adf_migrate_loc: 474 / 500 [lines] -- PASS
+ [ok] bundler_loc: 125 / 500 [lines] -- PASS
+ [ok] parser_loc: 214 / 300 [lines] -- PASS
+ [ok] cli_entry_loc: 191 / 200 [lines] -- PASS
+
+ Verdict: PASS
+```
+
+What this shows:
+
+- **Metric ceilings enforce LOC limits on source files.** Each metric in a `.adf` module declares a ceiling. `--auto-measure` counts lines live from the sources referenced in the manifest.
+- **Self-correcting architecture.** When `bundler_loc` hit 413/500, Charter's own evidence gate flagged the pressure. The file was split into three focused modules (`manifest.ts`, `merger.ts`, `bundler.ts`) -- now 125/500. The system caught the problem and the system verified the fix.
+- **CI gating.** Generated governance workflows run `doctor --adf-only --ci` and `adf evidence --auto-measure --ci` on every PR, blocking merges on ceiling breaches.
+- **Available to any repo.** This is the same system you get by running `charter adf init` in your own project.
+
+## Quick Reference
+
+```bash
+# Scaffold .ai/ with starter modules
+charter adf init
+
+# Reformat to canonical form
+charter adf fmt .ai/core.adf --write
+
+# Apply a typed patch
+charter adf patch .ai/state.adf --ops '[{"op":"ADD_BULLET","section":"STATE","value":"Reviewing PR #42"}]'
+
+# Bundle context for a task (trigger-based module loading)
+charter adf bundle --task "Fix the React login component"
+
+# Migrate existing agent configs into ADF
+charter adf migrate --dry-run
+
+# Verify .adf files haven't drifted from locked hashes
+charter adf sync --check
+
+# Validate metric constraints
+charter adf evidence --auto-measure
+
+# Recalibrate metric ceilings
+charter adf metrics recalibrate --headroom 15 --reason "Scope expansion" --dry-run
+
+# Expose ADF context as an MCP server (for Claude Code / any MCP client)
+charter serve
+```
+
+## Why Charter
+
+- **Modular AI context** -- trigger-routed `.ai/` modules replace monolithic config files
+- **Five-minute migration** -- classify and route existing CLAUDE.md / .cursorrules / GEMINI.md rules automatically
+- **MCP server** -- `charter serve` exposes your ADF context as an MCP server; Claude Code can query constraints, architectural decisions, and recent changes without reading raw files
+- **Evidence-based governance** -- metric ceilings with auto-measurement, structured pass/fail reports, CI gating
+- **Self-regulating** -- pre-commit hooks enforce constraints before code lands
+- **Commit governance** -- validate `Governed-By` and `Resolves-Request` trailers, score commit risk
+- **Drift detection** -- scan for stack drift against blessed patterns
+- **Stable JSON output** -- every command supports `--format json` with `nextActions` hints for agent workflows
+
+## Install
+
+```bash
+npm install --save-dev @stackbilt/cli
+```
+
+For pnpm workspaces: `pnpm add -Dw @stackbilt/cli`. For global install: `npm install -g @stackbilt/cli`.
+
+## Getting Started
+
+### Human Workflow
+
+```bash
+charter # Repo risk/value snapshot
+charter bootstrap --ci github # One-command onboarding
+charter doctor # Validate environment/config
+charter validate # Check commit governance
+charter drift # Scan for stack drift
+charter audit # Governance summary
+charter adf init # Scaffold .ai/ context directory
+```
+
+### Claude Code Integration (MCP)
+
+`charter serve` exposes your `.ai/` modules as an MCP server. Add it to `.claude/settings.json`:
+
+```json
+{
+ "mcpServers": {
+ "charter": {
+ "command": "charter",
+ "args": ["serve"]
+ }
+ }
+}
+```
+
+Claude Code can then call `getProjectContext`, `getArchitecturalDecisions`, `getProjectState`, and `getRecentChanges` directly — no manual `adf bundle` needed in the conversation.
+
+### Agent Workflow
+
+Prefer JSON mode and exit-code handling:
+
+```bash
+charter --format json
+charter setup --ci github --yes --format json
+charter doctor --format json
+charter validate --format json --ci
+charter drift --format json --ci
+charter audit --format json
+charter adf bundle --task "describe the task" --format json
+charter adf evidence --auto-measure --format json --ci
+charter adf sync --check --format json
+```
+
+Agent contract:
+- Inputs: git repo + optional existing `.charter/`
+- Stable machine output: `--format json` with `nextActions` hints where applicable
+- Exit codes: `0` success, `1` policy violation, `2` runtime/usage error
+- CI behavior: with `--ci`, treat `1` as gating failure and surface actionable remediation
+- Evidence: `adf evidence --ci` exits 1 on metric ceiling breaches; warnings (at boundary) don't fail
+
+
+Trailer Adoption Ramp
+
+Teams often score lower early due to missing governance trailers. Use this ramp:
+- Stage 1: run `charter validate --ci --format json` in PR CI and fail on policy violations.
+- Stage 2: add a commit template in the repo that includes `Governed-By` and `Resolves-Request`.
+- Stage 3: track audit trend; trailer coverage should rise naturally as PR gating normalizes behavior.
+
+
+
+## Cross-Platform Support
+
+Charter works across WSL, PowerShell, CMD, macOS, and Linux. All git operations use a unified invocation layer with cross-platform PATH resolution. Line endings are normalized via `.gitattributes`.
+
+## Command Reference
+
+- `charter`: show repo risk/value snapshot and recommended next action
+- `charter bootstrap [--ci github] [--preset ] [--yes] [--skip-install] [--skip-doctor]`: one-command onboarding (detect + setup + ADF + migrate + install + doctor)
+- `charter setup [--ci github] [--preset ] [--detect-only] [--no-dependency-sync]`: detect stack and scaffold `.charter/` baseline
+- `charter init [--preset ]`: scaffold `.charter/` templates only
+- `charter doctor [--adf-only]`: validate environment/config state (`--adf-only` runs strict ADF wiring checks)
+- `charter validate [--ci] [--range ]`: validate commit governance and citations
+- `charter drift [--path ] [--ci]`: run drift scan
+- `charter audit [--ci] [--range ]`: produce governance audit summary
+- `charter classify `: classify change scope heuristically
+- `charter hook install --commit-msg [--force]`: install commit-msg trailer normalization hook
+- `charter hook install --pre-commit [--force]`: install pre-commit ADF routing + evidence gate
+- `charter adf init [--ai-dir ] [--force]`: scaffold `.ai/` context directory
+- `charter adf fmt [--check] [--write]`: parse and reformat ADF files to canonical form
+- `charter adf fmt --explain`: show canonical formatter section ordering
+- `charter adf patch --ops | --ops-file `: apply typed delta operations
+- `charter adf create [--triggers "a,b,c"] [--load default|on-demand]`: create and register a module
+- `charter adf bundle --task "" [--ai-dir ]`: resolve manifest and output merged context
+- `charter adf sync --check [--ai-dir ]`: verify .adf files match locked hashes
+- `charter adf sync --write [--ai-dir ]`: update `.adf.lock` with current hashes
+- `charter adf evidence [--task ""] [--ai-dir ] [--auto-measure] [--context '{"k":v}'] [--context-file ]`: validate metric constraints and produce evidence report
+- `charter adf metrics recalibrate [--headroom ] [--reason ""|--auto-rationale] [--dry-run]`: recalibrate metric baselines/ceilings
+- `charter adf migrate [--dry-run] [--source ] [--no-backup] [--merge-strategy append|dedupe|replace]`: migrate existing agent config files into ADF modules
+- `charter serve [--name ] [--ai-dir ]`: start an MCP server (stdio) exposing ADF context as tools and resources for Claude Code and other MCP clients
+- `charter telemetry report [--period <30m|24h|7d>]`: summarize local CLI telemetry
+- `charter why`: explain adoption rationale and expected payoff
+
+Global options: `--config `, `--format text|json`, `--ci`, `--yes`.
+
+## Exit Code Contract
+
+- `0`: success/pass
+- `1`: policy violation in CI mode
+- `2`: runtime/config/usage error
+
+## CI Integration
+
+- Reusable template: `.github/workflows/governance.yml`
+- Generated in target repos by `charter setup --ci github`: `.github/workflows/charter-governance.yml`
+- The governance workflow runs `validate`, `drift`, ADF wiring integrity (`doctor --adf-only --ci`), ADF ceiling evidence (`adf evidence --auto-measure --ci`), and `audit` on every PR.
+
+## Workspace Layout
+
+```text
+packages/
+ types/ Shared contracts
+ core/ Schemas, sanitization, errors
+ adf/ ADF parser, formatter, patcher, bundler, evidence pipeline
+ git/ Trailer parsing and risk scoring
+ classify/ Heuristic classification
+ validate/ Governance validation
+ drift/ Pattern drift scanning
+ cli/ `charter` command
+ ci/ GitHub Actions integration helpers
+```
+
+## Development
+
+- `pnpm run clean`
+- `pnpm run typecheck`
+- `pnpm run build`
+- `pnpm run test`
+
+## SemVer and Stability Policy
+
+Charter uses [Semantic Versioning](https://semver.org/) for this repository and for published `@stackbilt/*` packages.
+
+Until `1.0.0`, Charter may still evolve quickly, but breaking changes should remain exceptional, deliberate, and clearly documented. The goal of `1.0.0` is simple: a connected coding agent or developer can rely on Charter's machine-facing contracts without source archaeology.
+
+The following surfaces are semver-governed:
+
+- **Package APIs** -- exported functions, classes, and types from published `@stackbilt/*` packages
+- **CLI behavior** -- command names, flags, exit codes, and machine-readable `--format json` output
+- **ADF behavior** -- parse, format, patch, bundle, sync, and evidence semantics
+- **Generated artifacts** -- thin pointer files, `.ai/manifest.adf`, `.adf.lock`, and related scaffolded outputs
+- **Governance schemas** -- evidence, audit, drift, doctor, and scorecard JSON envelopes
+
+Versioning rules:
+
+- **PATCH** -- bug fixes, docs, internal refactors, and non-breaking UX improvements
+- **MINOR** -- additive commands, flags, fields, modules, templates, and advisory checks that do not break existing consumers
+- **MAJOR** -- incompatible changes to CLI contracts, JSON schemas, ADF semantics, generated artifact conventions, or other machine-facing behavior that agents may rely on
+
+For agent-facing workflows, schema stability is treated as a core product promise, not a nice-to-have.
+
+## License
+
+Apache-2.0.
diff --git a/src/content/docs/img-forge.md b/src/content/docs/img-forge.md
index 54539db..b2e26c9 100644
--- a/src/content/docs/img-forge.md
+++ b/src/content/docs/img-forge.md
@@ -1,312 +1,312 @@
----
-title: "img-forge API"
-description: "AI image generation API with REST endpoints, MCP tools, and multi-tier quality options. OAuth 2.1 and API key authentication."
-section: "platform"
-order: 9
-color: "#f472b6"
-tag: "09"
----
-
-# img-forge API
-
-img-forge is StackBilt's AI image generation service. Submit a text prompt, get back a generated image. Supports multiple quality tiers (Stable Diffusion XL through Gemini), async job queuing, and content-addressed image storage on R2.
-
-**Gateway:** `https://imgforge.stackbilt.dev`
-**MCP Server:** `https://img-forge-mcp.blue-pine-edf6.workers.dev/mcp`
-
-## Authentication
-
-img-forge supports three auth paths, checked in order by the gateway middleware.
-
-### API Key
-
-Include your key in the `Authorization` header or `X-API-Key` header:
-
-```bash
-curl -X POST https://imgforge.stackbilt.dev/v2/generate \
- -H "Authorization: Bearer imgf_your_key_here" \
- -H "Content-Type: application/json" \
- -d '{"prompt": "A mountain landscape at sunset"}'
-```
-
-API keys use the `imgf_` prefix followed by 64 hex characters. You receive the raw key once at creation — store it securely.
-
-### OAuth 2.1 (MCP Clients)
-
-The MCP server acts as both Authorization Server and Resource Server using `@cloudflare/workers-oauth-provider`. MCP clients follow the standard OAuth 2.1 + PKCE flow:
-
-1. Discover endpoints via `/.well-known/oauth-authorization-server`
-2. Register dynamically at `/register` (RFC 7591)
-3. Redirect to `/authorize` with PKCE challenge
-4. User logs in via Better Auth and grants consent
-5. Exchange auth code for access token at `/token`
-
-**Token lifetimes:** Access token 1 hour, refresh token 30 days.
-**Scopes:** `generate`, `read`
-
-First-time users are auto-provisioned with a free-tier tenant and 100 images/month entitlement on consent approval.
-
-### Anonymous
-
-No credentials required. Rate-limited to 100 images/month per IP address.
-
-## REST API
-
-### Generate an Image
-
-```
-POST /v2/generate
-```
-
-Submit a generation request. Returns immediately with a job ID (async) or waits for completion (sync).
-
-**Request body:**
-
-| Field | Type | Required | Default | Description |
-|-------|------|----------|---------|-------------|
-| `prompt` | string | Yes | — | Text description, 1–2000 characters |
-| `negative_prompt` | string | No | — | Things to exclude (effective on `draft` tier only) |
-| `quality_tier` | string | No | `standard` | `draft`, `standard`, `premium`, `ultra`, `ultra_plus` |
-| `sync` | boolean | No | `false` | Wait for completion before responding |
-| `idempotency_key` | string | No | — | Deduplication key (24h TTL) |
-
-**Example (async):**
-
-```bash
-curl -X POST https://imgforge.stackbilt.dev/v2/generate \
- -H "Authorization: Bearer imgf_your_key" \
- -H "Content-Type: application/json" \
- -d '{
- "prompt": "Isometric pixel art of a cloud server room",
- "quality_tier": "premium"
- }'
-```
-
-**Response (`202 Accepted`):**
-
-```json
-{
- "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
- "state": "queued",
- "original_prompt": "Isometric pixel art of a cloud server room",
- "final_prompt": "...",
- "enhancement_logic": "...",
- "asset_url": null,
- "error": null,
- "created_at": "2026-03-04T12:00:00.000Z",
- "completed_at": null
-}
-```
-
-When `sync: true`, the response is `201 Created` with `state: "completed"` and `asset_url` populated.
-
-### Poll Job Status
-
-```
-GET /v2/jobs/:id
-```
-
-Check the state of a generation job. Jobs are scoped to the authenticated tenant.
-
-**Response:**
-
-```json
-{
- "job_id": "a1b2c3d4-...",
- "state": "completed",
- "original_prompt": "...",
- "final_prompt": "...",
- "enhancement_logic": "...",
- "asset_url": "/v2/assets/sha256hash",
- "error": null,
- "created_at": "2026-03-04T12:00:00.000Z",
- "completed_at": "2026-03-04T12:00:08.000Z"
-}
-```
-
-**Job states:** `queued` → `processing` → `completed` | `failed`
-
-Jobs that remain in `processing` for more than 60 seconds are automatically marked `failed` with a timeout error.
-
-### Retrieve an Image
-
-```
-GET /v2/assets/:id
-```
-
-Stream the generated image from R2. Images are content-addressed by SHA-256 hash.
-
-Returns `image/png` with `Cache-Control: public, max-age=3600`. Returns `404` if the asset does not exist.
-
-### Health Check
-
-```
-GET /v2/health
-```
-
-Returns `{ "status": "ok", "version": "0.2.0" }`.
-
-## Quality Tiers
-
-| Tier | Provider | Model | Negative Prompt | Default Size |
-|------|----------|-------|-----------------|--------------|
-| `draft` | Cloudflare AI | Stable Diffusion XL Lightning | Yes | 1024×1024 |
-| `standard` | Cloudflare AI | FLUX.2 Klein 4B | No | 1024×768 |
-| `premium` | Cloudflare AI | FLUX.2 Dev | No | 1024×768 |
-| `ultra` | Gemini | Gemini 2.5 Flash Image | No | 1024×1024 |
-| `ultra_plus` | Gemini | Gemini 3.1 Flash Image Preview | No | 1024×1024 |
-
-## MCP Tools
-
-Connect MCP-compatible agents to img-forge for programmatic image generation.
-
-**Endpoint:** `https://img-forge-mcp.blue-pine-edf6.workers.dev/mcp`
-
-### Claude Code Configuration
-
-Add to your MCP settings:
-
-```json
-{
- "mcpServers": {
- "img-forge": {
- "url": "https://img-forge-mcp.blue-pine-edf6.workers.dev/mcp"
- }
- }
-}
-```
-
-### generate_image
-
-Generate an image from a text prompt. Requires `generate` scope.
-
-| Parameter | Type | Required | Default | Description |
-|-----------|------|----------|---------|-------------|
-| `prompt` | string | Yes | — | Text description, 1–2000 characters |
-| `quality_tier` | enum | No | `standard` | `draft`, `standard`, `premium`, `ultra`, `ultra_plus` |
-| `negative_prompt` | string | No | — | Exclusions (effective on `draft` tier only) |
-
-The MCP tool always uses sync mode — it returns the completed image URL directly.
-
-### list_models
-
-List all available quality tiers with their providers, models, and default sizes. Requires `read` scope. Takes no parameters.
-
-### check_job
-
-Check the status of a generation job. Requires `read` scope.
-
-| Parameter | Type | Required | Description |
-|-----------|------|----------|-------------|
-| `job_id` | string (UUID) | Yes | The job ID to check |
-
-## Rate Limits
-
-| Auth Method | Quota | Period | Enforcement |
-|-------------|-------|--------|-------------|
-| Anonymous | 100 images | Calendar month | Per IP, via KV |
-| API key (free tier) | 100 images | Calendar month | Per tenant, via D1 entitlements |
-| OAuth / MCP (free tier) | 100 images | Calendar month | Per tenant, via D1 entitlements |
-
-When quota is exceeded, the API returns `429` with error code `QUOTA_EXCEEDED` (authenticated) or `RATE_LIMITED` (anonymous).
-
-## Tenant Management
-
-Authenticated users can manage their API keys through tenant endpoints.
-
-### Create Tenant
-
-```
-POST /v2/tenants
-```
-
-Requires a Better Auth session. Returns the raw API key **once** — it cannot be retrieved again.
-
-```json
-{
- "tenant_id": "uuid",
- "api_key": "imgf_...",
- "api_key_prefix": "imgf_abcd1234",
- "scopes": ["generate", "read"],
- "tier": "free"
-}
-```
-
-### List Tenants
-
-```
-GET /v2/tenants
-```
-
-Returns all tenants for the authenticated user. Does not include raw API keys, only prefixes.
-
-### Rotate API Key
-
-```
-POST /v2/tenants/:id/rotate
-```
-
-Invalidates the current key and returns a new one.
-
-### Check Usage
-
-```
-GET /v2/tenants/:id/usage
-```
-
-Returns active entitlements and total job count:
-
-```json
-{
- "tenant_id": "...",
- "tier": "free",
- "total_jobs": 12,
- "entitlements": [
- {
- "type": "standard",
- "quota_limit": 100,
- "quota_used": 12,
- "remaining": 88,
- "period_start": "2026-03-01T00:00:00Z",
- "period_end": "2026-03-31T23:59:59Z",
- "source": "img-forge-free"
- }
- ]
-}
-```
-
-## TypeScript Example
-
-```typescript
-const GATEWAY = "https://imgforge.stackbilt.dev";
-const API_KEY = "imgf_your_key_here";
-
-// Generate (async)
-const genRes = await fetch(`${GATEWAY}/v2/generate`, {
- method: "POST",
- headers: {
- "Authorization": `Bearer ${API_KEY}`,
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- prompt: "A neon-lit cyberpunk alleyway",
- quality_tier: "premium",
- }),
-});
-const job = await genRes.json();
-console.log("Job ID:", job.job_id);
-
-// Poll until complete
-let result = job;
-while (result.state !== "completed" && result.state !== "failed") {
- await new Promise((r) => setTimeout(r, 2000));
- const pollRes = await fetch(`${GATEWAY}/v2/jobs/${job.job_id}`, {
- headers: { "Authorization": `Bearer ${API_KEY}` },
- });
- result = await pollRes.json();
-}
-
-if (result.state === "completed") {
- console.log("Image:", `${GATEWAY}${result.asset_url}`);
-}
-```
+---
+title: "img-forge API"
+description: "AI image generation API with REST endpoints, MCP tools, and multi-tier quality options. OAuth 2.1 and API key authentication."
+section: "platform"
+order: 9
+color: "#f472b6"
+tag: "09"
+---
+
+# img-forge API
+
+img-forge is StackBilt's AI image generation service. Submit a text prompt, get back a generated image. Supports multiple quality tiers (Stable Diffusion XL through Gemini), async job queuing, and content-addressed image storage on R2.
+
+**Gateway:** `https://imgforge.stackbilt.dev`
+**MCP Server:** `https://img-forge-mcp.blue-pine-edf6.workers.dev/mcp`
+
+## Authentication
+
+img-forge supports three auth paths, checked in order by the gateway middleware.
+
+### API Key
+
+Include your key in the `Authorization` header or `X-API-Key` header:
+
+```bash
+curl -X POST https://imgforge.stackbilt.dev/v2/generate \
+ -H "Authorization: Bearer imgf_your_key_here" \
+ -H "Content-Type: application/json" \
+ -d '{"prompt": "A mountain landscape at sunset"}'
+```
+
+API keys use the `imgf_` prefix followed by 64 hex characters. You receive the raw key once at creation — store it securely.
+
+### OAuth 2.1 (MCP Clients)
+
+The MCP server acts as both Authorization Server and Resource Server using `@cloudflare/workers-oauth-provider`. MCP clients follow the standard OAuth 2.1 + PKCE flow:
+
+1. Discover endpoints via `/.well-known/oauth-authorization-server`
+2. Register dynamically at `/register` (RFC 7591)
+3. Redirect to `/authorize` with PKCE challenge
+4. User logs in via Better Auth and grants consent
+5. Exchange auth code for access token at `/token`
+
+**Token lifetimes:** Access token 1 hour, refresh token 30 days.
+**Scopes:** `generate`, `read`
+
+First-time users are auto-provisioned with a free-tier tenant and 100 images/month entitlement on consent approval.
+
+### Anonymous
+
+No credentials required. Rate-limited to 100 images/month per IP address.
+
+## REST API
+
+### Generate an Image
+
+```
+POST /v2/generate
+```
+
+Submit a generation request. Returns immediately with a job ID (async) or waits for completion (sync).
+
+**Request body:**
+
+| Field | Type | Required | Default | Description |
+|-------|------|----------|---------|-------------|
+| `prompt` | string | Yes | — | Text description, 1–2000 characters |
+| `negative_prompt` | string | No | — | Things to exclude (effective on `draft` tier only) |
+| `quality_tier` | string | No | `standard` | `draft`, `standard`, `premium`, `ultra`, `ultra_plus` |
+| `sync` | boolean | No | `false` | Wait for completion before responding |
+| `idempotency_key` | string | No | — | Deduplication key (24h TTL) |
+
+**Example (async):**
+
+```bash
+curl -X POST https://imgforge.stackbilt.dev/v2/generate \
+ -H "Authorization: Bearer imgf_your_key" \
+ -H "Content-Type: application/json" \
+ -d '{
+ "prompt": "Isometric pixel art of a cloud server room",
+ "quality_tier": "premium"
+ }'
+```
+
+**Response (`202 Accepted`):**
+
+```json
+{
+ "job_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ "state": "queued",
+ "original_prompt": "Isometric pixel art of a cloud server room",
+ "final_prompt": "...",
+ "enhancement_logic": "...",
+ "asset_url": null,
+ "error": null,
+ "created_at": "2026-03-04T12:00:00.000Z",
+ "completed_at": null
+}
+```
+
+When `sync: true`, the response is `201 Created` with `state: "completed"` and `asset_url` populated.
+
+### Poll Job Status
+
+```
+GET /v2/jobs/:id
+```
+
+Check the state of a generation job. Jobs are scoped to the authenticated tenant.
+
+**Response:**
+
+```json
+{
+ "job_id": "a1b2c3d4-...",
+ "state": "completed",
+ "original_prompt": "...",
+ "final_prompt": "...",
+ "enhancement_logic": "...",
+ "asset_url": "/v2/assets/sha256hash",
+ "error": null,
+ "created_at": "2026-03-04T12:00:00.000Z",
+ "completed_at": "2026-03-04T12:00:08.000Z"
+}
+```
+
+**Job states:** `queued` → `processing` → `completed` | `failed`
+
+Jobs that remain in `processing` for more than 60 seconds are automatically marked `failed` with a timeout error.
+
+### Retrieve an Image
+
+```
+GET /v2/assets/:id
+```
+
+Stream the generated image from R2. Images are content-addressed by SHA-256 hash.
+
+Returns `image/png` with `Cache-Control: public, max-age=3600`. Returns `404` if the asset does not exist.
+
+### Health Check
+
+```
+GET /v2/health
+```
+
+Returns `{ "status": "ok", "version": "0.2.0" }`.
+
+## Quality Tiers
+
+| Tier | Provider | Model | Negative Prompt | Default Size |
+|------|----------|-------|-----------------|--------------|
+| `draft` | Cloudflare AI | Stable Diffusion XL Lightning | Yes | 1024×1024 |
+| `standard` | Cloudflare AI | FLUX.2 Klein 4B | No | 1024×768 |
+| `premium` | Cloudflare AI | FLUX.2 Dev | No | 1024×768 |
+| `ultra` | Gemini | Gemini 2.5 Flash Image | No | 1024×1024 |
+| `ultra_plus` | Gemini | Gemini 3.1 Flash Image Preview | No | 1024×1024 |
+
+## MCP Tools
+
+Connect MCP-compatible agents to img-forge for programmatic image generation.
+
+**Endpoint:** `https://img-forge-mcp.blue-pine-edf6.workers.dev/mcp`
+
+### Claude Code Configuration
+
+Add to your MCP settings:
+
+```json
+{
+ "mcpServers": {
+ "img-forge": {
+ "url": "https://img-forge-mcp.blue-pine-edf6.workers.dev/mcp"
+ }
+ }
+}
+```
+
+### generate_image
+
+Generate an image from a text prompt. Requires `generate` scope.
+
+| Parameter | Type | Required | Default | Description |
+|-----------|------|----------|---------|-------------|
+| `prompt` | string | Yes | — | Text description, 1–2000 characters |
+| `quality_tier` | enum | No | `standard` | `draft`, `standard`, `premium`, `ultra`, `ultra_plus` |
+| `negative_prompt` | string | No | — | Exclusions (effective on `draft` tier only) |
+
+The MCP tool always uses sync mode — it returns the completed image URL directly.
+
+### list_models
+
+List all available quality tiers with their providers, models, and default sizes. Requires `read` scope. Takes no parameters.
+
+### check_job
+
+Check the status of a generation job. Requires `read` scope.
+
+| Parameter | Type | Required | Description |
+|-----------|------|----------|-------------|
+| `job_id` | string (UUID) | Yes | The job ID to check |
+
+## Rate Limits
+
+| Auth Method | Quota | Period | Enforcement |
+|-------------|-------|--------|-------------|
+| Anonymous | 100 images | Calendar month | Per IP, via KV |
+| API key (free tier) | 100 images | Calendar month | Per tenant, via D1 entitlements |
+| OAuth / MCP (free tier) | 100 images | Calendar month | Per tenant, via D1 entitlements |
+
+When quota is exceeded, the API returns `429` with error code `QUOTA_EXCEEDED` (authenticated) or `RATE_LIMITED` (anonymous).
+
+## Tenant Management
+
+Authenticated users can manage their API keys through tenant endpoints.
+
+### Create Tenant
+
+```
+POST /v2/tenants
+```
+
+Requires a Better Auth session. Returns the raw API key **once** — it cannot be retrieved again.
+
+```json
+{
+ "tenant_id": "uuid",
+ "api_key": "imgf_...",
+ "api_key_prefix": "imgf_abcd1234",
+ "scopes": ["generate", "read"],
+ "tier": "free"
+}
+```
+
+### List Tenants
+
+```
+GET /v2/tenants
+```
+
+Returns all tenants for the authenticated user. Does not include raw API keys, only prefixes.
+
+### Rotate API Key
+
+```
+POST /v2/tenants/:id/rotate
+```
+
+Invalidates the current key and returns a new one.
+
+### Check Usage
+
+```
+GET /v2/tenants/:id/usage
+```
+
+Returns active entitlements and total job count:
+
+```json
+{
+ "tenant_id": "...",
+ "tier": "free",
+ "total_jobs": 12,
+ "entitlements": [
+ {
+ "type": "standard",
+ "quota_limit": 100,
+ "quota_used": 12,
+ "remaining": 88,
+ "period_start": "2026-03-01T00:00:00Z",
+ "period_end": "2026-03-31T23:59:59Z",
+ "source": "img-forge-free"
+ }
+ ]
+}
+```
+
+## TypeScript Example
+
+```typescript
+const GATEWAY = "https://imgforge.stackbilt.dev";
+const API_KEY = "imgf_your_key_here";
+
+// Generate (async)
+const genRes = await fetch(`${GATEWAY}/v2/generate`, {
+ method: "POST",
+ headers: {
+ "Authorization": `Bearer ${API_KEY}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ prompt: "A neon-lit cyberpunk alleyway",
+ quality_tier: "premium",
+ }),
+});
+const job = await genRes.json();
+console.log("Job ID:", job.job_id);
+
+// Poll until complete
+let result = job;
+while (result.state !== "completed" && result.state !== "failed") {
+ await new Promise((r) => setTimeout(r, 2000));
+ const pollRes = await fetch(`${GATEWAY}/v2/jobs/${job.job_id}`, {
+ headers: { "Authorization": `Bearer ${API_KEY}` },
+ });
+ result = await pollRes.json();
+}
+
+if (result.state === "completed") {
+ console.log("Image:", `${GATEWAY}${result.asset_url}`);
+}
+```