Skip to content

Authentication

Subscribe Flow supports two authentication methods: API keys for programmatic access and magic links for dashboard login. All /api/v1/* endpoints accept either method (dual auth).

API Keys (Programmatic Access)

API keys are the recommended method for server-to-server communication and SDK usage. Each API key is scoped to exactly one organization.

Key Types

Type Prefix Usage
Live sf_live_ Production
Test sf_test_ Development & Testing

Creating an API Key

  1. Navigate to Settings > API Keys
  2. Click Create API Key
  3. Assign a name and select permissions
  4. Copy the key (it is only shown once!)
Bash
1
2
3
4
5
6
curl -X POST /api/v1/api-keys \
  -H "X-API-Key: sf_live_existing_key" \
  -d '{
    "name": "Production Backend",
    "scopes": ["subscribers:read", "subscribers:write"]
  }'
Bash
make api-key

Usage

Bash
curl -H "X-API-Key: sf_live_xxxxx" \
  https://api.subscribeflow.net/api/v1/subscribers

The organization context is resolved automatically from the API key. No additional headers needed.

Scopes

Scope Description
subscribers:read Read subscribers
subscribers:write Create/modify subscribers
subscribers:delete Delete subscribers
tags:read Read tags
tags:write Create/modify tags
webhooks:manage Manage webhooks
* All permissions (admin)

Security Best Practices

  • Never use API keys in frontend/client-side code
  • Store keys in environment variables
  • Rotate keys regularly (every 90 days recommended)
  • Grant minimal permissions (principle of least privilege)

Magic links provide passwordless authentication for the admin dashboard. No passwords are stored or managed.

How It Works

sequenceDiagram
    participant U as User
    participant D as Dashboard
    participant API as Subscribe Flow API
    participant E as Email (Resend)

    U->>D: Enter email on login page
    D->>API: POST /auth/magic-link {email}
    API->>E: Send magic link email
    API->>D: 200 OK "Check your email"
    E->>U: Email with login link

    U->>U: Click link in email
    U->>API: GET /auth/verify?token=xxx
    API->>API: Validate token (not expired, not used)
    API->>API: Create/find user, set session cookie
    API-->>U: Set-Cookie: sf_session (HttpOnly, 7 days)
    API->>D: Redirect to dashboard

Step-by-Step Flow

  1. Request magic link: User enters their email at the login page

    Bash
    POST /auth/magic-link
    {"email": "user@example.com", "locale": "de"}
    
    Always returns 200 OK regardless of whether the email exists (prevents email enumeration).

    Der optionale locale-Parameter (de oder en, Standard: en) steuert die Sprache des Magic-Link-E-Mail-Inhalts (Betreff, Text, Button).

  2. Receive email: User receives an email containing a unique, time-limited login link.

  3. Click link: The link calls the verify endpoint with the token:

    Text Only
    GET /auth/verify?token=abc123xyz...
    

  4. Session established: On success, the API:

    • Verifies the token is valid, not expired, not already used
    • Creates the user account if it does not exist yet
    • Sets an HTTP-only session cookie (sf_session) valid for 7 days
    • Returns the user's organizations
  5. Use the dashboard: Subsequent requests include the session cookie automatically. The dashboard sends an X-Organization-Id header to specify which organization the user is working with.

Session Properties

Property Value
Cookie name sf_session
Format JWT (signed with app secret)
HttpOnly Yes
SameSite Lax
Secure Yes (production only)
Lifetime 7 days

Organization Context

When using session auth, requests must include the organization context:

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

The API verifies that the user is a member of the specified organization before processing the request.

Logout

Bash
POST /auth/logout

Clears the session cookie.

Dual Auth

All /api/v1/* endpoints accept either API key or session cookie:

Method Header/Cookie Org Resolution
API Key X-API-Key: sf_live_xxx From API key record
Session Cookie sf_session + Header X-Organization-Id From header, verified via membership

This means the same endpoints work for both SDK integrations and the admin dashboard.

Technisches Detail: Wenn eine Session Cookie vorhanden ist, gibt die get_api_key() Dependency None zurück statt 401 Unauthorized. Dadurch können alle require_scope Dependencies den Request über Session Auth autorisieren. Ohne diesen Mechanismus würden Dashboard-Requests (Session Cookie, kein API Key) an scope-geschützten Endpoints fehlschlagen.

Roles

Each user has a role within an organization:

Role Access
Owner Full access: subscribers, tags, billing, settings, API keys
Member Standard access: subscribers, tags, templates, campaigns

Owner-only features:

  • Billing management (/api/v1/billing/*)
  • Email settings (/api/v1/settings/email)
  • Organization settings

Preference Center Tokens

The Preference Center uses separate JWT tokens for subscriber self-service. These are not related to dashboard authentication.

Bash
1
2
3
4
5
# Generate a token (requires API key or session auth)
POST /api/v1/subscribers/{id}/token

# Use the token
GET /preference-center?token=eyJhbGciOi...

Tokens are scoped to a single subscriber and grant access only to that subscriber's preference data.

Security Recommendations

API Keys

  1. Never in client code: API keys belong on the server only
  2. Environment variables: export SUBSCRIBEFLOW_API_KEY=sf_live_xxx
  3. Regular rotation: At least every 90 days
  4. Audit logs: Monitor key usage
  1. Token security: Magic links are single-use and expire after 15 minutes
  2. Anti-enumeration: The magic link endpoint always returns success
  3. Cookie security: HttpOnly + SameSite=Lax + Secure prevents common web attacks
  4. Session expiry: 7-day lifetime limits exposure window

General

Python
# Example: Secure key storage
import os
from subscribeflow import Client

# Never hardcode!
# api_key = "sf_live_xxx"  # WRONG

# Load from environment
api_key = os.environ["SUBSCRIBEFLOW_API_KEY"]  # CORRECT
client = Client(api_key=api_key)

Troubleshooting

401 Unauthorized
  • Check the X-API-Key header (not Authorization: Bearer)
  • Has the key/token expired?
  • Is the key prefix correct (live vs. test)?
  • For session auth: is the sf_session cookie present?
403 Forbidden
  • Does the key have the required scopes?
  • Does the resource belong to the same organization?
  • For session auth: is X-Organization-Id set correctly?
  • Is the user a member of the organization?
  • Are you accessing an owner-only endpoint as a member?
Magic link not received
  • Check spam/junk folder
  • The API always returns 200 (anti-enumeration), so the email may not exist
  • Verify Resend is configured correctly
  • Check server logs for email delivery errors