Skip to content

Error Codes

Overview of all API error codes and their meanings.

Response Format

Errors are returned in the following format:

JSON
{
  "error": {
    "code": "validation_error",
    "message": "Human-readable error message",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address"
      }
    ]
  }
}

HTTP Status Codes

Status Meaning Description
200 OK Request successful
201 Created Resource created
204 No Content Successful, no data
400 Bad Request Invalid request data
401 Unauthorized Authentication missing/invalid
402 Payment Required Plan limit exceeded
403 Forbidden No permission
404 Not Found Resource not found
409 Conflict Resource already exists
422 Unprocessable Validation error
429 Too Many Requests Rate limit exceeded
500 Internal Error Server error
503 Service Unavailable Service temporarily unavailable

Error Codes

Authentication Errors (401)

Code Description Solution
unauthorized No auth header/cookie Add X-API-Key header or session cookie
invalid_token Token invalid Request a new token
token_expired Token expired Refresh the token
invalid_api_key API key invalid Check key or create a new one
api_key_revoked API key revoked Create a new key
magic_link_expired Magic link token has expired Request a new magic link
magic_link_already_used Magic link was already consumed Request a new magic link
invalid_session Session cookie is invalid or expired Re-authenticate via magic link
JSON
1
2
3
4
5
6
{
  "error": {
    "code": "magic_link_expired",
    "message": "This magic link has expired. Please request a new one."
  }
}

Authorization Errors (403)

Code Description Solution
forbidden No permission Check permissions
insufficient_permissions Missing permissions Use an API key with broader scopes
organization_mismatch Wrong organization Resource belongs to another org
organization_not_found Organization ID not valid Check X-Organization-Id header
not_a_member User is not a member of the org Request access from an org owner
owner_only Action restricted to owners Only organization owners can access billing/settings
JSON
1
2
3
4
5
6
{
  "error": {
    "code": "not_a_member",
    "message": "You are not a member of this organization"
  }
}

Payment Required Errors (402)

These errors occur when an organization exceeds the limits of their subscription tier.

Code Description Solution
subscriber_limit_exceeded Max subscriber count reached Upgrade plan or remove inactive subscribers
email_limit_exceeded Monthly email limit reached Upgrade plan or wait for monthly reset
JSON
{
  "error": {
    "code": "subscriber_limit_exceeded",
    "message": "Subscriber limit exceeded",
    "limit": "max_subscribers",
    "current": 500,
    "max": 500,
    "tier": "free",
    "upgrade_url": "/api/v1/billing/create-checkout-session"
  }
}

Tier Limits

Tier Subscribers Emails/Month
Free 500 1,000
Starter 5,000 10,000
Professional 50,000 100,000

Billing Errors (400/402)

Code Description Solution
no_active_subscription Organization has no paid subscription Create a checkout session first
invalid_price Invalid Stripe price ID Use a valid price ID from your Stripe dashboard
checkout_failed Stripe checkout session creation failed Retry or check Stripe status
portal_unavailable Cannot create Stripe portal Ensure org has a Stripe customer ID
JSON
1
2
3
4
5
6
{
  "error": {
    "code": "no_active_subscription",
    "message": "No active subscription found. Please subscribe to a plan first."
  }
}

Validation Errors (400/422)

Code Description Solution
validation_error Validation failed Correct the fields
invalid_email Email invalid Use a valid email address
invalid_tag Tag does not exist Check the tag name
missing_field Required field missing Add the field
JSON
{
  "error": {
    "code": "validation_error",
    "message": "Validation failed",
    "details": [
      {
        "field": "email",
        "message": "Must be a valid email address",
        "code": "invalid_email"
      },
      {
        "field": "first_name",
        "message": "Must not exceed 100 characters",
        "code": "max_length"
      }
    ]
  }
}

Resource Errors (404/409)

Code Description Solution
not_found Resource not found Check the ID
subscriber_not_found Subscriber not found Check the subscriber ID
tag_not_found Tag not found Check the tag name/ID
organization_not_found Organization not found Check the organization ID
already_exists Resource already exists Observe unique constraint
email_exists Email already exists Use a different email or update
JSON
1
2
3
4
5
6
{
  "error": {
    "code": "subscriber_not_found",
    "message": "Subscriber with ID 'xxx' not found"
  }
}

Rate Limit Errors (429)

Code Description Solution
rate_limited Rate limit reached Wait or upgrade
JSON
1
2
3
4
5
6
7
{
  "error": {
    "code": "rate_limited",
    "message": "Rate limit exceeded. Retry after 60 seconds",
    "retry_after": 60
  }
}

Response Headers:

Text Only
1
2
3
4
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1609459200
Retry-After: 60

Server Errors (5xx)

Code Description Solution
internal_error Internal error Retry with backoff
database_error Database error Retry with backoff
external_service_error External service error Retry with backoff
service_unavailable Service unavailable Try again later
JSON
1
2
3
4
5
6
7
{
  "error": {
    "code": "internal_error",
    "message": "An unexpected error occurred",
    "request_id": "req_abc123"
  }
}

Request ID

A request_id is returned for server errors. Include it when contacting support.

Error Handling Best Practices

Python

Python
from subscribeflow import Client
from subscribeflow.exceptions import (
    SubscribeFlowError,
    ValidationError,
    NotFoundError,
    RateLimitError,
    LimitExceededError,
)

client = Client(api_key="...")

try:
    subscriber = client.subscribers.create(email="invalid")
except LimitExceededError as e:
    print(f"Limit exceeded: {e.limit} ({e.current}/{e.max})")
    print(f"Upgrade at: {e.upgrade_url}")
except ValidationError as e:
    print(f"Validation failed: {e.details}")
except NotFoundError as e:
    print(f"Not found: {e.message}")
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after}s")
except SubscribeFlowError as e:
    print(f"Error: {e.code} - {e.message}")

TypeScript

TypeScript
import { SubscribeFlow, SubscribeFlowError } from '@subscribeflow/sdk';

const client = new SubscribeFlow('...');

try {
  const subscriber = await client.subscribers.create({ email: 'invalid' });
} catch (error) {
  if (error instanceof SubscribeFlowError) {
    switch (error.code) {
      case 'subscriber_limit_exceeded':
      case 'email_limit_exceeded':
        console.log(`Plan limit reached: ${error.current}/${error.max}`);
        break;
      case 'validation_error':
        console.log('Validation failed:', error.details);
        break;
      case 'rate_limited':
        console.log(`Retry after ${error.retryAfter}s`);
        break;
      default:
        console.log(`Error: ${error.code} - ${error.message}`);
    }
  }
}

Retry Strategy

Python
import time
from subscribeflow.exceptions import RateLimitError, ServerError

def with_retry(func, max_retries=3):
    for attempt in range(max_retries):
        try:
            return func()
        except RateLimitError as e:
            time.sleep(e.retry_after)
        except ServerError:
            time.sleep(2 ** attempt)  # Exponential backoff
    raise Exception("Max retries exceeded")