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
- Authentication
- API Keys
- Email Verification
- Password Reset
- Accounts Management
- Collections
- Records (CRUD)
- Roles
- Collection Rules
- Groups
- Users
- Invitations
- Macros
- OAuth Authentication
- SAML Authentication
- Realtime API
- Files
- Admin Configuration
- Email Template Management
- Audit Logs
- Migrations
- Dashboard
- Health Checks
- Error Handling
- Best Practices
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
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
- OpenAPI JSON: http://localhost:8000/openapi.json
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_verifiedfield - User must verify email before they can authenticate
Validation Rules:
account_name: 1-255 charactersaccount_slug: 3-32 characters, alphanumeric + hyphens, starts with letter (optional, auto-generated from name)email: Valid email formatpassword: 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 Case | Recommended Method |
|---|---|
| Browser applications | JWT (access/refresh tokens) |
| Mobile apps | JWT (access/refresh tokens) |
| Service-to-service calls | API Keys |
| CLI tools | API Keys |
| Webhooks | API Keys |
| Third-party integrations | API 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 prefixAB1234- 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
| Feature | API Keys | JWT Tokens |
|---|---|---|
| Use Case | Service-to-service, CLI, webhooks | Browser, mobile apps |
| Lifetime | Indefinite (until revoked) | Access: 1 hour, Refresh: 7 days |
| Storage | Server-side (hash) | Client-side (localStorage/cookie) |
| Visibility | Full key shown only at creation | Tokens visible in responses |
| Rotation | Manual (create new, revoke old) | Automatic (on refresh) |
| Revocation | Immediate | On refresh or logout |
| User Context | Single user per key | Can include user, role, account |
| Security | SHA-256 hashed at rest | Signed, 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:
| Parameter | Type | Default | Constraints |
|---|---|---|---|
page | int | 1 | >= 1 |
page_size | int | 25 | >= 1, <= 100 |
sort_by | str | "created_at" | id, account_code, slug, name, created_at |
sort_order | str | "desc" | asc, desc |
search | str | null | - |
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:
| Parameter | Type | Default | Constraints |
|---|---|---|---|
page | int | 1 | >= 1 |
page_size | int | 25 | >= 1, <= 100 |
sort_by | str | "created_at" | - |
sort_order | str | "desc" | asc, desc |
search | str | null | - |
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:
| Type | SQL Type | Description |
|---|---|---|
text | TEXT | String values |
number | REAL | Numeric values (int or float, not bool) |
boolean | INTEGER (0/1) | True/false |
datetime | DATETIME | ISO 8601 datetime strings |
email | TEXT | Email addresses (validated) |
url | TEXT | URLs (validated, must start with http:// or https://) |
json | TEXT | JSON objects |
reference | TEXT | Foreign key to another collection |
Field Options:
required: Boolean (default: false)default: Default valueunique: Boolean (default: false)options: Array of allowed values (enum)pii: Boolean - Enable PII maskingmask_type: For PII fields - email, ssn, phone, name, full, custom
Auto-Added Fields: Every collection automatically includes:
id(TEXT PRIMARY KEY) - Auto-generated UUIDaccount_id(TEXT NOT NULL) - For multi-tenancycreated_at(DATETIME) - ISO 8601 timestampcreated_by(TEXT) - User ID who created the recordupdated_at(DATETIME) - ISO 8601 timestampupdated_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
collectionandon_deleteattributes
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_byare 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_accessgroup
2. List Records
Endpoint: GET /api/v1/records/{collection}
Authentication: Required
Query Parameters:
| Parameter | Type | Default | Constraints |
|---|---|---|---|
skip | int | 0 | >= 0 |
limit | int | 30 | >= 1, <= 100 |
sort | str | "-created_at" | +/- prefix for asc/desc |
fields | str | null | Comma-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 0limit: 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:
| Parameter | Type | Default | Constraints |
|---|---|---|---|
skip | int | 0 | >= 0 |
limit | int | 25 | >= 1, <= 100 |
account_id | str | null | Filter by account |
role_id | int | null | Filter by role |
is_active | bool | null | Filter by active status |
search | str | null | Search in email |
sort | str | "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.
| Macro | Example | Expansion |
|---|---|---|
@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.0github- GitHub OAuth Appmicrosoft- Microsoft Azure ADapple- 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 IDredirect_uri(required): URL to redirect after authenticationstate(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 ADokta- Okta Identity Cloudgeneric- 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 IDprovider(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 IDprovider(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:
- Header:
Authorization: Bearer <token>(if supported by client) - 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"]
}
operationsis 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 IDcategory: 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:
| Parameter | Type | Default | Description |
|---|---|---|---|
type | str | null | Filter by template type |
enabled | bool | null | Filter by enabled status |
search | str | null | Search 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:
| Parameter | Type | Default | Description |
|---|---|---|---|
skip | int | 0 | Pagination offset |
limit | int | 50 | Results per page (max 100) |
template_type | str | null | Filter by template type |
status | str | null | Filter by status (sent, failed, pending) |
recipient_email | str | null | Filter by recipient |
from_date | datetime | null | ISO 8601 start date |
to_date | datetime | null | ISO 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 sendingsent: Successfully sentfailed: Failed to send (checkerror_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:
| Parameter | Type | Default | Constraints |
|---|---|---|---|
skip | int | 0 | >= 0 |
limit | int | 50 | >= 1, <= 100 |
account_id | str | null | Filter by account |
table_name | str | null | Filter by table/collection |
record_id | str | null | Filter by record ID |
user_id | str | null | Filter by user |
operation | str | null | Filter by operation |
from_date | datetime | null | ISO 8601 format |
to_date | datetime | null | ISO 8601 format |
sort_by | str | "timestamp" | Field name |
sort_order | str | "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:csvorjson(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
| Code | Meaning | Example |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Validation error |
| 401 | Unauthorized | Missing or invalid token |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate resource |
| 422 | Unprocessable Entity | Invalid data format |
| 500 | Internal Server Error | Server 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 resourcePUT: Replace entire resource (idempotent)PATCH: Update partial resourceDELETE: 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:
- User registers → NO TOKENS returned, just message
- User receives verification email
- User clicks verification link or enters token
- Email verified → User can now login
- Login checks
email_verifiedfield, 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
- API Documentation: http://localhost:8000/docs
- GitHub Issues: [repository]/issues
- Community: [community-link]