# FEATURE REGISTRY — what exists right now

The single table that answers "what does the system do today, where does it live, and does the
GUI have its API twin?". It is **parsed by** `tools/check_rules.py --parity`, so keep the format:

- One row per user-facing feature (not per route).
- **GUI route(s)**: `METHOD /path` items, comma-separated. Use `{id}` for path variables. `—` if none.
- **API endpoint(s)**: `METHOD /api/v1/...` items, or `n/a: <reason>` when a GUI feature legitimately
  has no API twin. A GUI row with an empty API cell fails the check.
- **Service / facade**: the function both adapters call (see `ARCHITECTURE.md` §7 layering).
- **Doc**: section in `DOC/API.md`.
- **Status**: `ok` · `api-only` (API has it, GUI does not) · `drift` (both exist but behave differently) · `gap`.

Rules of the road:
1. Every change that adds or modifies a route updates this table in the same commit.
2. `api-only` is allowed (the API may be a superset of the GUI). `gap` and `drift` are debts and
   must reference the change ID that will close them.
3. Parity means **same capability, same validation, same defaults, same error semantics**, not just
   "a URL exists". The script verifies existence; the reviewer verifies semantics (see `API-PARITY.md`).

| Feature | GUI route(s) | API endpoint(s) | Service / facade | Doc | Status | Notes |
|---------|--------------|-----------------|------------------|-----|--------|-------|
| Health check | — | GET /api/v1/health | — | §6 | api-only | Operational probe; no GUI needed |
| Sign in / connect account (TikTok Login) | GET /, GET /login, GET /auth/start, GET /auth/callback | n/a: interactive TikTok consent screen cannot be driven by an API key; API clients use accounts connected through the GUI | `auth.get_auth_url`, `auth.save_account_from_login` | §2, §3 | ok | |
| Sign out | POST /logout | n/a: browser-session concept only; the API is stateless (account chosen per request) | — | — | ok | |
| Dashboard: account stats + recent videos | GET /dashboard | GET /api/v1/accounts/{id}, GET /api/v1/videos | `tiktok_api.get_user_info`, `tiktok_api.list_videos` | §7.2, §8.1 | ok | Dashboard also counts pending scheduled posts |
| List connected accounts | GET /accounts | GET /api/v1/accounts | — | §7.1 | ok | |
| Connect another account | GET /accounts/add | n/a: starts the OAuth flow above | `auth.get_auth_url` | §2 | ok | |
| Switch active account | POST /accounts/{id}/activate | n/a: session-only; API selects the account per request via account_id or X-Account-Id | — | §5 | ok | |
| Refresh account stats | POST /accounts/{id}/refresh | POST /api/v1/accounts/{id}/refresh | `auth.ensure_fresh_token`, `tiktok_api.get_user_info` | §7.3 | ok | |
| Disconnect an account | POST /accounts/{id}/delete | DELETE /api/v1/accounts/{id} | — | §7.4 | ok | Cascade-deletes its scheduled posts |
| List published videos | GET /posts | GET /api/v1/videos | `tiktok_api.list_videos` | §8.1 | ok | |
| Compose / publish a video (draft to the TikTok inbox by default, or direct) | GET /posts/new, POST /posts/new | POST /api/v1/posts, POST /api/v1/posts/media | `services.posts.publish_now` | §9.1, §9.2 | ok | One validation for both adapters (ADR-002); file uploads via `services.uploads.store_upload` |
| Publish processing status | — | GET /api/v1/posts/{publish_id}/status | `tiktok_api.fetch_publish_status` | §10.1 | api-only | The GUI shows the verdict in the flash message after publishing |
| Schedule a post | POST /posts/new (with scheduled_at) | POST /api/v1/scheduled, POST /api/v1/scheduled/media | `services.scheduled.create_scheduled_post` | §11.2, §11.3 | ok | App-side scheduling (ADR-005); the `tiktok-scheduler` container publishes when due |
| List scheduled posts | GET /posts/scheduled | GET /api/v1/scheduled | — | §11.1 | ok | All statuses, oldest first |
| Reschedule a scheduled post | POST /scheduled/{id}/reschedule | PATCH /api/v1/scheduled/{id} | `services.scheduled.reschedule_post` | §11.4 | ok | Re-queues as pending and clears the error |
| Retry a failed scheduled post | POST /scheduled/{id}/retry | POST /api/v1/scheduled/{id}/retry | `services.scheduled.retry_post` | §11.5 | ok | |
| Delete a scheduled post | POST /scheduled/{id}/delete | DELETE /api/v1/scheduled/{id} | `services.scheduled.delete_scheduled_post` | §11.6 | ok | Deletes the stored media file too |
| Insights: account totals + top videos | GET /insights | GET /api/v1/insights/account, GET /api/v1/insights/videos | `tiktok_api.get_user_info`, `tiktok_api.list_videos` | §12.1, §12.2 | ok | The page renders what `insights/account` returns; `insights/videos` exposes the ranking |
| Comments (list / reply / delete) | — | — | — | — | gap | TikTok's Comment API requires an audited app and separate scopes (`comment.list`, …); not implemented — a product decision to revisit after the app passes TikTok's audit |

## Debts recorded here

| Status | Feature | Closing change |
|--------|---------|----------------|
| gap | Comments (list / reply / delete) | *(unassigned)* — blocked on TikTok app audit; revisit then |
