# Pombo API guide

The API is versioned under `/api/v1` and accepts JSON. The complete,
machine-readable OpenAPI 3.1 reference is available as
[`openapi.yaml`](/openapi.yaml) or [`openapi.json`](/openapi.json). Agent
discovery information is available at [`llms.txt`](/llms.txt).

Authenticated endpoints accept tokens in the standard header:

```text
Authorization: Bearer pombo_...
```

## CLI login (device authorization)

1. Start a login:

   ```http
   POST /api/v1/auth/device
   Content-Type: application/json

   {"client_name":"Pombo CLI"}
   ```

   The response contains `device_code`, `user_code`, `verification_uri`,
   `verification_uri_complete`, `expires_in`, and the minimum polling `interval`
   in seconds. Open `verification_uri_complete` in the user's browser.

2. Poll for the token no more often than `interval`:

   ```http
   POST /api/v1/auth/token
   Content-Type: application/json

   {"device_code":"..."}
   ```

   Until the browser flow finishes, this responds with
   `{"error":"authorization_pending"}` and HTTP 400. It can instead return
   `access_denied`, `expired_token`, or `invalid_grant`. After approval it returns:

   ```json
   {"access_token":"pombo_...","token_type":"Bearer"}
   ```

   The device code can only be exchanged once. Store the access token in the
   operating system credential store; the server only stores its SHA-256 digest.

The production API origin is `https://pombomailer.com`. For local development,
use `http://localhost:3000`.

## Initial authenticated endpoints

- `GET /api/v1/me` — current account identity
- `GET /api/v1/newsletters` — newsletters the account owns or can edit
- `DELETE /api/v1/auth/token` — revoke the bearer token used for the request

## Bookmarks

```http
GET /api/v1/newsletters/:newsletter_id/bookmarks
```

Returns the newsletter's bookmarks as HTML:

```json
{"newsletter_id":"...","bookmarks":"<p>...</p>"}
```

The authenticated account must own or be able to edit the newsletter.

## Issues

Issues are scoped to a newsletter the authenticated account owns or can edit.

### List issues

```http
GET /api/v1/newsletters/:newsletter_id/issues
```

Issues are returned newest first by `created_at`. Every record includes `id`,
`newsletter_id`, `subject`, `status`, `content`, `summary`, issue metadata,
`created_at`, and `updated_at`. This makes the latest draft the first record
whose `status` is `draft`.

### Create a draft

```http
POST /api/v1/newsletters/:newsletter_id/issues
Content-Type: application/json

{"issue":{"subject":"New issue","content":"<p>Hello</p>"}}
```

New API-created issues always have `draft` status. Status changes are not
accepted through create or update.

### Update an issue

```http
PATCH /api/v1/newsletters/:newsletter_id/issues/:id
Content-Type: application/json

{"issue":{"subject":"Updated subject","content":"<p>Updated body</p>"}}
```

Editable fields are `subject`, `content`, `summary`, `archive`, and the SEO and
social metadata fields returned by the endpoint.

## Errors

Errors have a stable machine-readable `error` and a human-readable
`error_description`:

```json
{"error":"not_found","error_description":"The requested resource was not found."}
```

Validation errors use `validation_failed` and include an `errors` object. A
resource that exists but is not accessible to the current account is returned
as `not_found` rather than revealing its existence.

## Machine-readable reference

Use the [OpenAPI YAML document](/openapi.yaml) as the authoritative endpoint,
request, response, and error schema reference. This Markdown document explains
workflows and behavior that clients and agents need when orchestrating calls.
