---
title: "Subscribe an email for project updates (capture-only)"
method: POST
path: "/v1/newsletter"
tags: ["intake"]
---

# Subscribe an email for project updates (capture-only)

`POST /v1/newsletter`

## Request

- Base URL: `https://cotal.ai`
- URL: `https://cotal.ai/v1/newsletter`
- Auth: none declared

Open intake, idempotent per email: a duplicate returns ok with a null id. Supports `X-Sandbox`; `Idempotency-Key` is accepted and ignored because the email already is the key.

## Headers

- `Idempotency-Key` string
- `X-Sandbox` 'true' | 'false'

## Request body

- NewsletterInput
  - `email` string, email, required: Required.
  - `source` string: Page path / placement the form was submitted from.

## Response `200`

Stored. Returns the new record id (null on duplicate, honeypot, or sandbox).

- SubmitOk
  - `ok` true, required
  - `id` string, uuid, nullable: Record id; null when nothing was stored (duplicate, honeypot, or sandbox).
  - `replayed` boolean: True when a known Idempotency-Key returned the original record.
  - `sandbox` boolean: Present and true on X-Sandbox dry runs.

## Other responses

- `400`: Malformed body or validation error (`code`: bad_request | validation_error).
- `413`: Body over the size cap (`code`: payload_too_large).
- `429`: Rate limited (`code`: rate_limited). Retry after `Retry-After` seconds.
- `502`: Transient storage failure (`code`: storage_error). Safe to retry with the same Idempotency-Key.
- `default`: Any other failure. The body is always the Error object, with a stable `code` and a `hint`.

---

[API](https://skmtc.dev/cotal/apis/cotal-ai-public-api.md) · [All operations](https://skmtc.dev/cotal/apis/cotal-ai-public-api/llms.txt) · [OpenAPI document](https://skmtc.dev/cotal/apis/cotal-ai-public-api/revisions/34ab992f2172?raw)
