> ## Documentation Index
> Fetch the complete documentation index at: https://gladlabs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# API Reference

> REST endpoints exposed by the Poindexter worker — base URL, auth, and per-endpoint contracts.

<Info>
  **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-05-23.
</Info>

## Jump to a group

<CardGroup cols={2}>
  <Card title="Authentication" icon="key" href="#authentication">
    OAuth 2.1 Client Credentials grant. `poindexter setup` provisions the
    initial CLI client.
  </Card>

  <Card title="Tasks" icon="list-check" href="#tasks">
    Create, list, approve, and reject content tasks. The lifecycle is
    `pending → in_progress → awaiting_approval`.
  </Card>

  <Card title="Posts" icon="newspaper" href="#posts">
    Public post list + search, plus authenticated update / soft-delete.
  </Card>

  <Card title="Settings" icon="sliders" href="#settings">
    Read and update `app_settings` rows over the REST surface.
  </Card>
</CardGroup>

## 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:

```bash theme={null}
curl -H "Authorization: Bearer <jwt>" \
     http://localhost:8002/api/tasks
```

`poindexter setup` provisions an initial CLI OAuth client on first
run. Mint a fresh JWT for ad-hoc curl tests with:

```bash theme={null}
poindexter auth mint-token \
  --client-id <pdx_xxx> --client-secret <secret>
```

Or POST against the token endpoint directly:

```bash theme={null}
curl -X POST http://localhost:8002/token \
  -d grant_type=client_credentials \
  -d client_id=<pdx_xxx> \
  -d client_secret=<secret>
```

To register additional clients (brain daemon, MCP servers, scripts,
OpenClaw, Grafana), run the matching `poindexter auth migrate-*`
helper. The legacy static-Bearer fallback was removed in
Glad-Labs/poindexter#249.

### Development Bypass

When `development_mode` is `true` in `app_settings`, the literal
token `dev-token` is accepted as a Bearer credential:

```bash theme={null}
curl -H "Authorization: Bearer dev-token" \
     http://localhost:8002/api/tasks
```

**Never enable development\_mode in production.** As of the
2026-04-25 rotation, `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 health
* `GET /api/posts` — published post list
* `GET /api/posts/{slug}` — single post by slug
* `GET /api/categories` — category list
* `GET /api/posts/search?q=...` — post search

***

## Core Endpoints

### System Health

#### GET `/api/health`

```bash theme={null}
curl http://localhost:8002/api/health
```

**Response (200 OK):**

```json theme={null}
{
  "status": "healthy",
  "service": "cofounder-agent",
  "version": "0.5.0",
  "timestamp": "2026-05-23T10:30:00Z",
  "components": {
    "database": "healthy",
    "connection_pool": { "size": 5, "idle": 5 }
  }
}
```

(`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`.

```bash theme={null}
curl -H "Authorization: Bearer $TOKEN" \
     http://localhost:8002/api/tasks?status=awaiting_approval
```

#### POST `/api/tasks`

Create a content task.

```bash theme={null}
curl -X POST http://localhost:8002/api/tasks \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"topic": "Why Docker changed everything", "category": "technology"}'
```

The task enters the pipeline automatically: `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.

```bash theme={null}
# Publish now
curl -X POST http://localhost:8002/api/tasks/481/approve \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"approved": true}'

# Schedule for later
curl -X POST http://localhost:8002/api/tasks/481/approve \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"approved": true, "publish_at": "2026-04-18T13:00:00Z"}'
```

#### POST `/api/tasks/{task_id}/reject`

Reject a task with feedback.

```bash theme={null}
curl -X POST http://localhost:8002/api/tasks/481/reject \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"feedback": "Off-topic for the brand", "reason": "off-topic"}'
```

***

### 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.

```bash theme={null}
curl -X PUT http://localhost:8002/api/settings/auto_publish_threshold \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"value": "80"}'
```

***

### Other Endpoints

| Group           | Method + Path                             | Auth   | Purpose                                                      |
| --------------- | ----------------------------------------- | ------ | ------------------------------------------------------------ |
| **Categories**  | `GET /api/categories`                     | public | List all content categories with post counts                 |
| Categories      | `GET /api/categories/{slug}`              | public | Single category metadata + posts                             |
| **Posts**       | `GET /api/posts`                          | public | Paginated post list (status, category, limit, offset query)  |
| Posts           | `GET /api/posts/{slug}`                   | public | Single published post by slug                                |
| Posts           | `GET /api/posts/search?q=...`             | public | Full-text + pgvector similarity search                       |
| Posts           | `GET /api/posts/preview/{preview_token}`  | token  | Preview a pre-publish post using a signed one-shot token     |
| Posts           | `PATCH /api/posts/{post_id}`              | bearer | Update post metadata (title, tags, SEO fields)               |
| Posts           | `DELETE /api/posts/{post_id}`             | bearer | Soft delete (archive) a post                                 |
| **Tags**        | `GET /api/tags`                           | public | List all tags with post counts                               |
| **Memory**      | `GET /api/memory/search?q=...`            | bearer | Semantic search via pgvector (top-k cosine similarity)       |
| Memory          | `GET /api/memory/stats`                   | bearer | Embedding counts by source\_table / writer                   |
| **Pipeline**    | `GET /api/pipeline/events`                | bearer | Live event stream (recent task transitions, with pagination) |
| Pipeline        | `GET /api/pipeline/events/task/{task_id}` | bearer | Event history for a single task                              |
| **Analytics**   | `GET /api/analytics/views`                | bearer | Page view rollups (day, post\_id, geo if configured)         |
| **Export**      | `POST /api/export/rebuild`                | bearer | Trigger static JSON + RSS rebuild to R2 / configured storage |
| **Media**       | `GET /images/generated/{filename}`        | public | Serve a generated image-gen / Pexels image                   |
| Media           | `GET /episodes`                           | public | List all podcast episodes                                    |
| Media           | `GET /episodes/{post_id}.mp3`             | public | Stream a podcast audio episode                               |
| Media           | `GET /episodes/{post_id}.mp4`             | public | Stream a podcast video episode                               |
| Media           | `POST /generate/{post_id}`                | bearer | Regenerate audio/video for a specific post                   |
| **Subscribers** | `GET /subscribers/count`                  | public | Total opted-in newsletter subscribers                        |
| Subscribers     | `POST /subscribe`                         | public | Double opt-in newsletter signup (captcha recommended)        |
| Subscribers     | `POST /unsubscribe`                       | public | One-click unsubscribe by token                               |
| **Feed**        | `GET /feed.xml`                           | public | RSS 2.0 feed of published posts                              |
| **Webhooks**    | `POST /alertmanager`                      | signed | Prometheus Alertmanager webhook receiver (routes to Discord) |
| **Frontend**    | `POST /revalidate-cache`                  | bearer | Trigger Next.js ISR cache revalidation after publish         |
| Frontend        | `GET /preview/{preview_token}`            | token  | Server-rendered preview HTML for vision-gate QA              |

Everything prefixed `/api` is the REST surface. Media routes
(`/episodes/*`, `/images/generated/*`) and the RSS feed sit at the
server root without the `/api` prefix to match classic web
expectations.

### Webhook security

`POST /alertmanager` verifies the Alertmanager webhook signature
(HMAC-SHA256, shared secret in `app_settings.alertmanager_webhook_secret`).
Invalid signatures are rejected with 401.

Preview tokens (`/api/posts/preview/{token}`, `/preview/{token}`) are
single-purpose signed tokens issued by the worker at post creation.
They expire after 24 hours and can only be used to read — never to
modify state.

***

### Error Responses

```json theme={null}
{
  "error_code": "VALIDATION_ERROR",
  "message": "Request validation failed",
  "errors": { "feedback": "Field required" },
  "request_id": "3c158a96-..."
}
```

Common status codes: `400` validation, `401` missing/bad token,
`404` not found, `500` server error.

***

## Related Documentation

* **[Local Development Setup](../operations/local-development-setup)** — full setup walkthrough
* **[Troubleshooting](../operations/troubleshooting)** — production issues and fixes
