Authentication
Secure your Hub API with optional API key authentication. Enable it in production with a single environment variable.
How It Works
Hub runs without authentication by default for easy local development. When you set SERVICE_API_KEY, all API endpoints require the X-API-Key header - except health checks and documentation, which remain public.
Enabling API Key Authentication
1. Set Environment Variable
Add SERVICE_API_KEY to your .env file:
SERVICE_API_KEY=your-secret-key-here
Generate a cryptographically secure key:
# macOS/Linux
openssl rand -base64 32
# Or use a password manager's random generator
2. Restart the Service
# Stop the service (Ctrl+C)
# Then restart:
make dev
You should see in the logs:
ℹ️ API key authentication enabled
3. Include Key in Requests
All API requests (except /health and /docs) now require the X-API-Key header:
curl -H "X-API-Key: your-secret-key-here" \
http://localhost:8080/v1/experiences
Protected Endpoints
When API key auth is enabled, these endpoints require authentication:
POST /v1/experiences- Create experienceGET /v1/experiences- List experiencesGET /v1/experiences/{id}- Get experiencePATCH /v1/experiences/{id}- Update experienceDELETE /v1/experiences/{id}- Delete experienceGET /v1/experiences/search- Semantic search
Always public (no auth required):
GET /health- Health checkGET /docs- API documentationGET /openapi.json- OpenAPI spec
Authentication Workflow
Request Flow
graph TD
A[Client Request] --> B{Has X-API-Key?}
B -->|No| C[401 Unauthorized]
B -->|Yes| D{Key Matches?}
D -->|No| E[401 Unauthorized]
D -->|Yes| F[Process Request]
F --> G[200 Success]
Example Requests
Without authentication (enabled):
curl http://localhost:8080/v1/experiences
Response:
{
"title": "Unauthorized",
"status": 401,
"detail": "Missing or invalid API key"
}
With authentication:
curl -H "X-API-Key: your-secret-key-here" \
http://localhost:8080/v1/experiences
Response:
{
"data": [...],
"total": 10,
...
}
Production Deployment
Hub's built-in API key authentication is ideal for:
- ✅ Internal services and tools
- ✅ Server-to-server communication
- ✅ Quick production deployments
For public APIs or multi-tenant systems, consider deploying behind an API gateway (AWS API Gateway, Kong, Traefik) for advanced features like OAuth, JWT, or per-user access control.
Docker Deployment with Auth
docker run -d \
-p 8080:8080 \
-e SERVICE_DATABASE_URL=postgres://... \
-e SERVICE_API_KEY=your-secret-key-here \
ghcr.io/formbricks/hub:latest
Environment Variables
# Required
SERVICE_DATABASE_URL=postgres://user:pass@host/db
# Optional (but recommended for production)
SERVICE_API_KEY=your-secret-key-here
SERVICE_LOG_LEVEL=info
SERVICE_ENVIRONMENT=production
See full environment variable reference →
Security Best Practices
1. Use HTTPS in Production
Always use HTTPS to encrypt API keys in transit:
❌ http://api.example.com (insecure)
✅ https://api.example.com (secure)
2. Store Keys Securely
✅ Good practices:
- Use environment variables (not hardcoded)
- Store in secret management (AWS Secrets Manager, HashiCorp Vault)
- Rotate keys regularly
❌ Avoid:
- Committing keys to Git
- Sharing keys via email/Slack
- Using the same key for dev and prod
3. Rotate Keys Regularly
Schedule regular key rotation (e.g., every 90 days):
- Generate new key
- Update
SERVICE_API_KEYin production - Restart service
- Update clients with new key
- Revoke old key
4. Monitor for Unauthorized Access
Enable structured logging to track failed auth attempts:
SERVICE_LOG_LEVEL=info
Look for 401 responses in logs:
{
"level": "warn",
"msg": "unauthorized request",
"path": "/v1/experiences",
"ip": "203.0.113.1"
}
5. Rate Limiting
Hub includes built-in rate limiting to protect against abuse:
- Per-IP limits: Default 100 requests/second per IP address
- Global limits: Default 1000 requests/second across all IPs
- Configurable: Adjust via
SERVICE_RATE_LIMIT_*environment variables
Learn more about rate limiting configuration →
Disabling Authentication
To disable auth for local development:
-
Remove or comment out
SERVICE_API_KEYin.env:# SERVICE_API_KEY=your-key -
Restart:
make dev
Logs will show:
ℹ️ API key authentication disabled
Troubleshooting
401 Unauthorized Error
Symptom:
{
"title": "Unauthorized",
"status": 401,
"detail": "Missing or invalid API key"
}
Solutions:
-
Check if auth is enabled:
grep SERVICE_API_KEY .env -
Verify header syntax:
# Correct
curl -H "X-API-Key: your-key" http://...
# Wrong (lowercase 'k')
curl -H "X-API-key: your-key" http://... -
Check key matches
.env:echo $SERVICE_API_KEY
Health Check Returns 401
Health checks (/health) should never require auth.
If you see this, it's a bug. Please report it.
Docs Page Returns 401
The /docs endpoint should always be public.
If you see this, it's a bug. Please report it.
Examples
cURL
curl -H "X-API-Key: abc123" \
-X POST http://localhost:8080/v1/experiences \
-H "Content-Type: application/json" \
-d '{"source_type":"survey",...}'
JavaScript (Node.js)
const response = await fetch('http://localhost:8080/v1/experiences', {
headers: {
'X-API-Key': 'abc123',
'Content-Type': 'application/json',
},
method: 'POST',
body: JSON.stringify({
source_type: 'survey',
field_id: 'q1',
field_type: 'text',
value_text: 'Great!',
}),
});
Python
import requests
headers = {
'X-API-Key': 'abc123',
'Content-Type': 'application/json',
}
response = requests.get(
'http://localhost:8080/v1/experiences',
headers=headers
)
Go
client := &http.Client{}
req, _ := http.NewRequest("GET", "http://localhost:8080/v1/experiences", nil)
req.Header.Set("X-API-Key", "abc123")
resp, err := client.Do(req)
Next Steps
- Webhooks - React to data changes
- API Reference - Explore endpoints
- Environment Variables - Configuration reference