Skip to content

API Reference

Comprehensive documentation of all Subscribe Flow API endpoints.

Interactive Documentation

Format URL Description
Swagger UI /docs Interactive API exploration with try-it-out
ReDoc /redoc Clear reference documentation
OpenAPI /openapi.json OpenAPI 3.1 Specification

Base URL

Environment URL
Production https://api.subscribeflow.net
Local http://localhost:8000

Authentication

Subscribe Flow supports dual authentication: API key OR session cookie. Every /api/v1/* endpoint accepts either method.

API Key (Programmatic Access)

All admin endpoints (/api/v1/*) accept API key authentication:

Bash
curl -X GET "http://localhost:8000/api/v1/subscribers" \
  -H "X-API-Key: sf_live_your_api_key"

API keys are organization-scoped. Each key belongs to exactly one organization.

Scopes:

Scope Description
subscribers:read Read subscribers
subscribers:write Create/update subscribers
subscribers:delete Delete subscribers
tags:read Read tags
tags:write Create/update/delete tags
webhooks:manage Manage webhook endpoints

Dashboard users authenticate via magic link, which sets an HTTP-only sf_session cookie. Organization context is provided via the X-Organization-Id header:

Bash
1
2
3
curl -X GET "http://localhost:8000/api/v1/subscribers" \
  -H "X-Organization-Id: 550e8400-e29b-41d4-a716-446655440000" \
  -b "sf_session=eyJhbGciOi..."

Token (Preference Center)

Preference Center endpoints use JWT tokens as query parameters:

Bash
curl "http://localhost:8000/preference-center?token=eyJhbGciOi..."

Auth API

Endpoints for magic link login, session management, and organization management. These endpoints do not require API key auth.

HTTP
POST /auth/magic-link

Request Body:

JSON
1
2
3
{
  "email": "user@example.com"
}

Response (200 OK):

JSON
1
2
3
{
  "message": "If the email exists, a magic link has been sent."
}

Anti-Enumeration

Always returns 200 regardless of whether the email exists.


Verifies the token and sets an HTTP-only session cookie (sf_session, 7 days).

HTTP
GET /auth/verify?token={magic_link_token}

Response (200 OK):

JSON
{
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "user@example.com",
  "has_organizations": true,
  "organizations": [
    {
      "id": "660e8400-e29b-41d4-a716-446655440000",
      "name": "My Company",
      "slug": "my-company",
      "role": "owner"
    }
  ]
}

Errors:

Status Error Description
401 magic_link_expired Token has expired
401 magic_link_already_used Token was already used
401 invalid_token Token not found or invalid

Create Organization

Requires session auth (cookie). The calling user becomes the owner.

HTTP
POST /auth/organizations

Request Body:

JSON
1
2
3
4
{
  "name": "My Company",
  "slug": "my-company"
}

Response (200 OK):

JSON
1
2
3
4
5
{
  "id": "660e8400-e29b-41d4-a716-446655440000",
  "name": "My Company",
  "slug": "my-company"
}

List Organizations

Requires session auth. Returns all organizations the current user belongs to.

HTTP
GET /auth/organizations

Response (200 OK):

JSON
1
2
3
4
5
6
7
8
[
  {
    "id": "660e8400-e29b-41d4-a716-446655440000",
    "name": "My Company",
    "slug": "my-company",
    "role": "owner"
  }
]

Logout

Clears the session cookie.

HTTP
POST /auth/logout

Response (200 OK):

JSON
1
2
3
{
  "message": "Logged out successfully."
}

Subscribers API

Create Subscriber

Creates a new subscriber with optional tag subscriptions.

HTTP
POST /api/v1/subscribers

Auth: API Key or Session

Request Body:

JSON
1
2
3
4
5
6
7
8
{
  "email": "user@example.com",
  "tags": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"],
  "metadata": {
    "source": "website",
    "campaign": "summer-2026"
  }
}

Response (201 Created):

JSON
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "email": "user@example.com",
  "status": "active",
  "tags": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "Product Updates",
      "slug": "product-updates"
    }
  ],
  "metadata": {
    "source": "website",
    "campaign": "summer-2026"
  },
  "created_at": "2026-02-03T10:30:00Z",
  "updated_at": "2026-02-03T10:30:00Z"
}

Errors:

Status Error Description
400 validation_error Invalid email address
402 limit_exceeded Subscriber limit reached for current plan
409 conflict Email already exists

List Subscribers

Lists subscribers with pagination and filtering.

HTTP
GET /api/v1/subscribers

Auth: API Key or Session

Query Parameters:

Parameter Type Default Description
cursor string - Cursor for the next page
limit integer 50 Max. results (1-100)
status string - Filter: active, unsubscribed, bounced, complained
tag string - Filter by tag ID
sort string created_at Sort by: created_at, updated_at, email
order string desc Direction: asc, desc

Response (200 OK):

JSON
{
  "items": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "email": "user@example.com",
      "status": "active",
      "tags": [],
      "created_at": "2026-02-03T10:30:00Z",
      "updated_at": "2026-02-03T10:30:00Z"
    }
  ],
  "total": 1234,
  "next_cursor": "eyJpZCI6IjU1MGU4NDAwLi4uIn0="
}

Response Headers:

Header Description
X-Total-Count Total number of subscribers
Link Pagination links (RFC 5988)

Get Subscriber

HTTP
GET /api/v1/subscribers/{subscriber_id}

Auth: API Key or Session


Get Subscriber by Email

HTTP
GET /api/v1/subscribers/by-email/{email}

Auth: API Key or Session


Update Subscriber

HTTP
PUT /api/v1/subscribers/{subscriber_id}

Auth: API Key or Session

Request Body:

JSON
1
2
3
4
5
6
{
  "metadata": {
    "plan": "premium"
  },
  "status": "unsubscribed"
}

Response (200 OK): Updated subscriber


Delete Subscriber

Permanently deletes a subscriber.

HTTP
DELETE /api/v1/subscribers/{subscriber_id}

Auth: API Key or Session

Response: 204 No Content


Manage Subscriber Tags

Add Tags

HTTP
POST /api/v1/subscribers/{subscriber_id}/tags

Auth: API Key or Session

Request Body:

JSON
1
2
3
{
  "tag_ids": ["3fa85f64-5717-4562-b3fc-2c963f66afa6"]
}

Remove Tag

HTTP
DELETE /api/v1/subscribers/{subscriber_id}/tags/{tag_id}

Auth: API Key or Session


Get Subscriber Tags

HTTP
GET /api/v1/subscribers/{subscriber_id}/tags

Auth: API Key or Session



Tags API

Create Tag

HTTP
POST /api/v1/tags

Auth: API Key or Session

Request Body:

JSON
1
2
3
4
5
6
7
8
{
  "name": "Product Updates",
  "slug": "product-updates",
  "description": "Receive notifications about new features",
  "metadata": {
    "category": "product"
  }
}

Response (201 Created):

JSON
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "Product Updates",
  "slug": "product-updates",
  "description": "Receive notifications about new features",
  "metadata": {
    "category": "product"
  },
  "subscriber_count": 0,
  "created_at": "2026-02-03T10:30:00Z",
  "updated_at": "2026-02-03T10:30:00Z"
}

List Tags

HTTP
GET /api/v1/tags

Auth: API Key or Session

Query Parameters:

Parameter Type Default Description
cursor string - Cursor for the next page
limit integer 50 Max. results (1-100)
sort string name Sort by: name, created_at, subscriber_count
order string asc Direction: asc, desc

Get Tag

HTTP
GET /api/v1/tags/{tag_id}

Auth: API Key or Session


Get Tag by Name

HTTP
GET /api/v1/tags/by-name/{name}

Auth: API Key or Session


Update Tag

HTTP
PUT /api/v1/tags/{tag_id}

Auth: API Key or Session

Request Body:

JSON
1
2
3
4
{
  "name": "New Name",
  "description": "New description"
}

Delete Tag

HTTP
DELETE /api/v1/tags/{tag_id}

Auth: API Key or Session

Response: 204 No Content

Impact on Subscribers

Deleting a tag automatically removes all subscriber associations.


Templates API

Create Template

HTTP
POST /api/v1/templates

Auth: API Key or Session


List Templates

HTTP
GET /api/v1/templates

Auth: API Key or Session


Get Template

HTTP
GET /api/v1/templates/{template_id}

Auth: API Key or Session


Update Template

HTTP
PUT /api/v1/templates/{template_id}

Auth: API Key or Session


Delete Template

HTTP
DELETE /api/v1/templates/{template_id}

Auth: API Key or Session


Preview Template

HTTP
POST /api/v1/templates/{template_id}/preview

Auth: API Key or Session


Emails API

Send Email

HTTP
POST /api/v1/emails/send

Auth: API Key or Session

Errors:

Status Error Description
402 limit_exceeded Monthly email limit reached for current plan

Campaigns API

Create Campaign

HTTP
POST /api/v1/campaigns

Auth: API Key or Session


List Campaigns

HTTP
GET /api/v1/campaigns

Auth: API Key or Session


Get Campaign

HTTP
GET /api/v1/campaigns/{campaign_id}

Auth: API Key or Session


Update Campaign

HTTP
PUT /api/v1/campaigns/{campaign_id}

Auth: API Key or Session


Send Campaign

HTTP
POST /api/v1/campaigns/{campaign_id}/send

Auth: API Key or Session


Cancel Campaign

HTTP
POST /api/v1/campaigns/{campaign_id}/cancel

Auth: API Key or Session


Retry Campaign

HTTP
POST /api/v1/campaigns/{campaign_id}/retry

Auth: API Key or Session


Duplicate Campaign

HTTP
POST /api/v1/campaigns/{campaign_id}/duplicate

Auth: API Key or Session


Count Campaign Recipients

HTTP
POST /api/v1/campaigns/{campaign_id}/count-recipients

Auth: API Key or Session


Email Triggers API

Create Trigger

HTTP
POST /api/v1/email-triggers

Auth: API Key or Session


List Triggers

HTTP
GET /api/v1/email-triggers

Auth: API Key or Session


Get Trigger

HTTP
GET /api/v1/email-triggers/{trigger_id}

Auth: API Key or Session


Update Trigger

HTTP
PUT /api/v1/email-triggers/{trigger_id}

Auth: API Key or Session


Delete Trigger

HTTP
DELETE /api/v1/email-triggers/{trigger_id}

Auth: API Key or Session


Outgoing Webhooks API

Create Webhook Endpoint

HTTP
POST /api/v1/webhooks

Auth: API Key or Session

Request Body:

JSON
1
2
3
4
5
6
7
8
9
{
  "url": "https://your-app.com/webhooks/subscribeflow",
  "events": [
    "subscriber.created",
    "subscriber.updated",
    "tag.subscribed"
  ],
  "description": "Main production webhook"
}

Response (201 Created):

JSON
{
  "id": "8fa85f64-5717-4562-b3fc-2c963f66afa6",
  "url": "https://your-app.com/webhooks/subscribeflow",
  "events": ["subscriber.created", "subscriber.updated", "tag.subscribed"],
  "description": "Main production webhook",
  "status": "active",
  "signing_secret": "whsec_abc123...",
  "created_at": "2026-02-03T10:30:00Z",
  "updated_at": "2026-02-03T10:30:00Z"
}

Signing Secret

The signing_secret is only returned upon creation. Store it securely for signature verification.

Available Events:

Event Description
subscriber.created New subscriber created
subscriber.updated Subscriber updated
subscriber.deleted Subscriber deleted
tag.subscribed Tag subscribed
tag.unsubscribed Tag unsubscribed
subscriber.note_added Note added to subscriber
feedback.created Feedback created
email.sent Email sent
email.delivered Email delivered
email.bounced Email bounced
email.complained Email marked as spam

email.delivered, email.bounced and email.complained currently fire only for organizations on EMAIL_PROVIDER=brevo. email.sent fires for every provider but only for the direct transactional-send path, not trigger- or campaign-driven sends — see Webhooks for the full caveat and the email.* payload shape.


List Webhooks

HTTP
GET /api/v1/webhooks

Auth: API Key or Session


Get Webhook

HTTP
GET /api/v1/webhooks/{webhook_id}

Auth: API Key or Session


Update Webhook

HTTP
PUT /api/v1/webhooks/{webhook_id}

Auth: API Key or Session


Delete Webhook

HTTP
DELETE /api/v1/webhooks/{webhook_id}

Auth: API Key or Session


Test Webhook

Sends a test event to the webhook endpoint.

HTTP
POST /api/v1/webhooks/{webhook_id}/test

Auth: API Key or Session

Response (200 OK):

JSON
1
2
3
4
5
{
  "success": true,
  "status_code": 200,
  "response_time_ms": 145
}

Regenerate Webhook Secret

HTTP
POST /api/v1/webhooks/{webhook_id}/regenerate-secret

Auth: API Key or Session


List Webhook Deliveries

HTTP
GET /api/v1/webhooks/{webhook_id}/deliveries

Auth: API Key or Session


Retry Webhook Delivery

HTTP
POST /api/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/retry

Auth: API Key or Session


Get Webhook Stats

HTTP
GET /api/v1/webhooks/{webhook_id}/stats

Auth: API Key or Session


Get Global Webhook Stats

HTTP
GET /api/v1/webhooks/stats/global

Auth: API Key or Session


List Webhook Event Types

HTTP
GET /api/v1/webhooks/event-types

Auth: API Key or Session


Subscriber Notes API

Create Note

HTTP
POST /api/v1/subscribers/{subscriber_id}/notes

Auth: API Key or Session


List Notes

HTTP
GET /api/v1/subscribers/{subscriber_id}/notes

Auth: API Key or Session


Get Note

HTTP
GET /api/v1/subscribers/{subscriber_id}/notes/{note_id}

Auth: API Key or Session


Update Note

HTTP
PUT /api/v1/subscribers/{subscriber_id}/notes/{note_id}

Auth: API Key or Session


Delete Note

HTTP
DELETE /api/v1/subscribers/{subscriber_id}/notes/{note_id}

Auth: API Key or Session


API Keys API

List Available Scopes

HTTP
GET /api/v1/api-keys/scopes

Auth: API Key or Session


Create API Key

HTTP
POST /api/v1/api-keys

Auth: API Key or Session

Request Body:

JSON
1
2
3
4
{
  "name": "Production Backend",
  "scopes": ["subscribers:read", "subscribers:write", "tags:read"]
}

Response (201 Created):

JSON
1
2
3
4
5
6
7
{
  "id": "9fa85f64-5717-4562-b3fc-2c963f66afa6",
  "name": "Production Backend",
  "key": "sf_live_abc123xyz...",
  "scopes": ["subscribers:read", "subscribers:write", "tags:read"],
  "created_at": "2026-02-03T10:30:00Z"
}

API Key Storage

The full API key is only displayed once upon creation. Store it securely -- it cannot be recovered.


List API Keys

HTTP
GET /api/v1/api-keys

Auth: API Key or Session


Get API Key

HTTP
GET /api/v1/api-keys/{api_key_id}

Auth: API Key or Session


Update API Key

HTTP
PATCH /api/v1/api-keys/{api_key_id}

Auth: API Key or Session


Rotate API Key

HTTP
POST /api/v1/api-keys/{api_key_id}/rotate

Auth: API Key or Session


Delete API Key

HTTP
DELETE /api/v1/api-keys/{api_key_id}

Auth: API Key or Session


Preference Center API

Public endpoints for subscriber self-service. These endpoints use token-based authentication (JWT in query parameter).

Get Preferences

HTTP
GET /preference-center?token={jwt_token}

Response:

JSON
{
  "email": "user@example.com",
  "subscribed_tags": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "name": "Product Updates",
      "slug": "product-updates",
      "description": "Receive notifications about new features",
      "subscribed": true,
      "subscribed_at": "2026-01-15T10:30:00Z"
    }
  ],
  "available_tags": [
    {
      "id": "7ca85f64-5717-4562-b3fc-2c963f66afa9",
      "name": "Weekly Digest",
      "slug": "weekly-digest",
      "description": "Weekly summary",
      "subscribed": false
    }
  ]
}

Subscribe to Tag

HTTP
PUT /preference-center/tags/{tag_id}?token={jwt_token}

Unsubscribe from Tag

HTTP
DELETE /preference-center/tags/{tag_id}?token={jwt_token}

Request Data Export (GDPR)

HTTP
GET /preference-center/data-export?token={jwt_token}

Request Account Deletion (GDPR)

HTTP
DELETE /preference-center/account?token={jwt_token}

Billing API

Endpoints for subscription management and Stripe integration.

Get Current Subscription

HTTP
GET /api/v1/billing/subscription

Auth: API Key or Session (Owner only)

Response (200 OK):

JSON
1
2
3
4
5
6
7
8
9
{
  "tier": "starter",
  "status": "active",
  "current_period_end": "2026-03-03T00:00:00Z",
  "max_subscribers": 5000,
  "max_emails_per_month": 10000,
  "emails_sent_this_month": 1234,
  "subscriber_count": 450
}

Create Checkout Session

Creates a Stripe Checkout session for upgrading the subscription.

HTTP
POST /api/v1/billing/create-checkout-session

Auth: API Key or Session (Owner only)

Request Body:

JSON
1
2
3
{
  "price_id": "price_xxx"
}

Response (200 OK):

JSON
1
2
3
{
  "checkout_url": "https://checkout.stripe.com/..."
}

Customer Portal

Opens the Stripe Customer Portal for managing payment methods and invoices.

HTTP
POST /api/v1/billing/customer-portal

Auth: API Key or Session (Owner only)

Response (200 OK):

JSON
1
2
3
{
  "portal_url": "https://billing.stripe.com/..."
}

Sync Subscription

Manually syncs the subscription status from Stripe.

HTTP
POST /api/v1/billing/sync-subscription

Auth: API Key or Session (Owner only)


Email Settings API

Endpoints for managing organization email sending configuration.

Get Email Settings

HTTP
GET /api/v1/settings/email

Auth: API Key or Session

Response (200 OK):

JSON
1
2
3
4
5
6
7
8
9
{
  "from_name": "My Company",
  "from_address": "noreply@mycompany.com",
  "effective_from": "My Company <noreply@mycompany.com>",
  "is_custom_domain": true,
  "custom_domain": "mycompany.com",
  "custom_domain_status": "verified",
  "can_add_custom_domain": false
}

Update Email Settings

Update from_name (all tiers) or from_address (requires verified custom domain).

HTTP
PUT /api/v1/settings/email

Auth: API Key or Session

Request Body:

JSON
1
2
3
4
{
  "from_name": "My Company",
  "from_address": "noreply@mycompany.com"
}

Add Custom Domain

Start domain verification via Resend API. Professional tier only.

HTTP
POST /api/v1/settings/email/domain

Auth: API Key or Session

Request Body:

JSON
1
2
3
{
  "domain": "mycompany.com"
}

Response (201 Created):

JSON
{
  "domain": "mycompany.com",
  "status": "pending",
  "dns_records": [
    {
      "type": "TXT",
      "name": "_resend.mycompany.com",
      "value": "v=spf1 ..."
    }
  ]
}

Check Domain Verification

HTTP
GET /api/v1/settings/email/domain

Auth: API Key or Session


Remove Custom Domain

HTTP
DELETE /api/v1/settings/email/domain

Auth: API Key or Session

Response: 204 No Content


Platform Admin API

Endpoints for platform-level administration. Only accessible by organizations with is_platform_admin = true (currently only Talent Factory GmbH).

All endpoints require the RequirePlatformAdmin dependency, which verifies the requesting organization has platform admin privileges.

List Organizations

HTTP
GET /api/v1/admin/organizations

Auth: API Key or Session (Platform Admin only)


Get Organization

HTTP
GET /api/v1/admin/organizations/{organization_id}

Auth: API Key or Session (Platform Admin only)


Update Organization

HTTP
PATCH /api/v1/admin/organizations/{organization_id}

Auth: API Key or Session (Platform Admin only)


Delete Organization

HTTP
DELETE /api/v1/admin/organizations/{organization_id}

Auth: API Key or Session (Platform Admin only)


List Users

HTTP
GET /api/v1/admin/users

Auth: API Key or Session (Platform Admin only)


Get User

HTTP
GET /api/v1/admin/users/{user_id}

Auth: API Key or Session (Platform Admin only)


List Members of Organization

HTTP
GET /api/v1/admin/organizations/{organization_id}/members

Auth: API Key or Session (Platform Admin only)


Add Member to Organization

HTTP
POST /api/v1/admin/organizations/{organization_id}/members

Auth: API Key or Session (Platform Admin only)


Remove Member from Organization

HTTP
DELETE /api/v1/admin/organizations/{organization_id}/members/{user_id}

Auth: API Key or Session (Platform Admin only)


Incoming Webhooks

Resend Webhook

Receives webhook events from Resend (contact and email events).

HTTP
POST /webhooks/resend

Auth: HMAC SHA-256 signature (X-Resend-Signature header)

No API key required. Signature-verified only.


Stripe Webhook

Receives webhook events from Stripe (checkout, subscription, payment events).

HTTP
POST /webhooks/stripe

Auth: Stripe webhook signature

No API key required. Signature-verified only.


Health Endpoints

Basic Health Check

HTTP
GET /health

Response (200 OK):

JSON
1
2
3
{
  "status": "healthy"
}

Readiness Check

Checks database connection and Redis.

HTTP
GET /ready

Response (200 OK):

JSON
1
2
3
4
5
6
7
{
  "status": "ready",
  "checks": {
    "database": "ok",
    "redis": "ok"
  }
}

Error Responses

All errors follow the RFC 7807 Problem Details format:

JSON
1
2
3
4
5
6
7
{
  "type": "https://subscribeflow.net/errors/not_found",
  "title": "Resource Not Found",
  "status": 404,
  "detail": "Subscriber with ID '550e8400...' not found",
  "instance": "/api/v1/subscribers/550e8400..."
}

See Error Codes Reference for the complete list.


SDKs

Python

Python
1
2
3
4
5
6
7
from subscribeflow import SubscribeFlowClient

async with SubscribeFlowClient(api_key="sf_live_xxx") as client:
    subscriber = await client.subscribers.create(
        email="user@example.com",
        tags=["product-updates"],
    )

TypeScript

TypeScript
import { SubscribeFlowClient } from '@subscribeflow/sdk';

const client = new SubscribeFlowClient({
  apiKey: 'sf_live_xxx',
});

const subscriber = await client.subscribers.create({
  email: 'user@example.com',
  tags: ['product-updates'],
});

See SDK Setup Guide for details.


Further Reading