API Reference
Bulwark exposes a REST API over HTTPS. All request and response bodies are JSON.
Base URL
https://api.bulwarkauth.com/api/v1
For self-hosted instances:
https://your-domain.com/api/v1
Authentication
API Key (server-to-server)
Authorization: Bearer bwk_live_<key>
User JWT (client requests)
Authorization: Bearer eyJ...
Agent Biscuit Token
Authorization: Bearer En0KH...
Required and Optional Headers
Tenant Header
All requests must include the tenant header to identify your workspace:
X-Bulwark-Tenant: <tenant-id>
Application Header
To scope results to a specific application, include the optional application header:
X-Bulwark-App-Id: <app-id>
When present, list endpoints such as sessions and audit events return only records associated with the specified application. If omitted, results include records from all applications in the workspace.
Example with both headers
curl https://api.bulwarkauth.com/api/v1/audit \
-H "Authorization: Bearer bwk_live_..." \
-H "X-Bulwark-Tenant: tenant_01j..." \
-H "X-Bulwark-App-Id: app_01j..."
Response Format
Success
{
"data": { ... },
"meta": {
"requestId": "req_01j..."
}
}
Error
{
"error": {
"code": "INVALID_CREDENTIALS",
"message": "Email or password is incorrect",
"requestId": "req_01j..."
}
}
Common Error Codes
| Code | HTTP | Description |
|------|------|-------------|
| INVALID_CREDENTIALS | 401 | Wrong email/password |
| TOKEN_EXPIRED | 401 | JWT or Biscuit token expired |
| INSUFFICIENT_SCOPE | 403 | Token lacks required scope |
| NOT_FOUND | 404 | Resource not found |
| RATE_LIMITED | 429 | Too many requests |
| TENANT_REQUIRED | 400 | Missing X-Bulwark-Tenant header |
Rate Limits
| Plan | Requests/min | |------|--------------| | Free | 60 | | Pro | 600 | | Enterprise | Custom |
Rate limit headers returned on every response:
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 598
X-RateLimit-Reset: 1709558400
Pagination
List endpoints support cursor-based pagination:
GET /api/v1/agents?limit=20&cursor=<cursor>
Response includes:
{
"data": [...],
"pagination": {
"cursor": "next_cursor_value",
"hasMore": true
}
}