Authentication & Scopes
All non-public endpoints require a public key + secret pair. Scopes restrict what each key can do.
Headers
X-API-Key: pk_live_… (public identifier — stored plaintext)
X-API-Secret: sk_live_… (secret — bcrypt-hashed at rest)
Origin: https://your-frontend.com (matched against allowed_origins)
Creating a key
- Go to Admin → API Keys → New.
- Name the key, pick its scopes, set allowed origins and rate limit.
- Save. The secret is shown once on the success page — copy it to your client's secret store immediately.
Scopes
| Scope | Grants |
|---|---|
read | GET endpoints (always allowed by default) |
write | POST, PUT, PATCH endpoints |
delete | DELETE endpoints |
Scope enforcement is per-route via the api.scope middleware:
Route::post('upload', …)->middleware('api.scope:write');
Route::delete('upload/files/{id}', …)->middleware('api.scope:delete');
Route::post('{model}', …)->middleware('api.scope:write');
Route::put('{model}/{id}', …)->middleware('api.scope:write');
Route::delete('{model}/{id}', …)->middleware('api.scope:delete');
CORS
The api.cors middleware checks Origin against each key's allowed_origins JSON array. The array may contain explicit origins, wildcards (*), or subdomain wildcards (https://*.example.com). Preflight (OPTIONS) is auto-handled.
Rate limiting
The api.rate middleware enforces a sliding window of api_keys.rate_limit requests per minute, keyed by the API key UUID. Exceeded requests get:
HTTP/1.1 429 Too Many Requests
Retry-After: 12
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1716981234
Errors
| Status | Code | When |
|---|---|---|
| 401 | UNAUTHENTICATED | Missing headers |
| 401 | INVALID_KEY | Key not found / secret mismatch |
| 401 | KEY_INACTIVE | is_active = false |
| 401 | KEY_EXPIRED | expires_at in the past |
| 403 | ORIGIN_NOT_ALLOWED | CORS allowlist mismatch |
| 403 | SCOPE_REQUIRED | Missing scope for the endpoint |
| 429 | RATE_LIMITED | Exceeded per-key window |
Health check
GET /api/v1/ping
Confirms the key works and echoes the key name + active scopes — useful for client startup diagnostics.
{
"data": {
"message": "pong",
"key_name": "Website Production",
"scopes": ["read", "write"]
},
"error": null
}
Public (unauthenticated) endpoints
The following do not require headers — they are intentionally open for frontends to consume directly:
GET /api/v1/i18n/localesGET /api/v1/settingsGET /api/v1/forms/{slug}POST /api/v1/forms/{slug}/submit(rate-limited 5/min/IP)