Base URL:
http://localhost:8002 (local worker — the only supported deployment today).
Status: Alpha. Surface area is broad but not contractually stable across releases.
Last updated: 2026-09-28.Jump to a group
Authentication
OAuth 2.1 Client Credentials grant.
poindexter setup provisions the
initial CLI client.Tasks
Create, list, approve, and reject content tasks. The lifecycle is
pending → in_progress → awaiting_approval.Posts
Public post list + search, plus authenticated update / soft-delete.
Settings
Read and update
app_settings rows over the REST surface.Overview
Reference for the REST API exposed by the Poindexter worker. All routes are under/api/* — there is no URL-based versioning.
Stability warning. The API is not yet considered stable. Route shapes change between releases, especially around task metadata. Pin to a specific commit until the first tagged release.
API Principles
- RESTful: Standard HTTP methods and status codes
- JSON: All requests/responses in JSON format
- Authentication: Bearer token in Authorization header
- Pagination: Limit/offset for list endpoints
Authentication
OAuth 2.1 Client Credentials
All protected endpoints require an OAuth-issued JWT in the Authorization header:poindexter setup provisions an initial CLI OAuth client on first
run. Mint a fresh JWT for ad-hoc curl tests with:
poindexter auth migrate-*
helper. The legacy static-Bearer fallback was removed in
Glad-Labs/poindexter#249.
Development Bypass
Whendevelopment_mode is true in app_settings, the literal
token dev-token is accepted as a Bearer credential:
development_mode=false by default — the
bypass is dormant unless an operator explicitly flips the flag.
Public Endpoints (no auth required)
GET /api/health— system healthGET /api/posts— published post listGET /api/posts/{slug}— single post by slugGET /api/categories— category listGET /api/posts/search?q=...— post search
Core Endpoints
System Health
GET /api/health
services/task_executor.py was deleted 2026-05-16 in the Prefect Stage 4 cutover; dispatch is now Prefect-native and there is no task_executor component in the health payload.)
Tasks
GET /api/tasks
List tasks (requires auth). Supports ?status=pending&limit=20&offset=0.
POST /api/tasks
Create a content task.
pending → in_progress → awaiting_approval (or rejected if QA fails).
POST /api/tasks/{task_id}/approve
Approve a task for publishing. Publishes immediately unless
publish_at is set.
POST /api/tasks/{task_id}/reject
Reject a task with feedback.
Posts
GET /api/posts
List published posts. Public (no auth).
GET /api/posts/{slug}
Get a single post by slug. Public.
GET /api/posts/search?q={query}
Search posts by title/content. Public.
Settings
GET /api/settings
List all app_settings (requires auth).
PUT /api/settings/{key}
Update a setting.
Other Endpoints
Everything prefixed
/api is the REST surface. Generated images
(/images/generated/{filename}) and the server-rendered preview
(/preview/{preview_token}) sit at the server root without the /api
prefix. The posts RSS feed (/feed.xml) is served by the public site,
not the worker.
Video has no public list or stream endpoint. The video feed’s enclosures point
at each approved video in object storage, and an operator streams a
local render through the authenticated
GET /api/media-approval/{post_id}/video/preview. The old
/api/video/episodes list and stream routes were removed: they looked
files up by post id, and renders are named by task id.
The podcast feed likewise lists each approved episode with its URL in
object storage, and an episode awaiting review streams through the
authenticated GET /api/media-approval/{post_id}/podcast/preview. One
podcast stream route remains: GET /api/podcast/episodes/{task_id}.mp3
serves the file a pipeline task rendered, which is how the operator
console plays a podcast-type task. The GET /api/podcast/episodes list
was removed for the same reason as the video routes.
A preview reports a podcast or long-form video as ready once one is
rendered for the post or its source task. It plays one only after it is
approved and published to object storage: the same file the RSS feed
serves.
Newsletter signup payload
POST /api/newsletter/subscribe takes email plus optional first_name
and last_name. It stores those, a verified flag and a fresh unsubscribe
token, and mirrors the signup into the Resend segment when one is
configured. It does not record the caller’s IP address or user-agent. The
public site does not call it: site signups go to Resend, and the worker
pulls them from there.
company, interest_categories and marketing_consent were retired on
2026-09-28. Nothing read them, so the worker no longer stores them and
their columns are gone. A request that still sends any of them succeeds as
before. The fields are ignored, the OpenAPI schema marks them deprecated,
and the response carries two headers:
Webhook and preview-token security
POST /api/webhooks/alertmanager requires Authorization: Bearer <token>:
either an OAuth JWT (mint one with poindexter auth mint-grafana-token)
or the static token in app_settings.alertmanager_webhook_token. A
missing or mismatched token is rejected with 401; with neither
configured the route fails closed with 503.
Preview tokens (/api/posts/preview/{token}, /preview/{token}) are
random 32-character hex strings. The pipeline mints one per task and
stores it in the task’s metadata; older posts carry one in
posts.preview_token. The token is the only credential. It does not
expire, and it can only read, never modify state.
Error Responses
400 validation, 401 missing/bad token,
404 not found, 500 server error.
Related Documentation
- Local Development Setup — full setup walkthrough
- Troubleshooting — production issues and fixes