SnackBase API Examples

January 19, 2026 · View on GitHub

Complete guide to using the SnackBase REST API with practical examples.


Table of Contents


Getting Started

Base URL

Development: http://localhost:8000
Production:  https://api.yourdomain.com

API Version

All endpoints are prefixed with /api/v1:

http://localhost:8000/api/v1/auth/register
http://localhost:8000/api/v1/collections
http://localhost:8000/api/v1/records/posts

Important: Records are accessed via /api/v1/records/{collection}, NOT /api/v1/{collection}. This is critical for proper route registration.

Interactive Documentation

Common Headers

Content-Type: application/json
Authorization: Bearer <access_token>
X-Correlation-ID: <optional-request-id>

Authentication

1. Register New Account

Create a new account with the first admin user.

Endpoint: POST /api/v1/auth/register

Authentication: None (public endpoint)

Request:

curl -X POST http://localhost:8000/api/v1/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "account_name": "Acme Corporation",
    "account_slug": "acme",
    "email": "admin@acme.com",
    "password": "SecurePass123!"
  }'

Response (201 Created):

{
  "message": "Registration successful. Please check your email to verify your account.",
  "account": {
    "id": "AB1234",
    "slug": "acme",
    "name": "Acme Corporation",
    "created_at": "2025-12-24T22:00:00Z"
  },
  "user": {
    "id": "usr_abc123",
    "email": "admin@acme.com",
    "role": "admin",
    "is_active": true,
    "email_verified": false,
    "created_at": "2025-12-24T22:00:00Z"
  }
}

Important Changes:

  • Registration NO LONGER returns tokens immediately
  • Email verification is REQUIRED before login
  • Response includes email_verified field
  • User must verify email before they can authenticate

Validation Rules:

  • account_name: 1-255 characters
  • account_slug: 3-32 characters, alphanumeric + hyphens, starts with letter (optional, auto-generated from name)
  • email: Valid email format
  • password: Min 12 characters, must include uppercase, lowercase, number, and special character

Password Strength Requirements:

  • Minimum 12 characters
  • At least one uppercase letter (A-Z)
  • At least one lowercase letter (a-z)
  • At least one digit (0-9)
  • At least one special character: !@#$%^&*()_+\-=\[\]{};':\"\\|,.<>\/?~

Error Examples:

// Weak password
{
  "error": "Validation error",
  "details": [
    {
      "field": "password",
      "message": "Password must be at least 12 characters and include uppercase, lowercase, number, and special character"
    }
  ]
}

// Duplicate slug
{
  "error": "Conflict",
  "message": "Account slug 'acme' already exists"
}

2. Login

Authenticate with email, password, and account identifier.

Endpoint: POST /api/v1/auth/login

Authentication: None (public endpoint)

Request:

curl -X POST http://localhost:8000/api/v1/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "account": "acme",
    "email": "admin@acme.com",
    "password": "SecurePass123!"
  }'

Response (200 OK):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600,
  "account": {
    "id": "AB1234",
    "slug": "acme",
    "name": "Acme Corporation"
  },
  "user": {
    "id": "usr_abc123",
    "email": "admin@acme.com",
    "role": "admin"
  }
}

Account Identifier Options:

  • Account slug: "acme"
  • Account ID: "AB1234"

Error Examples:

// Email not verified (401)
{
  "error": "Email not verified",
  "message": "Please verify your email before logging in",
  "redirect_url": "/api/v1/auth/send-verification"
}

// Invalid credentials (401)
{
  "error": "Authentication failed",
  "message": "Invalid credentials"
}

// Wrong authentication method (401)
{
  "error": "Wrong authentication method",
  "message": "This account uses OAuth authentication. Please use the OAuth login flow.",
  "auth_provider": "oauth",
  "auth_provider_name": "google",
  "redirect_url": "/api/v1/auth/oauth/google/authorize"
}

Note: All authentication failures return a generic 401 message (prevents user enumeration), except for email verification and wrong auth method which provide specific guidance.


3. Refresh Token

Get a new access token using a refresh token.

Endpoint: POST /api/v1/auth/refresh

Authentication: None (uses refresh token)

Request:

curl -X POST http://localhost:8000/api/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  }'

Response (200 OK):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600
}

Notes:

  • Old refresh token is invalidated after successful refresh (token rotation)
  • Access tokens expire in 1 hour (configurable)
  • Refresh tokens expire in 7 days (configurable)

4. Get Current User

Get information about the authenticated user.

Endpoint: GET /api/v1/auth/me

Authentication: Required (Bearer token)

Request:

curl -X GET http://localhost:8000/api/v1/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Response (200 OK):

{
  "user_id": "usr_abc123",
  "account_id": "AB1234",
  "email": "admin@acme.com",
  "role": "admin",
  "email_verified": true
}

API Keys

API keys provide an alternative authentication method for service-to-service communication, CLI tools, and integrations where JWT token management is impractical.

When to Use API Keys

Use CaseRecommended Method
Browser applicationsJWT (access/refresh tokens)
Mobile appsJWT (access/refresh tokens)
Service-to-service callsAPI Keys
CLI toolsAPI Keys
WebhooksAPI Keys
Third-party integrationsAPI Keys

API Key Format

API keys follow this format:

sb_sk_<account_code>_<random_32_characters>

Example:

sb_sk_AB1234_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

Components:

  • sb_sk - SnackBase Secret Key prefix
  • AB1234 - Account code (human-readable identifier)
  • a1b2c3...o5p6 - 32-character cryptographically secure random string

1. Create an API Key

Generate a new API key for your account.

Endpoint: POST /api/v1/api-keys/

Authentication: Required (JWT bearer token)

Request:

curl -X POST http://localhost:8000/api/v1/api-keys/ \
  -H "Authorization: Bearer <jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production API",
    "description": "Used by production backend service"
  }'

Response (201 Created):

{
  "id": "ak_abc123xyz",
  "name": "Production API",
  "description": "Used by production backend service",
  "key": "sb_sk_AB1234_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  "account_id": "AB1234",
  "created_by": "usr_abc123",
  "created_at": "2026-01-17T10:30:00Z",
  "last_used_at": null,
  "is_revoked": false
}

Important: The full API key (key field) is only shown once at creation time. Save it securely - you won't be able to retrieve it again.

Creating with cURL (storing the key):

# Create and save to environment variable
response=$(curl -s -X POST http://localhost:8000/api/v1/api-keys/ \
  -H "Authorization: Bearer <jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "My Service"}')

# Extract and export the key
export SNACKBASE_API_KEY=$(echo "$response" | jq -r '.key')
echo "API Key saved to SNACKBASE_API_KEY"

2. List API Keys

Get all API keys for the current account.

Endpoint: GET /api/v1/api-keys/

Authentication: Required (JWT bearer token)

Request:

curl -X GET http://localhost:8000/api/v1/api-keys/ \
  -H "Authorization: Bearer <jwt_token>"

Response (200 OK):

{
  "items": [
    {
      "id": "ak_abc123xyz",
      "name": "Production API",
      "description": "Used by production backend service",
      "account_id": "AB1234",
      "created_by": "usr_abc123",
      "created_at": "2026-01-17T10:30:00Z",
      "last_used_at": "2026-01-17T15:45:00Z",
      "is_revoked": false
    },
    {
      "id": "ak_def456uvw",
      "name": "Development CLI",
      "description": "Local development tools",
      "account_id": "AB1234",
      "created_by": "usr_abc123",
      "created_at": "2026-01-15T09:00:00Z",
      "last_used_at": "2026-01-17T08:20:00Z",
      "is_revoked": false
    }
  ],
  "total": 2,
  "page": 1,
  "page_size": 50
}

Note: The list endpoint does NOT return the full API key value (only metadata).


3. Authenticate with API Key

Use an API key instead of JWT token for API requests.

Endpoint: Any protected endpoint

Authentication: API Key (Bearer token)

Request:

# Using API key in Authorization header
curl -X GET http://localhost:8000/api/v1/auth/me \
  -H "Authorization: Bearer sb_sk_AB1234_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

# Using API key with records
curl -X GET http://localhost:8000/api/v1/posts \
  -H "Authorization: Bearer sb_sk_AB1234_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

# Creating a record
curl -X POST http://localhost:8000/api/v1/posts \
  -H "Authorization: Bearer sb_sk_AB1234_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Hello World",
    "content": "My first post"
  }'

Response: Same as JWT authentication (user context is extracted from the API key)

{
  "id": "post_abc123",
  "title": "Hello World",
  "content": "My first post",
  "created_by": "usr_abc123",
  "account_id": "AB1234",
  "created_at": "2026-01-17T10:30:00Z"
}

4. Revoke an API Key

Invalidate an API key immediately (useful for security incidents or key rotation).

Endpoint: POST /api/v1/api-keys/{key_id}/revoke

Authentication: Required (JWT bearer token)

Request:

curl -X POST http://localhost:8000/api/v1/api-keys/ak_abc123xyz/revoke \
  -H "Authorization: Bearer <jwt_token>"

Response (200 OK):

{
  "id": "ak_abc123xyz",
  "name": "Production API",
  "description": "Used by production backend service",
  "account_id": "AB1234",
  "created_by": "usr_abc123",
  "created_at": "2026-01-17T10:30:00Z",
  "last_used_at": "2026-01-17T15:45:00Z",
  "is_revoked": true,
  "revoked_at": "2026-01-17T16:00:00Z",
  "revoked_by": "usr_abc123"
}

Note: Once revoked, an API key cannot be restored. You must create a new key.


5. Get API Key Details

Get details about a specific API key.

Endpoint: GET /api/v1/api-keys/{key_id}

Authentication: Required (JWT bearer token)

Request:

curl -X GET http://localhost:8000/api/v1/api-keys/ak_abc123xyz \
  -H "Authorization: Bearer <jwt_token>"

Response (200 OK):

{
  "id": "ak_abc123xyz",
  "name": "Production API",
  "description": "Used by production backend service",
  "account_id": "AB1234",
  "created_by": "usr_abc123",
  "created_at": "2026-01-17T10:30:00Z",
  "last_used_at": "2026-01-17T15:45:00Z",
  "is_revoked": false
}

Security Best Practices for API Keys

1. Storage:

# ✅ Good: Environment variable
export SNACKBASE_API_KEY="sb_sk_..."

# ✅ Good: Secure credential store (AWS Secrets Manager, Vault, etc.)

# ❌ Bad: Hardcoded in source code
api_key = "sb_sk_..."  # NEVER do this

# ❌ Bad: Committed to version control
git add .env  # NEVER commit API keys

2. Key Rotation:

# Create new key
new_key=$(curl -s -X POST http://localhost:8000/api/v1/api-keys/ \
  -H "Authorization: Bearer <jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "Rotated Key"}' | jq -r '.key')

# Update your service with new key
export SNACKBASE_API_KEY="$new_key"

# Verify new key works
curl -H "Authorization: Bearer $new_key" http://localhost:8000/api/v1/auth/me

# Revoke old key
curl -X POST http://localhost:8000/api/v1/api-keys/ak_old_key/revoke \
  -H "Authorization: Bearer <jwt_token>"

3. Scoping and Naming:

# Use descriptive names for easy identification
{
  "name": "Production - Payment Service",
  "description": "Used by payment processing service in production"
}

# Create separate keys for different environments
{
  "name": "Development - Local CLI"
}

# Create separate keys for different services
{
  "name": "Staging - Webhook Handler"
}

4. Monitoring:

# Regularly audit API keys
curl -X GET http://localhost:8000/api/v1/api-keys/ \
  -H "Authorization: Bearer <jwt_token>" | jq '.items[] | select(.last_used_at == null)'

# Look for unused keys that can be revoked
# Check for keys with old last_used_at dates

5. Revocation on Compromise:

# Immediately revoke if key is leaked
curl -X POST http://localhost:8000/api/v1/api-keys/ak_leaked_key/revoke \
  -H "Authorization: Bearer <jwt_token>"

# Create replacement key
curl -X POST http://localhost:8000/api/v1/api-keys/ \
  -H "Authorization: Bearer <jwt_token>" \
  -H "Content-Type: application/json" \
  -d '{"name": "Replacement for leaked key"}'

API Key vs JWT Comparison

FeatureAPI KeysJWT Tokens
Use CaseService-to-service, CLI, webhooksBrowser, mobile apps
LifetimeIndefinite (until revoked)Access: 1 hour, Refresh: 7 days
StorageServer-side (hash)Client-side (localStorage/cookie)
VisibilityFull key shown only at creationTokens visible in responses
RotationManual (create new, revoke old)Automatic (on refresh)
RevocationImmediateOn refresh or logout
User ContextSingle user per keyCan include user, role, account
SecuritySHA-256 hashed at restSigned, verifiable signature

Error Responses

// Invalid API key (401)
{
  "detail": "Invalid API key"
}

// Revoked API key (401)
{
  "detail": "API key has been revoked"
}

// API key not found (404)
{
  "detail": "API key not found"
}

// Unauthorized access to key (403)
{
  "detail": "You do not have permission to access this API key"
}

// Missing name on creation (422)
{
  "detail": [
    {
      "type": "missing",
      "loc": ["body", "name"],
      "msg": "Field required"
    }
  ]
}

Email Verification

Email verification is required for new user registrations before they can log in.

1. Send Verification Email

Send a verification email to the current authenticated user.

Endpoint: POST /api/v1/auth/send-verification

Authentication: Required

Request:

curl -X POST http://localhost:8000/api/v1/auth/send-verification \
  -H "Authorization: Bearer <token>"

Response (200 OK):

{
  "message": "Verification email sent successfully",
  "email": "admin@acme.com"
}

Error Responses:

// Already verified
{
  "error": "Email already verified",
  "message": "Your email has already been verified"
}

// Rate limited
{
  "error": "Too many requests",
  "message": "Please wait before requesting another verification email"
}

2. Resend Verification Email

Resend a verification email (public endpoint for use after registration).

Endpoint: POST /api/v1/auth/resend-verification

Authentication: None (public)

Request:

curl -X POST http://localhost:8000/api/v1/auth/resend-verification \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@acme.com",
    "account": "acme"
  }'

Response (200 OK):

{
  "message": "Verification email sent successfully",
  "email": "admin@acme.com"
}

3. Verify Email with Token

Verify email address using the token from the verification email.

Endpoint: POST /api/v1/auth/verify-email

Authentication: None (public)

Request:

curl -X POST http://localhost:8000/api/v1/auth/verify-email \
  -H "Content-Type: application/json" \
  -d '{
    "token": "verify_token_abc123..."
  }'

Response (200 OK):

{
  "message": "Email verified successfully",
  "user": {
    "id": "usr_abc123",
    "email": "admin@acme.com",
    "email_verified": true
  }
}

Error Responses:

// Invalid/expired token
{
  "error": "Invalid or expired token",
  "message": "The verification token is invalid or has expired"
}

Password Reset

Password reset flow allows users to recover access to their account if they forget their password.

1. Request Password Reset

Send a password reset email (public endpoint).

Endpoint: POST /api/v1/auth/forgot-password

Authentication: None (public)

Request:

curl -X POST http://localhost:8000/api/v1/auth/forgot-password \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@acme.com",
    "account": "acme"
  }'

Response (200 OK):

{
  "message": "If an account with that email exists, a password reset link has been sent."
}

Note: To prevent user enumeration, this endpoint always returns the same success message regardless of whether the email or account exists.


2. Verify Reset Token

Check if a password reset token is valid before showing the reset form.

Endpoint: GET /api/v1/auth/verify-reset-token/{token}

Authentication: None (public)

Request:

curl -X GET http://localhost:8000/api/v1/auth/verify-reset-token/reset_token_abc123...

Response (200 OK):

{
  "valid": true,
  "expires_at": "2026-01-10T20:00:00Z"
}

3. Reset Password

Update the password using a valid reset token.

Endpoint: POST /api/v1/auth/reset-password

Authentication: None (public)

Request:

curl -X POST http://localhost:8000/api/v1/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{
    "token": "reset_token_abc123...",
    "new_password": "NewSecurePass789!"
  }'

Response (200 OK):

{
  "message": "Password reset successfully. You can now log in with your new password."
}

Error Responses:

// Invalid/expired token (400)
{
  "error": "Bad Request",
  "message": "Invalid, expired, or already used reset token"
}

// Password validation error (400)
{
  "error": "Validation error",
  "details": [
    {
      "field": "password",
      "message": "Password must be at least 12 characters and include uppercase, lowercase, number, and special character"
    }
  ]
}

Accounts Management

All account management endpoints require Superadmin access (user must belong to system account SY0000).

System Account Details:

  • Account ID: 00000000-0000-0000-0000-000000000000 (UUID format for system-level configs)
  • Account Code: SY0000 (display format)

1. List Accounts

Endpoint: GET /api/v1/accounts

Authentication: Superadmin required

Query Parameters:

ParameterTypeDefaultConstraints
pageint1>= 1
page_sizeint25>= 1, <= 100
sort_bystr"created_at"id, account_code, slug, name, created_at
sort_orderstr"desc"asc, desc
searchstrnull-

Request:

curl -X GET "http://localhost:8000/api/v1/accounts?page=1&page_size=25" \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "items": [
    {
      "id": "AB1234",
      "account_code": "AB1234",
      "slug": "acme",
      "name": "Acme Corporation",
      "created_at": "2025-12-24T22:00:00Z",
      "user_count": 5,
      "status": "active"
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 25,
  "total_pages": 1
}

2. Get Account Details

Endpoint: GET /api/v1/accounts/{account_id}

Authentication: Superadmin required

Request:

curl -X GET http://localhost:8000/api/v1/accounts/AB1234 \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "id": "AB1234",
  "account_code": "AB1234",
  "slug": "acme",
  "name": "Acme Corporation",
  "created_at": "2025-12-24T22:00:00Z",
  "updated_at": "2025-12-24T22:00:00Z",
  "user_count": 5,
  "collections_used": []
}

3. Create Account

Endpoint: POST /api/v1/accounts

Authentication: Superadmin required

Request:

curl -X POST http://localhost:8000/api/v1/accounts \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "New Company LLC",
    "slug": "newcompany"
  }'

Response (201 Created):

{
  "id": "AB1235",
  "account_code": "NE0001",
  "slug": "newcompany",
  "name": "New Company LLC",
  "created_at": "2025-12-24T22:00:00Z",
  "updated_at": "2025-12-24T22:00:00Z",
  "user_count": 0,
  "collections_used": []
}

4. Update Account

Endpoint: PUT /api/v1/accounts/{account_id}

Authentication: Superadmin required

Note: Only the account name can be updated. Account ID and slug are immutable.

Request:

curl -X PUT http://localhost:8000/api/v1/accounts/AB1234 \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corporation Updated"
  }'

5. Delete Account

Endpoint: DELETE /api/v1/accounts/{account_id}

Authentication: Superadmin required

Request:

curl -X DELETE http://localhost:8000/api/v1/accounts/AB1234 \
  -H "Authorization: Bearer <superadmin_token>"

Response: 204 No Content (empty body)

Note: System account (SY0000) cannot be deleted.


6. Get Account Users

Endpoint: GET /api/v1/accounts/{account_id}/users

Authentication: Superadmin required

Request:

curl -X GET "http://localhost:8000/api/v1/accounts/AB1234/users?page=1&page_size=25" \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "items": [
    {
      "id": "usr_abc123",
      "email": "admin@acme.com",
      "role": "admin",
      "is_active": true,
      "created_at": "2025-12-24T22:00:00Z"
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 25,
  "total_pages": 1
}

Collections

All collection endpoints require Superadmin access.

1. List Collections

Endpoint: GET /api/v1/collections/

Authentication: Superadmin required

Query Parameters:

ParameterTypeDefaultConstraints
pageint1>= 1
page_sizeint25>= 1, <= 100
sort_bystr"created_at"-
sort_orderstr"desc"asc, desc
searchstrnull-

Request:

curl -X GET "http://localhost:8000/api/v1/collections/?page=1&page_size=25" \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "items": [
    {
      "id": "col_xyz789",
      "name": "posts",
      "table_name": "col_posts",
      "fields_count": 5,
      "records_count": 42,
      "created_at": "2025-12-24T22:00:00Z"
    }
  ],
  "total": 1,
  "page": 1,
  "page_size": 25,
  "total_pages": 1
}

2. Get Collection Names

Endpoint: GET /api/v1/collections/names

Authentication: Superadmin required

Response (200 OK):

{
  "names": ["posts", "users", "products"],
  "total": 3
}

3. Get Single Collection

Endpoint: GET /api/v1/collections/{collection_id}

Authentication: Superadmin required

Request:

curl -X GET http://localhost:8000/api/v1/collections/col_xyz789 \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "id": "col_xyz789",
  "name": "posts",
  "table_name": "col_posts",
  "fields": [
    {
      "name": "title",
      "type": "text",
      "required": true,
      "default": null,
      "unique": false,
      "options": null,
      "pii": false
    },
    {
      "name": "content",
      "type": "text",
      "required": true,
      "pii": false
    },
    {
      "name": "author_id",
      "type": "reference",
      "collection": "users",
      "on_delete": "cascade",
      "required": false
    }
  ],
  "created_at": "2025-12-24T22:00:00Z",
  "updated_at": "2025-12-24T22:00:00Z"
}

4. Create Collection

Endpoint: POST /api/v1/collections/

Authentication: Superadmin required

Request:

curl -X POST http://localhost:8000/api/v1/collections/ \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "posts",
    "schema": [
      {
        "name": "title",
        "type": "text",
        "required": true
      },
      {
        "name": "content",
        "type": "text",
        "required": true
      },
      {
        "name": "published",
        "type": "boolean",
        "default": false
      },
      {
        "name": "views",
        "type": "number",
        "default": 0
      },
      {
        "name": "author_id",
        "type": "reference",
        "collection": "users",
        "on_delete": "cascade"
      },
      {
        "name": "status",
        "type": "text",
        "options": ["draft", "published", "archived"]
      },
      {
        "name": "email",
        "type": "email"
      },
      {
        "name": "website",
        "type": "url"
      },
      {
        "name": "metadata",
        "type": "json"
      },
      {
        "name": "social_security",
        "type": "text",
        "pii": true,
        "mask_type": "ssn"
      }
    ]
  }'

Response (201 Created):

{
  "id": "col_xyz789",
  "name": "posts",
  "table_name": "col_posts",
  "fields": [...],
  "created_at": "2025-12-24T22:00:00Z",
  "updated_at": "2025-12-24T22:00:00Z"
}

Field Types:

TypeSQL TypeDescription
textTEXTString values
numberREALNumeric values (int or float, not bool)
booleanINTEGER (0/1)True/false
datetimeDATETIMEISO 8601 datetime strings
emailTEXTEmail addresses (validated)
urlTEXTURLs (validated, must start with http:// or https://)
jsonTEXTJSON objects
referenceTEXTForeign key to another collection

Field Options:

  • required: Boolean (default: false)
  • default: Default value
  • unique: Boolean (default: false)
  • options: Array of allowed values (enum)
  • pii: Boolean - Enable PII masking
  • mask_type: For PII fields - email, ssn, phone, name, full, custom

Auto-Added Fields: Every collection automatically includes:

  • id (TEXT PRIMARY KEY) - Auto-generated UUID
  • account_id (TEXT NOT NULL) - For multi-tenancy
  • created_at (DATETIME) - ISO 8601 timestamp
  • created_by (TEXT) - User ID who created the record
  • updated_at (DATETIME) - ISO 8601 timestamp
  • updated_by (TEXT) - User ID who last updated the record

Physical Table Naming: Tables are prefixed with col_ (e.g., collection "posts" becomes col_posts).

Validation Rules:

  • Collection name: 3-64 chars, starts with letter, alphanumeric + underscores only
  • Field names: Cannot use reserved names (id, account_id, created_at, created_by, updated_at, updated_by)
  • Reference fields require collection and on_delete attributes

5. Update Collection

Endpoint: PUT /api/v1/collections/{collection_id}

Authentication: Superadmin required

Constraints:

  • Cannot delete existing fields
  • Cannot change field types
  • Can only add new fields

Request:

curl -X PUT http://localhost:8000/api/v1/collections/col_xyz789 \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "schema": [
      {
        "name": "title",
        "type": "text",
        "required": true
      },
      {
        "name": "content",
        "type": "text",
        "required": true
      },
      {
        "name": "published",
        "type": "boolean",
        "default": false
      },
      {
        "name": "views",
        "type": "number",
        "default": 0
      },
      {
        "name": "category",
        "type": "text"
      }
    ]
  }'

6. Delete Collection

Endpoint: DELETE /api/v1/collections/{collection_id}

Authentication: Superadmin required

Request:

curl -X DELETE http://localhost:8000/api/v1/collections/col_xyz789 \
  -H "Authorization: Bearer <superadmin_token>"

Response: 200 OK with record count confirmation


Records (CRUD)

IMPORTANT: All record operations use /api/v1/records/{collection}, NOT /api/v1/{collection}.

Route Registration Order: The records_router MUST be registered LAST in the FastAPI app to avoid capturing specific routes like /invitations, /collections, etc.

1. Create Record

Endpoint: POST /api/v1/records/{collection}

Authentication: Required

Example - Create Post:

curl -X POST http://localhost:8000/api/v1/records/posts \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Getting Started with SnackBase",
    "content": "SnackBase is an open-source Backend-as-a-Service...",
    "published": true,
    "author_id": "usr_abc123"
  }'

Response (201 Created):

{
  "id": "rec_abc123",
  "title": "Getting Started with SnackBase",
  "content": "SnackBase is an open-source Backend-as-a-Service...",
  "published": true,
  "views": 0,
  "account_id": "AB1234",
  "created_at": "2025-12-24T22:00:00Z",
  "created_by": "usr_abc123",
  "updated_at": "2025-12-24T22:00:00Z",
  "updated_by": "usr_abc123"
}

Superadmin Special Access: Superadmin can create records for any account by passing account_id in the request body:

curl -X POST http://localhost:8000/api/v1/records/posts \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Post for another account",
    "content": "Content...",
    "account_id": "AB1235"
  }'

Notes:

  • account_id, created_at, created_by, updated_at, updated_by are auto-set by built-in hooks
  • Required fields must be provided
  • Default values are applied for missing optional fields
  • Reference values are validated for existence
  • PII fields are automatically masked for users without pii_access group

2. List Records

Endpoint: GET /api/v1/records/{collection}

Authentication: Required

Query Parameters:

ParameterTypeDefaultConstraints
skipint0>= 0
limitint30>= 1, <= 100
sortstr"-created_at"+/- prefix for asc/desc
fieldsstrnullComma-separated field list
{field}any-Filter by field value

Example - List Posts:

curl -X GET "http://localhost:8000/api/v1/records/posts?skip=0&limit=10&sort=-created_at" \
  -H "Authorization: Bearer <token>"

Response (200 OK):

{
  "items": [
    {
      "id": "rec_abc123",
      "title": "Getting Started with SnackBase",
      "content": "...",
      "published": true,
      "views": 42,
      "created_at": "2025-12-24T22:00:00Z"
    },
    {
      "id": "rec_def456",
      "title": "Advanced Features",
      "content": "...",
      "published": false,
      "views": 0,
      "created_at": "2025-12-24T21:00:00Z"
    }
  ],
  "total": 2,
  "skip": 0,
  "limit": 10
}

Example - Field Limiting:

curl -X GET "http://localhost:8000/api/v1/records/posts?fields=id,title,created_at" \
  -H "Authorization: Bearer <token>"

Response:

{
  "items": [
    {
      "id": "rec_abc123",
      "title": "Getting Started with SnackBase",
      "created_at": "2025-12-24T22:00:00Z"
    }
  ],
  "total": 1
}

Example - Filtering:

# Filter by field value (exact match only)
curl -X GET "http://localhost:8000/api/v1/records/posts?published=true" \
  -H "Authorization: Bearer <token>"

# Multiple filters (combined with AND)
curl -X GET "http://localhost:8000/api/v1/records/posts?published=true&status=draft" \
  -H "Authorization: Bearer <token>"

3. Get Single Record

Endpoint: GET /api/v1/records/{collection}/{id}

Authentication: Required

Example:

curl -X GET http://localhost:8000/api/v1/records/posts/rec_abc123 \
  -H "Authorization: Bearer <token>"

Response (200 OK):

{
  "id": "rec_abc123",
  "title": "Getting Started with SnackBase",
  "content": "SnackBase is an open-source Backend-as-a-Service...",
  "published": true,
  "views": 42,
  "account_id": "AB1234",
  "created_at": "2025-12-24T22:00:00Z",
  "created_by": "usr_abc123",
  "updated_at": "2025-12-24T22:00:00Z",
  "updated_by": "usr_abc123"
}

Error (404 Not Found):

{
  "error": "Not found",
  "message": "Record not found"
}

4. Update Record (Full)

Endpoint: PUT /api/v1/records/{collection}/{id}

Authentication: Required

Example:

curl -X PUT http://localhost:8000/api/v1/records/posts/rec_abc123 \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Getting Started with SnackBase (Updated)",
    "content": "Updated content...",
    "published": true,
    "views": 100
  }'

Response (200 OK):

{
  "id": "rec_abc123",
  "title": "Getting Started with SnackBase (Updated)",
  "content": "Updated content...",
  "published": true,
  "views": 100,
  "updated_at": "2025-12-24T22:30:00Z",
  "updated_by": "usr_abc123"
}

Note: PUT replaces the entire record. All non-system fields must be provided.


5. Update Record (Partial)

Endpoint: PATCH /api/v1/records/{collection}/{id}

Authentication: Required

Example:

curl -X PATCH http://localhost:8000/api/v1/records/posts/rec_abc123 \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "views": 150
  }'

Response (200 OK):

{
  "id": "rec_abc123",
  "title": "Getting Started with SnackBase (Updated)",
  "content": "Updated content...",
  "published": true,
  "views": 150,
  "updated_at": "2025-12-24T22:35:00Z",
  "updated_by": "usr_abc123"
}

Note: PATCH updates only the provided fields.


6. Delete Record

Endpoint: DELETE /api/v1/records/{collection}/{id}

Authentication: Required

Example:

curl -X DELETE http://localhost:8000/api/v1/records/posts/rec_abc123 \
  -H "Authorization: Bearer <token>"

Response (204 No Content): Empty body

Error - Foreign Key Restriction (409 Conflict):

{
  "error": "Conflict",
  "message": "Cannot delete record: it is referenced by other records"
}

Roles

Role management endpoints. Note that permission rules are now managed via Collection Rules, not purely by role.

1. List Roles

Endpoint: GET /api/v1/roles

Authentication: Superadmin required

Request:

curl -X GET "http://localhost:8000/api/v1/roles" \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

[
  {
    "id": 1,
    "name": "admin",
    "description": "Administrator",
    "created_at": "2025-12-24T22:00:00Z",
    "updated_at": "2025-12-24T22:00:00Z"
  },
  {
    "id": 2,
    "name": "user",
    "description": "Regular User",
    "created_at": "2025-12-24T22:00:00Z",
    "updated_at": "2025-12-24T22:00:00Z"
  }
]

2. Create Role

Endpoint: POST /api/v1/roles

Authentication: Superadmin required

Request:

curl -X POST "http://localhost:8000/api/v1/roles" \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "editor",
    "description": "Content Editor"
  }'

Response (201 Created):

{
  "id": 3,
  "name": "editor",
  "description": "Content Editor",
  "created_at": "2026-01-01T10:00:00Z",
  "updated_at": "2026-01-01T10:00:00Z"
}

3. Get Role

Endpoint: GET /api/v1/roles/{role_id}

Authentication: Superadmin required

Request:

curl -X GET "http://localhost:8000/api/v1/roles/3" \
  -H "Authorization: Bearer <superadmin_token>"

4. Update Role

Endpoint: PUT /api/v1/roles/{role_id}

Authentication: Superadmin required

Request:

curl -X PUT "http://localhost:8000/api/v1/roles/3" \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Senior Content Editor"
  }'

5. Delete Role

Endpoint: DELETE /api/v1/roles/{role_id}

Authentication: Superadmin required

Request:

curl -X DELETE "http://localhost:8000/api/v1/roles/3" \
  -H "Authorization: Bearer <superadmin_token>"

Collection Rules

Manage access control rules directly on collections.

Get Collection Rules

curl -X GET "https://api.snackbase.io/api/v1/collections/posts/rules" \
     -H "Authorization: Bearer <superadmin_token>"

Update Collection Rules

curl -X PUT "https://api.snackbase.io/api/v1/collections/posts/rules" \
     -H "Authorization: Bearer <superadmin_token>" \
     -H "Content-Type: application/json" \
     -d '{
       "list_rule": "status = '\''published'\''",
       "view_rule": "status = '\''published'\'' || created_by = @request.auth.id",
       "create_rule": "@request.auth.id != '\'''\'",
       "update_rule": "created_by = @request.auth.id",
       "delete_rule": "created_by = @request.auth.id",
       "list_fields": ["id", "title", "status", "created_at"]
     }'

Groups

Groups are account-isolated (not superadmin).

1. Create Group

Endpoint: POST /api/v1/groups

Authentication: Required (account-isolated)

Request:

curl -X POST http://localhost:8000/api/v1/groups \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "managers",
    "description": "Manager group with elevated permissions"
  }'

Response (201 Created):

{
  "id": "grp_abc123",
  "account_id": "AB1234",
  "account_code": "AB1234",
  "name": "managers",
  "description": "Manager group with elevated permissions",
  "created_at": "2025-12-24T22:00:00Z",
  "updated_at": "2025-12-24T22:00:00Z"
}

2. List Groups

Endpoint: GET /api/v1/groups

Query Parameters:

  • skip: Default 0
  • limit: Default 100

Request:

curl -X GET "http://localhost:8000/api/v1/groups?skip=0&limit=100" \
  -H "Authorization: Bearer <token>"

Response (200 OK):

[
  {
    "id": "grp_abc123",
    "account_id": "AB1234",
    "account_code": "AB1234",
    "name": "managers",
    "description": "Manager group with elevated permissions",
    "created_at": "2025-12-24T22:00:00Z",
    "updated_at": "2025-12-24T22:00:00Z"
  }
]

3. Get Single Group

Endpoint: GET /api/v1/groups/{group_id}


4. Update Group

Endpoint: PATCH /api/v1/groups/{group_id}

Request:

curl -X PATCH http://localhost:8000/api/v1/groups/grp_abc123 \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Updated description"
  }'

5. Delete Group

Endpoint: DELETE /api/v1/groups/{group_id}


6. Add User to Group

Endpoint: POST /api/v1/groups/{group_id}/users

Request:

curl -X POST http://localhost:8000/api/v1/groups/grp_abc123/users \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "usr_def456"
  }'

Response (201 Created):

{
  "message": "User added to group"
}

7. Remove User from Group

Endpoint: DELETE /api/v1/groups/{group_id}/users/{user_id}

Request:

curl -X DELETE http://localhost:8000/api/v1/groups/grp_abc123/users/usr_def456 \
  -H "Authorization: Bearer <token>"

Response: 204 No Content


Users

All users endpoints require Superadmin access.

1. Create User

Endpoint: POST /api/v1/users

Authentication: Superadmin required

Request (Password Authentication):

curl -X POST http://localhost:8000/api/v1/users \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "password": "SecurePass123!",
    "account_id": "AB1234",
    "role_id": "2"
  }'

Request (OAuth/SAML Authentication - password auto-generated):

curl -X POST http://localhost:8000/api/v1/users \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "account_id": "AB1234",
    "role_id": "2",
    "auth_provider": "oauth",
    "auth_provider_name": "google"
  }'

Response (201 Created):

{
  "id": "usr_xyz789",
  "email": "user@example.com",
  "role": {
    "id": 2,
    "name": "user"
  },
  "is_active": true,
  "email_verified": false,
  "auth_provider": "password",
  "auth_provider_name": null,
  "account_id": "AB1234",
  "created_at": "2025-12-24T22:00:00Z",
  "updated_at": "2025-12-24T22:00:00Z"
}

Authentication Provider Fields:

  • auth_provider: "password", "oauth", or "saml"
  • auth_provider_name: Provider name (e.g., "google", "github", "azure_ad", "okta")
  • For non-password auth, password is auto-generated and user cannot login with password

2. List Users

Endpoint: GET /api/v1/users

Authentication: Superadmin required

Query Parameters:

ParameterTypeDefaultConstraints
skipint0>= 0
limitint25>= 1, <= 100
account_idstrnullFilter by account
role_idintnullFilter by role
is_activeboolnullFilter by active status
searchstrnullSearch in email
sortstr"created_at"Field name (prefix with - for desc)

Request:

curl -X GET "http://localhost:8000/api/v1/users?skip=0&limit=25" \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "items": [
    {
      "id": "usr_abc123",
      "email": "admin@acme.com",
      "role": {
        "id": 1,
        "name": "admin"
      },
      "is_active": true,
      "email_verified": true,
      "auth_provider": "password",
      "auth_provider_name": null,
      "account_id": "AB1234",
      "account_code": "AB1234",
      "account_name": "Acme Corporation",
      "created_at": "2025-12-24T22:00:00Z",
      "updated_at": "2025-12-24T22:00:00Z",
      "last_login": null
    }
  ],
  "total": 1,
  "skip": 0,
  "limit": 25
}

3. Get Single User

Endpoint: GET /api/v1/users/{user_id}

Authentication: Superadmin required

Request:

curl -X GET http://localhost:8000/api/v1/users/usr_abc123 \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "id": "usr_abc123",
  "email": "admin@acme.com",
  "role": {
    "id": 1,
    "name": "admin",
    "description": "Administrator with full access"
  },
  "is_active": true,
  "email_verified": true,
  "auth_provider": "password",
  "auth_provider_name": null,
  "account_id": "AB1234",
  "account_code": "AB1234",
  "account_name": "Acme Corporation",
  "created_at": "2025-12-24T22:00:00Z",
  "updated_at": "2025-12-24T22:00:00Z",
  "last_login": "2025-12-24T23:00:00Z",
  "groups": []
}

4. Update User

Endpoint: PATCH /api/v1/users/{user_id}

Authentication: Superadmin required

Note: Users cannot modify their own role or deactivate themselves.

Request:

curl -X PATCH http://localhost:8000/api/v1/users/usr_abc123 \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "role_id": 2
  }'

Response (200 OK):

{
  "id": "usr_abc123",
  "email": "admin@acme.com",
  "role": {
    "id": 2,
    "name": "user"
  },
  "is_active": true,
  "updated_at": "2025-12-24T22:30:00Z"
}

5. Reset User Password

Endpoint: PUT /api/v1/users/{user_id}/password

Authentication: Superadmin required

Request:

curl -X PUT http://localhost:8000/api/v1/users/usr_abc123/password \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "new_password": "NewSecurePass456!"
  }'

Response (200 OK):

{
  "message": "Password reset successfully"
}

6. Deactivate User

Endpoint: DELETE /api/v1/users/{user_id}

Authentication: Superadmin required

Note: This is a soft delete. The user is marked as inactive but not removed from the database.

Request:

curl -X DELETE http://localhost:8000/api/v1/users/usr_abc123 \
  -H "Authorization: Bearer <superadmin_token>"

Response: 204 No Content


Invitations

1. Create Invitation

Invite a user to your account.

Endpoint: POST /api/v1/invitations

Authentication: Required

Request:

curl -X POST http://localhost:8000/api/v1/invitations \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "newuser@example.com",
    "role_id": "2"
  }'

Response (201 Created):

{
  "id": "inv_xyz789",
  "account_id": "AB1234",
  "email": "newuser@example.com",
  "invited_by": "usr_abc123",
  "expires_at": "2025-12-26T22:00:00Z",
  "accepted_at": null,
  "created_at": "2025-12-24T22:00:00Z",
  "status": "pending"
}

Note: Token is not returned for security. It's sent via email (future feature). Invitations expire after 48 hours. The role_id field is currently reserved for future use - all invited users are assigned the "user" role by default.


2. List Invitations

Endpoint: GET /api/v1/invitations

Query Parameters:

  • status_filter: "pending" | "accepted" | "expired" | "cancelled" (note: cancelled invitations are deleted from database, so this filter returns no results)

Request:

curl -X GET "http://localhost:8000/api/v1/invitations?status_filter=pending" \
  -H "Authorization: Bearer <token>"

Response (200 OK):

{
  "invitations": [
    {
      "id": "inv_xyz789",
      "account_id": "AB1234",
      "email": "newuser@example.com",
      "invited_by": "usr_abc123",
      "expires_at": "2025-12-26T22:00:00Z",
      "accepted_at": null,
      "created_at": "2025-12-24T22:00:00Z",
      "status": "pending"
    }
  ],
  "total": 1
}

3. Accept Invitation

Endpoint: POST /api/v1/invitations/{token}/accept

Authentication: None (public)

Request:

curl -X POST http://localhost:8000/api/v1/invitations/inv_token_abc123/accept \
  -H "Content-Type: application/json" \
  -d '{
    "password": "SecurePass123!"
  }'

Response (200 OK):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600,
  "account": {
    "id": "AB1234",
    "slug": "acme",
    "name": "Acme Corporation",
    "created_at": "2025-12-24T22:00:00Z"
  },
  "user": {
    "id": "usr_new123",
    "email": "newuser@example.com",
    "role": "user",
    "is_active": true,
    "created_at": "2025-12-24T22:00:00Z"
  }
}

4. Cancel Invitation

Endpoint: DELETE /api/v1/invitations/{invitation_id}

Request:

curl -X DELETE http://localhost:8000/api/v1/invitations/inv_xyz789 \
  -H "Authorization: Bearer <token>"

Response: 204 No Content


Macros

Macros allow you to define reusable permission logic. They come in two types: Built-in (text fragments) and Custom (SQL subqueries defined in the database).

1. Create Custom Macro

Endpoint: POST /api/v1/macros/

curl -X POST "http://localhost:8000/api/v1/macros/" \
     -H "Authorization: Bearer <superadmin_token>" \
     -H "Content-Type: application/json" \
     -d '{
       "name": "is_project_member",
       "description": "Check if user has access to a project",
       "sql_query": "SELECT count(*) > 0 FROM project_members WHERE project_id = \$1 AND user_id = @request.auth.id"
     }'

2. List Macros

Endpoint: GET /api/v1/macros

curl -X GET "http://localhost:8000/api/v1/macros" \
     -H "Authorization: Bearer <superadmin_token>"

3. Built-in Macros

These macros are available by default and are expanded during rule compilation.

MacroExampleExpansion
@owns_record()@owns_record()(created_by = @request.auth.id)
@owns_record(field)@owns_record('uid')(uid = @request.auth.id)
@has_role(name)@has_role('editor')(@request.auth.role = 'editor')
@is_public()@is_public()(public = true)
@is_creator()@is_creator()(created_by = @request.auth.id)

4. Implementation in Rules

Use macros in your collection rules via the Admin UI or collection_rules API.

Example rule:

@has_role('admin') || @is_project_member(project_id)

OAuth Authentication

SnackBase supports OAuth 2.0 authentication for popular providers.

Supported Providers

  • google - Google OAuth 2.0
  • github - GitHub OAuth App
  • microsoft - Microsoft Azure AD
  • apple - Sign in with Apple

1. Initiate OAuth Flow

Endpoint: POST /api/v1/auth/oauth/{provider_name}/authorize

Authentication: None (public)

Request:

curl -X POST http://localhost:8000/api/v1/auth/oauth/google/authorize \
  -H "Content-Type: application/json" \
  -d '{
    "account": "acme",
    "redirect_uri": "http://localhost:3000/auth/callback",
    "state": "random_state_string"
  }'

Response (200 OK):

{
  "authorization_url": "https://accounts.google.com/o/oauth2/v2/auth?client_id=...&redirect_uri=...",
  "state": "random_state_string",
  "provider": "google"
}

Query Parameters:

  • account (required): Account slug or ID
  • redirect_uri (required): URL to redirect after authentication
  • state (optional): CSRF protection token

2. OAuth Callback

Endpoint: POST /api/v1/auth/oauth/{provider_name}/callback

Authentication: None (public)

Request:

curl -X POST http://localhost:8000/api/v1/auth/oauth/google/callback \
  -H "Content-Type: application/json" \
  -d '{
    "code": "4/0AX4XfWh...",
    "state": "random_state_string",
    "redirect_uri": "http://localhost:3000/auth/callback"
  }'

Response (200 OK):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600,
  "account": {
    "id": "AB1234",
    "slug": "acme",
    "name": "Acme Corporation",
    "created_at": "2025-12-24T22:00:00Z"
  },
  "user": {
    "id": "usr_oauth123",
    "email": "user@gmail.com",
    "role": "user",
    "is_active": true,
    "created_at": "2025-12-24T22:00:00Z"
  },
  "is_new_user": false,
  "is_new_account": false
}

Note: If is_new_user is true, a new user account is created. If is_new_account is true, both account and user are created.


SAML Authentication

SnackBase supports SAML 2.0 for enterprise single sign-on (SSO).

Supported Providers

  • azure - Microsoft Azure AD
  • okta - Okta Identity Cloud
  • generic - Any SAML 2.0 compliant IdP

1. Initiate SAML SSO

Endpoint: GET /api/v1/auth/saml/sso

Authentication: None (public)

Request:

curl -X GET "http://localhost:8000/api/v1/auth/saml/sso?account=acme&provider=azure" \
  -L

Response: Redirects to the Identity Provider's login page

Query Parameters:

  • account (required): Account slug or ID
  • provider (optional): SAML provider name (default: first configured)
  • relay_state (optional): State to return after authentication

2. SAML Assertion Consumer Service (ACS)

Endpoint: POST /api/v1/auth/saml/acs

Authentication: None (public)

Request:

curl -X POST http://localhost:8000/api/v1/auth/saml/acs \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "SAMLResponse=PHNhbWxwOlJlc3BvbnNlIHdpZGhOg...&RelayState=optional_state"

Response (200 OK):

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expires_in": 3600,
  "account": {
    "id": "AB1234",
    "slug": "acme",
    "name": "Acme Corporation",
    "created_at": "2025-12-24T22:00:00Z"
  },
  "user": {
    "id": "usr_saml123",
    "email": "user@company.com",
    "role": "user",
    "is_active": true,
    "created_at": "2025-12-24T22:00:00Z"
  }
}

3. Download SAML Metadata

Endpoint: GET /api/v1/auth/saml/metadata

Authentication: None (public)

Request:

curl -X GET "http://localhost:8000/api/v1/auth/saml/metadata?account=acme&provider=azure" \
  -o saml-metadata.xml

Response: XML metadata file for importing into your Identity Provider

Query Parameters:

  • account (required): Account slug or ID
  • provider (optional): SAML provider name (default: first configured)

Realtime API

SnackBase provides real-time updates via WebSocket and Server-Sent Events (SSE).

1. WebSocket Connection

Endpoint: ws://localhost:8000/api/v1/realtime/ws

Authentication:

  1. Header: Authorization: Bearer <token> (if supported by client)
  2. Query Parameter: ?token=<token> (browser native WebSocket)

Client Protocol:

Send JSON messages to interact with the server.

Subscribe to Collection:

{
  "action": "subscribe",
  "collection": "posts",
  "operations": ["create", "update", "delete"]
}
  • operations is optional, defaults to all.

Unsubscribe:

{
  "action": "unsubscribe",
  "collection": "posts"
}

Keep-Alive (Ping):

{
  "action": "ping"
}

Server Responses:

  • Heartbeat (every 30s): {"type": "heartbeat", "timestamp": "..."}
  • Events: {"type": "posts.create", "data": { ...record... }}
  • Errors: {"error": "Max subscriptions reached"}

2. Server-Sent Events (SSE)

Endpoint: GET /api/v1/realtime/subscribe

Authentication: Query param ?token=<token> or Cookie.

Query Parameters:

  • collection: Name of collection to subscribe to (can be repeated).

Request:

curl -N "http://localhost:8000/api/v1/realtime/subscribe?collection=posts&collection=users" \
  -H "Authorization: Bearer <token>"

Events:

  • event: message: standard data payload.
  • event: heartbeat: keep-alive signal.

Files

File upload and download endpoints.

1. Upload File

Endpoint: POST /api/v1/files/upload

Authentication: Required

Request:

curl -X POST http://localhost:8000/api/v1/files/upload \
  -H "Authorization: Bearer <token>" \
  -F "file=@/path/to/document.pdf"

Response (200 OK):

{
  "success": true,
  "file": {
    "filename": "document.pdf",
    "path": "uploads/AB1234/document_20250124_abc123.pdf",
    "size": 102400,
    "content_type": "application/pdf",
    "uploaded_at": "2025-12-24T22:00:00Z"
  },
  "message": "File uploaded successfully"
}

2. Download File

Endpoint: GET /api/v1/files/{file_path:path}

Authentication: Required

Request:

curl -X GET http://localhost:8000/api/v1/files/uploads/AB1234/document_20250124_abc123.pdf \
  -H "Authorization: Bearer <token>" \
  -o downloaded_document.pdf

Response: File content (appropriate Content-Type header)


Admin Configuration

Admin API for managing system and account provider configurations.

Configuration Management

All provider configurations (auth, email, storage) are managed through these endpoints.

1. Get Configuration Statistics

Endpoint: GET /api/v1/admin/configuration/stats

Authentication: Superadmin required

Response (200 OK):

{
  "system_configs": {
    "auth": 5,
    "oauth": 4,
    "email": 2,
    "total": 11
  },
  "account_configs": {
    "auth": 3,
    "oauth": 2,
    "total": 5
  }
}

2. List System Configurations

Endpoint: GET /api/v1/admin/configuration/system

Authentication: Superadmin required

Query Parameters:

  • category: Filter by category (auth, oauth, saml, email)

Request:

curl -X GET "http://localhost:8000/api/v1/admin/configuration/system?category=oauth" \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

[
  {
    "id": "config_abc123",
    "category": "oauth",
    "provider_name": "google",
    "display_name": "Google OAuth",
    "logo_url": null,
    "enabled": true,
    "priority": 1,
    "is_builtin": false,
    "created_at": "2025-12-24T22:00:00Z",
    "updated_at": "2025-12-24T22:00:00Z"
  }
]

3. List Account Configurations

Endpoint: GET /api/v1/admin/configuration/account

Authentication: Superadmin required

Query Parameters:

  • account_id (required): Account ID
  • category: Filter by category

Request:

curl -X GET "http://localhost:8000/api/v1/admin/configuration/account?account_id=AB1234" \
  -H "Authorization: Bearer <superadmin_token>"

4. Create Configuration

Endpoint: POST /api/v1/admin/configuration

Authentication: Superadmin required

Request:

curl -X POST http://localhost:8000/api/v1/admin/configuration \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "category": "oauth",
    "provider_name": "google",
    "display_name": "Google OAuth",
    "config": {
      "client_id": "your-client-id.apps.googleusercontent.com",
      "client_secret": "your-client-secret",
      "scope": "openid email profile"
    },
    "logo_url": "https://example.com/logo.png",
    "enabled": true,
    "priority": 1
  }'

Response (201 Created):

{
  "id": "config_xyz789",
  "message": "Configuration created successfully"
}

5. Update Configuration Values

Endpoint: PATCH /api/v1/admin/configuration/{config_id}/values

Authentication: Superadmin required

Request:

curl -X PATCH http://localhost:8000/api/v1/admin/configuration/config_xyz789/values \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "new-client-id.apps.googleusercontent.com",
    "client_secret": "new-client-secret"
  }'

Response (200 OK):

{
  "success": true,
  "message": "Configuration updated successfully"
}

6. Get Configuration Values

Endpoint: GET /api/v1/admin/configuration/{config_id}/values

Authentication: Superadmin required

Response (200 OK):

{
  "client_id": "your-client-id.apps.googleusercontent.com",
  "client_secret": "********", // Masked for security
  "scope": "openid email profile"
}

Note: Sensitive values (secrets) are masked in the response.


7. Enable/Disable Configuration

Endpoint: PATCH /api/v1/admin/configuration/{config_id}

Authentication: Superadmin required

Request:

curl -X PATCH http://localhost:8000/api/v1/admin/configuration/config_xyz789 \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": false
  }'

8. Delete Configuration

Endpoint: DELETE /api/v1/admin/configuration/{config_id}

Authentication: Superadmin required

Note: Built-in configurations cannot be deleted, only disabled.


9. List Provider Definitions

Endpoint: GET /api/v1/admin/configuration/providers

Authentication: Superadmin required

Query Parameters:

  • category: Filter by category

Response (200 OK):

[
  {
    "category": "oauth",
    "name": "google",
    "display_name": "Google OAuth 2.0",
    "description": "Authenticate users with Google",
    "logo_url": null
  }
]

10. Get Provider Schema

Endpoint: GET /api/v1/admin/configuration/schema/{category}/{provider_name}

Authentication: Superadmin required

Request:

curl -X GET http://localhost:8000/api/v1/admin/configuration/schema/oauth/google \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "type": "object",
  "properties": {
    "client_id": {
      "type": "string",
      "title": "Client ID",
      "description": "OAuth client ID from Google Cloud Console"
    },
    "client_secret": {
      "type": "string",
      "title": "Client Secret",
      "format": "password",
      "secret": true
    },
    "scope": {
      "type": "string",
      "title": "OAuth Scope",
      "default": "openid email profile"
    }
  },
  "required": ["client_id", "client_secret"]
}

11. Test Provider Connection

Endpoint: POST /api/v1/admin/configuration/test-connection

Authentication: Superadmin required

Request:

curl -X POST http://localhost:8000/api/v1/admin/configuration/test-connection \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "category": "oauth",
    "provider_name": "google",
    "config": {
      "client_id": "test-client-id",
      "client_secret": "test-client-secret"
    }
  }'

Response (200 OK):

{
  "success": true,
  "message": "Connection test successful"
}

12. Get Recent Configurations

Endpoint: GET /api/v1/admin/configuration/recent

Authentication: Superadmin required

Query Parameters:

  • limit: Number of results (default: 10)

Email Template Management

Admin API for managing email templates and viewing email logs.

1. List Email Templates

Endpoint: GET /api/v1/admin/email/templates

Authentication: Superadmin required

Query Parameters:

ParameterTypeDefaultDescription
typestrnullFilter by template type
enabledboolnullFilter by enabled status
searchstrnullSearch in name/description

Request:

curl -X GET "http://localhost:8000/api/v1/admin/email/templates" \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "items": [
    {
      "id": "email_verify_abc123",
      "type": "email_verification",
      "name": "Email Verification",
      "description": "Sent to users to verify their email address",
      "subject": "Verify Your Email Address",
      "enabled": true,
      "created_at": "2025-12-24T22:00:00Z",
      "updated_at": "2025-12-24T22:00:00Z"
    },
    {
      "id": "password_reset_def456",
      "type": "password_reset",
      "name": "Password Reset",
      "description": "Sent when user requests password reset",
      "subject": "Reset Your Password",
      "enabled": true,
      "created_at": "2025-12-24T22:00:00Z",
      "updated_at": "2025-12-24T22:00:00Z"
    }
  ],
  "total": 2
}

2. Get Single Email Template

Endpoint: GET /api/v1/admin/email/templates/{template_id}

Authentication: Superadmin required

Request:

curl -X GET http://localhost:8000/api/v1/admin/email/templates/email_verify_abc123 \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "id": "email_verify_abc123",
  "type": "email_verification",
  "name": "Email Verification",
  "description": "Sent to users to verify their email address",
  "subject": "Verify Your Email Address",
  "html_content": "<html><body><h1>Verify Your Email</h1>...</body></html>",
  "text_content": "Verify Your Email\n\nClick the link below...",
  "enabled": true,
  "variables": ["verification_link", "user_email", "account_name"],
  "created_at": "2025-12-24T22:00:00Z",
  "updated_at": "2025-12-24T22:00:00Z"
}

3. Update Email Template

Endpoint: PUT /api/v1/admin/email/templates/{template_id}

Authentication: Superadmin required

Request:

curl -X PUT http://localhost:8000/api/v1/admin/email/templates/email_verify_abc123 \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "subject": "Please Verify Your Email Address",
    "html_content": "<html><body><h1>Email Verification Required</h1>...</body></html>",
    "text_content": "Email Verification Required\n\nPlease click...",
    "enabled": true
  }'

Response (200 OK):

{
  "id": "email_verify_abc123",
  "message": "Template updated successfully"
}

Available Template Variables:

  • email_verification: {{verification_link}}, {{user_email}}, {{account_name}}
  • password_reset: {{reset_link}}, {{user_email}}, {{account_name}}
  • invitation: {{invitation_link}}, {{inviter_email}}, {{account_name}}

4. Render Template (Preview)

Endpoint: POST /api/v1/admin/email/templates/render

Authentication: Superadmin required

Request:

curl -X POST http://localhost:8000/api/v1/admin/email/templates/render \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "email_verification",
    "variables": {
      "verification_link": "https://example.com/verify?token=abc123",
      "user_email": "user@example.com",
      "account_name": "Acme Corp"
    }
  }'

Response (200 OK):

{
  "subject": "Verify Your Email Address",
  "html_content": "<html><body>...</html>",
  "text_content": "Verify Your Email\n\nClick: https://example.com/verify?token=abc123"
}

Note: This endpoint renders the template without sending an email.


5. Send Test Email

Endpoint: POST /api/v1/admin/email/templates/{template_id}/test

Authentication: Superadmin required

Request:

curl -X POST http://localhost:8000/api/v1/admin/email/templates/email_verify_abc123/test \
  -H "Authorization: Bearer <superadmin_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "recipient_email": "test@example.com",
    "variables": {
      "verification_link": "https://example.com/verify?token=test123",
      "user_email": "test@example.com",
      "account_name": "Test Account"
    }
  }'

Response (200 OK):

{
  "success": true,
  "message": "Test email sent successfully to test@example.com"
}

6. List Email Logs

Endpoint: GET /api/v1/admin/email/logs

Authentication: Superadmin required

Query Parameters:

ParameterTypeDefaultDescription
skipint0Pagination offset
limitint50Results per page (max 100)
template_typestrnullFilter by template type
statusstrnullFilter by status (sent, failed, pending)
recipient_emailstrnullFilter by recipient
from_datedatetimenullISO 8601 start date
to_datedatetimenullISO 8601 end date

Request:

curl -X GET "http://localhost:8000/api/v1/admin/email/logs?skip=0&limit=50" \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "items": [
    {
      "id": "log_abc123",
      "template_id": "email_verify_abc123",
      "template_type": "email_verification",
      "recipient_email": "user@example.com",
      "account_id": "AB1234",
      "status": "sent",
      "error_message": null,
      "sent_at": "2025-12-24T22:00:00Z",
      "created_at": "2025-12-24T22:00:00Z"
    }
  ],
  "total": 1,
  "skip": 0,
  "limit": 50
}

7. Get Single Email Log

Endpoint: GET /api/v1/admin/email/logs/{log_id}

Authentication: Superadmin required

Request:

curl -X GET http://localhost:8000/api/v1/admin/email/logs/log_abc123 \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "id": "log_abc123",
  "template_id": "email_verify_abc123",
  "template_type": "email_verification",
  "template_name": "Email Verification",
  "recipient_email": "user@example.com",
  "subject": "Verify Your Email Address",
  "account_id": "AB1234",
  "account_name": "Acme Corporation",
  "status": "sent",
  "error_message": null,
  "sent_at": "2025-12-24T22:00:00Z",
  "created_at": "2025-12-24T22:00:00Z"
}

Status Values:

  • pending: Queued for sending
  • sent: Successfully sent
  • failed: Failed to send (check error_message)

Audit Logs

Audit logging for GxP compliance. Logs are immutable and PII is masked based on group membership.

1. List Audit Logs

Endpoint: GET /api/v1/audit-logs

Authentication: Superadmin required

Query Parameters:

ParameterTypeDefaultConstraints
skipint0>= 0
limitint50>= 1, <= 100
account_idstrnullFilter by account
table_namestrnullFilter by table/collection
record_idstrnullFilter by record ID
user_idstrnullFilter by user
operationstrnullFilter by operation
from_datedatetimenullISO 8601 format
to_datedatetimenullISO 8601 format
sort_bystr"timestamp"Field name
sort_orderstr"desc"asc, desc

Request:

curl -X GET "http://localhost:8000/api/v1/audit-logs?skip=0&limit=50&sort_order=desc" \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "items": [
    {
      "id": 1,
      "account_id": "AB1234",
      "table_name": "col_posts",
      "record_id": "rec_abc123",
      "operation": "UPDATE",
      "user_id": "usr_abc123",
      "user_email": "admin@acme.com",
      "old_values": { "title": "Old Title" },
      "new_values": { "title": "New Title" },
      "changed_fields": ["title"],
      "ip_address": "192.168.1.100",
      "user_agent": "Mozilla/5.0...",
      "timestamp": "2025-12-24T22:00:00Z"
    }
  ],
  "total": 1,
  "skip": 0,
  "limit": 50
}

Note: PII fields are masked for users without pii_access group.


2. Get Single Audit Log

Endpoint: GET /api/v1/audit-logs/{log_id}

Authentication: Superadmin required


3. Export Audit Logs

Endpoint: GET /api/v1/audit-logs/export

Authentication: Superadmin required

Query Parameters:

  • format: csv or json (default: csv)
  • Same filters as list endpoint

Request:

curl -X GET "http://localhost:8000/api/v1/audit-logs/export?format=csv&from_date=2025-12-01T00:00:00Z" \
  -H "Authorization: Bearer <superadmin_token>" \
  -o audit_logs_export.csv

Response: File download (CSV or JSON)


Migrations

Database migration management using Alembic.

1. List Migrations

Endpoint: GET /api/v1/migrations

Authentication: Superadmin required

Response (200 OK):

{
  "items": [
    {
      "revision": "abc123def456",
      "down_revision": null,
      "branch_labels": null,
      "depends_on": null,
      "message": "Initial migration",
      "is_head": true,
      "is_baseline": true,
      "is_branch_point": false,
      "migration_type": "upgrade"
    }
  ],
  "total": 1
}

2. Get Current Revision

Endpoint: GET /api/v1/migrations/current

Authentication: Superadmin required

Response (200 OK):

{
  "current_revision": "abc123def456",
  "current_version": "1.0.0",
  "is_head": true
}

3. Get Migration History

Endpoint: GET /api/v1/migrations/history

Authentication: Superadmin required

Response (200 OK):

{
  "history": [
    {
      "revision": "abc123def456",
      "message": "Initial migration",
      "applied_at": "2025-12-24T22:00:00Z",
      "duration_seconds": 0.5
    }
  ]
}

Dashboard

Get Dashboard Statistics

Endpoint: GET /api/v1/dashboard/stats

Authentication: Superadmin required

Request:

curl -X GET http://localhost:8000/api/v1/dashboard/stats \
  -H "Authorization: Bearer <superadmin_token>"

Response (200 OK):

{
  "total_accounts": 10,
  "total_users": 45,
  "total_collections": 12,
  "total_records": 1523,
  "new_accounts_7d": 2,
  "new_users_7d": 8,
  "recent_registrations": [
    {
      "id": "usr_new123",
      "email": "user@example.com",
      "account_id": "AB1234",
      "account_code": "AB1234",
      "account_name": "Acme Corporation",
      "created_at": "2025-12-24T22:00:00Z"
    }
  ],
  "system_health": {
    "database_status": "connected",
    "storage_usage_mb": 45.2
  },
  "active_sessions": 15,
  "recent_audit_logs": []
}

Health Checks

Health check endpoints have no authentication requirement.

1. Basic Health Check

Endpoint: GET /health

Response (200 OK):

{
  "status": "healthy",
  "service": "SnackBase",
  "version": "0.1.0"
}

2. Readiness Check

Endpoint: GET /ready

Response (200 OK):

{
  "status": "ready",
  "service": "SnackBase",
  "version": "0.1.0",
  "database": "connected"
}

Error Response (503 Service Unavailable):

{
  "status": "not_ready",
  "service": "SnackBase",
  "version": "0.1.0",
  "database": "disconnected"
}

3. Liveness Check

Endpoint: GET /live

Response (200 OK):

{
  "status": "alive",
  "service": "SnackBase",
  "version": "0.1.0"
}

Error Handling

HTTP Status Codes

CodeMeaningExample
200OKSuccessful GET, PUT, PATCH
201CreatedSuccessful POST
204No ContentSuccessful DELETE
400Bad RequestValidation error
401UnauthorizedMissing or invalid token
403ForbiddenInsufficient permissions
404Not FoundResource doesn't exist
409ConflictDuplicate resource
422Unprocessable EntityInvalid data format
500Internal Server ErrorServer error

Error Response Format

{
  "error": "Error type",
  "message": "Error message describing what went wrong"
}

Validation Error Response

{
  "error": "Validation error",
  "details": [
    {
      "field": "password",
      "message": "Password must be at least 12 characters...",
      "code": "password_too_weak"
    }
  ]
}

Field Access Denied Response

{
  "error": "Field access denied",
  "message": "Permission denied to update fields: salary, ssn",
  "unauthorized_fields": ["salary", "ssn"],
  "allowed_fields": ["name", "email"],
  "field_type": "restricted"
}

Best Practices

1. Always Use HTTPS in Production

# Bad (production)
http://api.yourdomain.com/api/v1/records/posts

# Good (production)
https://api.yourdomain.com/api/v1/records/posts

2. Store Tokens Securely

// Bad - localStorage is vulnerable to XSS
localStorage.setItem("token", token);

// Good - httpOnly cookie (server-side)
// Or use secure session storage with proper CSP

3. Handle Token Expiration

// Refresh token before expiration
async function makeRequest(url) {
  let token = getAccessToken();

  // Check if token is about to expire
  if (isTokenExpiringSoon(token)) {
    token = await refreshAccessToken();
  }

  return fetch(url, {
    headers: { Authorization: `Bearer ${token}` },
  });
}

4. Use Pagination for Large Datasets

# Bad - fetching too many records
curl -X GET http://localhost:8000/api/v1/records/posts?limit=1000

# Good - paginate results
curl -X GET "http://localhost:8000/api/v1/records/posts?skip=0&limit=30"
curl -X GET "http://localhost:8000/api/v1/records/posts?skip=30&limit=30"

5. Use Field Limiting

# Bad - fetching all fields when you only need a few
curl -X GET http://localhost:8000/api/v1/records/posts

# Good - limit to needed fields
curl -X GET "http://localhost:8000/api/v1/records/posts?fields=id,title,created_at"

6. Add Correlation IDs

# Add X-Correlation-ID for request tracing
curl -X POST http://localhost:8000/api/v1/records/posts \
  -H "Authorization: Bearer <token>" \
  -H "X-Correlation-ID: req_abc123" \
  -d '{...}'

7. Handle Errors Gracefully

async function createPost(data) {
  try {
    const response = await fetch("/api/v1/records/posts", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify(data),
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message || error.detail);
    }

    return await response.json();
  } catch (error) {
    console.error("Failed to create post:", error.message);
    // Show user-friendly error message
  }
}

8. Use Appropriate HTTP Methods

  • GET: Retrieve data (idempotent, cacheable)
  • POST: Create new resource
  • PUT: Replace entire resource (idempotent)
  • PATCH: Update partial resource
  • DELETE: Remove resource (idempotent)

9. Remember Route Registration Order

CRITICAL: The records_router (for /api/v1/records/{collection}) MUST be registered LAST in your FastAPI app. This prevents it from capturing specific routes like /invitations, /collections, /accounts, etc.

# Correct order in app.py
app.include_router(invitations_router, prefix="/api/v1/invitations", tags=["invitations"])
app.include_router(collections_router, prefix="/api/v1/collections", tags=["collections"])
app.include_router(accounts_router, prefix="/api/v1/accounts", tags=["accounts"])
# ... all other specific routers ...
app.include_router(records_router, prefix="/api/v1/records", tags=["records"])  # MUST BE LAST

10. Verify Emails Before Login

Always implement email verification in your registration flow:

  1. User registers → NO TOKENS returned, just message
  2. User receives verification email
  3. User clicks verification link or enters token
  4. Email verified → User can now login
  5. Login checks email_verified field, returns 401 if not verified

Rate Limiting (Future)

Rate limiting will be added in a future phase. Recommended limits:

  • Authenticated requests: 1000 requests/hour
  • Unauthenticated requests: 100 requests/hour
  • Burst limit: 20 requests/second

SDK Examples (Future)

Official SDKs will be provided in future releases:

  • JavaScript/TypeScript: @snackbase/client
  • Python: snackbase-client
  • Go: github.com/snackbase/client-go

Support