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

  1. Go to Admin → API Keys → New.
  2. Name the key, pick its scopes, set allowed origins and rate limit.
  3. Save. The secret is shown once on the success page — copy it to your client's secret store immediately.

Scopes

ScopeGrants
readGET endpoints (always allowed by default)
writePOST, PUT, PATCH endpoints
deleteDELETE 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

StatusCodeWhen
401UNAUTHENTICATEDMissing headers
401INVALID_KEYKey not found / secret mismatch
401KEY_INACTIVEis_active = false
401KEY_EXPIREDexpires_at in the past
403ORIGIN_NOT_ALLOWEDCORS allowlist mismatch
403SCOPE_REQUIREDMissing scope for the endpoint
429RATE_LIMITEDExceeded 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: