Noosletter API

Add subscribers from your own systems, write and send emails, and hear about changes as they happen. Everything is scoped to one workspace and follows the same rules as the app. There's no per-person open, click or view data anywhere in the API.

StartErrorsNewslettersSubscribersTagsSegmentsEmailsWebhooksCommand lineOpenAPI document

Start

Create a key in Settings → Developers. A read key can only read; a read and write key can also change things and send. Keep keys on your server. Send the key as a bearer token:

curl https://app.noosletter.com/v1/newsletters \
  -H "Authorization: Bearer $NOOS_API_KEY"
  • Every response is JSON: { "data": … }, or { "error": { "type", "message" } }.
  • Field names are snake_case. Times are ISO 8601 in UTC.
  • Lists that can be long are paged: pass limit (up to 100) and the next_cursor you were given.
  • 120 requests a minute per key. Over that you get 429 with a Retry-After header.
  • Creating and sending take an Idempotency-Key header. Repeat it within 24 hours and you get the first response back instead of a second subscriber or a second send.

Errors

400 invalid_requestSomething in the request isn't right. The message says what.
401 unauthorizedThe key is missing, wrong or revoked.
403 forbiddenA read key tried to change something.
404 not_foundIt doesn't exist in this workspace.
409It conflicts with how things are, e.g. already_sent, already_exists, unsubscribed_by_reader.
422It can't be done as asked, e.g. invalid_email, blocked, not_ready, no_recipients.
429 rate_limitedSlow down; see Retry-After.

Newsletters

GET /v1/newsletters

List your newsletters

Any key

curl -X GET https://app.noosletter.com/v1/newsletters \
  -H "Authorization: Bearer $NOOS_API_KEY"

GET /v1/newsletters/{id}

Get a newsletter

Any key

curl -X GET https://app.noosletter.com/v1/newsletters/ID \
  -H "Authorization: Bearer $NOOS_API_KEY"

Subscribers

GET /v1/subscribers

List subscribers

Newest first.

Any key

  • limit 1 to 100, default 25
  • cursor The next_cursor from the previous page
  • email Find one address
  • newsletter_id Only people subscribed to this newsletter
  • status With newsletter_id: subscribed (default) or unsubscribed
curl -X GET https://app.noosletter.com/v1/subscribers \
  -H "Authorization: Bearer $NOOS_API_KEY"

POST /v1/subscribers

Add a subscriber

Runs your Blocklist and address verification; an address that can't receive email is refused. To subscribe them to newsletters, say how they agreed in consent.basis: it's kept with the subscription. Adding someone without newsletters creates a contact who gets nothing until they're subscribed.

Read and write key · accepts Idempotency-Key

  • email string, required
  • first_name string
  • last_name string
  • newsletter_ids array of string. Subscribe them to these newsletters. Needs consent.
  • consent object. Contains consent.basis.
  • tag_ids array of string
curl -X POST https://app.noosletter.com/v1/subscribers \
  -H "Authorization: Bearer $NOOS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"email":"ada@example.com","first_name":"Ada","newsletter_ids":["NEWSLETTER_ID"],"consent":{"basis":"Ticked the box at checkout"}}'

GET /v1/subscribers/{id}

Get a subscriber

Any key

curl -X GET https://app.noosletter.com/v1/subscribers/ID \
  -H "Authorization: Bearer $NOOS_API_KEY"

PATCH /v1/subscribers/{id}

Update a subscriber's name

Read and write key

  • first_name object
  • last_name object
curl -X PATCH https://app.noosletter.com/v1/subscribers/ID \
  -H "Authorization: Bearer $NOOS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"first_name":"Ada"}'

POST /v1/subscribers/{id}/subscriptions

Subscribe to a newsletter

Needs a consent basis. Someone who unsubscribed themselves can only come back themselves, so that's refused with unsubscribed_by_reader.

Read and write key · accepts Idempotency-Key

  • newsletter_id string, required
  • consent object, required. Contains consent.basis.
curl -X POST https://app.noosletter.com/v1/subscribers/ID/subscriptions \
  -H "Authorization: Bearer $NOOS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"newsletter_id":"NEWSLETTER_ID","consent":{"basis":"Asked at our event"}}'

DELETE /v1/subscribers/{id}/subscriptions/{newsletter_id}

Unsubscribe from a newsletter

Read and write key

curl -X DELETE https://app.noosletter.com/v1/subscribers/ID/subscriptions/NEWSLETTER_ID \
  -H "Authorization: Bearer $NOOS_API_KEY"

POST /v1/subscribers/{id}/tags

Tag a subscriber

Read and write key

  • tag_id string, required
curl -X POST https://app.noosletter.com/v1/subscribers/ID/tags \
  -H "Authorization: Bearer $NOOS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tag_id":"TAG_ID"}'

DELETE /v1/subscribers/{id}/tags/{tag_id}

Remove a tag

Read and write key

curl -X DELETE https://app.noosletter.com/v1/subscribers/ID/tags/TAG_ID \
  -H "Authorization: Bearer $NOOS_API_KEY"

Tags

GET /v1/tags

List tags

Any key

curl -X GET https://app.noosletter.com/v1/tags \
  -H "Authorization: Bearer $NOOS_API_KEY"

POST /v1/tags

Create a tag

Read and write key · accepts Idempotency-Key

  • name string, required
curl -X POST https://app.noosletter.com/v1/tags \
  -H "Authorization: Bearer $NOOS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"name":"Customers"}'

Segments

GET /v1/segments

List segments

Any key

curl -X GET https://app.noosletter.com/v1/segments \
  -H "Authorization: Bearer $NOOS_API_KEY"

GET /v1/segments/{id}

Get a segment and how many people are in it

Any key

curl -X GET https://app.noosletter.com/v1/segments/ID \
  -H "Authorization: Bearer $NOOS_API_KEY"

Emails

GET /v1/emails

List emails

Any key

  • limit 1 to 100, default 25
  • cursor The next_cursor from the previous page
  • status draft, scheduled, sending, sent, failed or cancelled
curl -X GET https://app.noosletter.com/v1/emails \
  -H "Authorization: Bearer $NOOS_API_KEY"

POST /v1/emails

Create a draft

Written in Markdown, the same as the editor. It's sent from your default verified sender.

Read and write key · accepts Idempotency-Key

  • newsletter_id string. Needed when the account has more than one newsletter
  • name string. Defaults to the subject
  • subject string
  • preview_text object
  • body_markdown string. The email, in Markdown
curl -X POST https://app.noosletter.com/v1/emails \
  -H "Authorization: Bearer $NOOS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"subject":"This week","body_markdown":"# Hello\n\nIt's been a busy week."}'

GET /v1/emails/{id}

Get an email

Any key

curl -X GET https://app.noosletter.com/v1/emails/ID \
  -H "Authorization: Bearer $NOOS_API_KEY"

PATCH /v1/emails/{id}

Edit a draft

Read and write key

  • name string
  • newsletter_id string
  • subject string
  • preview_text object
  • body_markdown string
curl -X PATCH https://app.noosletter.com/v1/emails/ID \
  -H "Authorization: Bearer $NOOS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"subject":"This week, properly"}'

POST /v1/emails/{id}/schedule

Schedule

Checks there's a subject, a body, a verified sender and your postal address first.

Read and write key · accepts Idempotency-Key

  • send_at string, required. When to send, ISO 8601 with a time zone offset
  • timezone string. IANA time zone to show it in, e.g. Europe/London. Defaults to the account's.
curl -X POST https://app.noosletter.com/v1/emails/ID/schedule \
  -H "Authorization: Bearer $NOOS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"send_at":"2026-10-02T08:00:00+01:00"}'

POST /v1/emails/{id}/cancel

Cancel a schedule

It goes back to being a draft.

Read and write key

curl -X POST https://app.noosletter.com/v1/emails/ID/cancel \
  -H "Authorization: Bearer $NOOS_API_KEY"

POST /v1/emails/{id}/send

Send now

Sends to everyone subscribed to the email's newsletter who can receive it. Pass an Idempotency-Key so a retry can never send twice; without one, a second send is refused.

Read and write key · accepts Idempotency-Key

curl -X POST https://app.noosletter.com/v1/emails/ID/send \
  -H "Authorization: Bearer $NOOS_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)"

GET /v1/emails/{id}/stats

Stats

Anonymous totals. Never who opened or clicked.

Any key

curl -X GET https://app.noosletter.com/v1/emails/ID/stats \
  -H "Authorization: Bearer $NOOS_API_KEY"

Webhooks

Add an https endpoint in Settings → Developers and choose its events. We POST a JSON body like this, signed with the endpoint's secret:

{
  "id": "delivery id",
  "type": "subscriber.created",
  "created_at": "2026-09-26T09:14:03.120Z",
  "data": {
    "subscriber": { "id": "…", "email": "ada@example.com", "first_name": "Ada", "last_name": null },
    "newsletter": { "id": "…", "name": "The Friday Letter" },
    "source": "hosted_form",
    "occurred_at": "2026-09-26T09:14:03.100Z"
  }
}

Events:

  • subscriber.created
  • subscriber.resubscribed
  • subscriber.unsubscribed
  • email.scheduled
  • email.sent

There are no open or click events, and no payload says who opened or clicked. Answer with any 2xx within 10 seconds. Anything else is retried after 1 min, 5 min, 30 min, 2 h, 6 h, 12 h, 24 h, then marked failed; you can retry it from the delivery log. Redirects aren't followed.

Check each request came from us with the Noosletter-Signature header (t= time, v1= HMAC-SHA256 of <t>.<raw body>):

import crypto from "node:crypto";

// header: the Noosletter-Signature header, e.g. "t=1760000000,v1=5f2c…"
// body: the raw request body, exactly as received
export function isFromNoosletter(secret, body, header) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false; // older than 5 minutes
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${body}`).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1 ?? ""));
}

Command line

noos wraps the API for scripts and quick jobs. It has no logic of its own.

npm install -g @noosletter/cli
export NOOS_API_KEY=noos_…

noos newsletters
noos subscribers add ada@example.com --newsletter NEWSLETTER_ID --consent "Asked at our event"
noos emails create --subject "This week" --file issue.md
noos emails send EMAIL_ID --yes
noos stats EMAIL_ID

Discover other newsletters · Manage your subscriptions · Report

Powered by Noosletter