# beehiiv

Lightweight, dependency-free, in-memory beehiiv API v2 fake for testing code that uses the language-agnostic beehiiv v2 REST API.

Default port: `4835`

## Quick start

Start the server:

```js
import { BeehiivServer } from "./services/beehiiv/src/server.js";

const server = new BeehiivServer(4835);
await server.start();
// ... run your app/tests ...
await server.stop();
```

Point a client at it (Bearer auth):

```js
await fetch("http://127.0.0.1:4835/v2/publications/pub_parlel/subscriptions", {
  method: "POST",
  headers: { Authorization: "Bearer parlel-beehiiv-key", "Content-Type": "application/json" },
  body: JSON.stringify({ email: "subscriber@parlel.dev" }),
});
// => { data: { id, email, status: "active", ... } }
```

Created posts are captured and inspectable via `/__parlel/*`.

## Access via MCP / preview URL

When run under the parlel pool, this service is reachable through the MCP gateway
and a preview URL at `http://127.0.0.1:4835`. Use `BEEHIIV_BASE_URL` to point
clients/agents at it. Captured posts live at `GET /__parlel/messages`.

## Implemented operations

All `/v2/*` routes require Bearer auth. Responses use the beehiiv envelopes `{ data: {...} }` and `{ data: [], limit, page, total_results, total_pages }`. State is in-memory and ephemeral.

- `GET /v2/publications` — list publications (a seeded `pub_parlel` exists).
- `GET /v2/publications/:pubId` — retrieve a publication.
- `GET /v2/publications/:pubId/subscriptions` — list subscriptions.
- `POST /v2/publications/:pubId/subscriptions` — create or upsert a subscription (`email` required).
- `GET /v2/publications/:pubId/subscriptions/:subscriptionId` — get a subscription (by id or email).
- `PATCH /v2/publications/:pubId/subscriptions/:subscriptionId` — update a subscription.
- `DELETE /v2/publications/:pubId/subscriptions/:subscriptionId` — delete a subscription (`204`).
- `GET /v2/publications/:pubId/posts` — list posts.
- `POST /v2/publications/:pubId/posts` — create a post (`title` required); captured.

### Service & inspection operations (parlel extensions)

- `GET /` — service metadata.
- `GET /health` — health check (`{ status: "ok" }`).
- `POST /__parlel/reset` — reset all in-memory state.
- `GET /__parlel/messages` — list captured posts (`{ messages, count }`).
- `GET /__parlel/messages/:id` — fetch a single captured post.
- `DELETE /__parlel/messages` — clear only the captured mailbox.

## Surface coverage

This emulator faithfully replicates the API surface most application code and agents exercise. Anything below the supported lines is either an intentional design choice for a fast, zero-cost local emulator (✓ By design) or a candidate for a future release (⟳ Roadmap) — never a silent inaccuracy.

Legend: ✅ fully supported · ◐ accepted (stored, not strictly enforced) · ✓ by design · ⟳ on the roadmap.

| Feature | Status |
| --- | --- |
| Publications (list/get) | ✅ Supported |
| Subscriptions CRUD (create/upsert/list/get/update/delete) | ✅ Supported |
| Posts (create/list) | ✅ Supported |
| Captured post inspection | ✅ Supported (parlel extension) |
| Actual newsletter delivery / SMTP | ✓ By design — Captured in-memory for inspection — no real messages sent |
| Segments / automations / referral program / webhooks | ⟳ Roadmap |
| Real pagination cursors | ◐ Single page; envelope fields returned but not truly paginated |
| Real API-key validity / scope enforcement | ✓ By design — Any non-empty credential is accepted — no real secrets needed |
| Rate limiting (`429`) | ✓ By design — Never throttles — local tests run at full speed, zero cost |

## Error shapes

Errors use the beehiiv envelope `{ "errors": [{ "status", "message" }] }`.

| Status | When |
| --- | --- |
| `401` | missing/invalid Bearer auth |
| `400` | invalid/missing `email` or `title`, malformed body |
| `404` | unknown publication / subscription / endpoint |

## Manifest

See `services/beehiiv/manifest.json`:

- name: `beehiiv`, image: `parlel/beehiiv:1.0`
- port: `4835`, protocol: `http`, healthcheck: `/health`, startup ≈ 100ms
- env: `BEEHIIV_API_KEY`, `BEEHIIV_PUBLICATION_ID`, `BEEHIIV_BASE_URL`
