API Reference — Overview & Conventions
Everything that applies to every endpoint, so the per-endpoint pages stay short.
Base URL
Production: https://accounts.camelcreatives.com
Local dev: http://localhost:8080The response envelope
Almost every endpoint returns:
{
"success": true,
"data": { "...": "..." },
"message": "optional human-readable note",
"errors": ["optional detail strings"]
}| Field | Present when |
|---|---|
success | Always |
data | Success responses with a payload (omitempty) |
message | Both success confirmations and errors (omitempty) |
errors | Error details (omitempty) |
Exempt endpoints (raw spec JSON)
These return their own format instead of the envelope so standard OAuth libraries work unmodified:
POST /oauth/token— OAuth2 token JSONGET /.well-known/openid-configuration— OIDC discoveryGET /oauth/jwks— JWKSGET /oauth/userinfo— plain claims map
Status codes in use
| Code | Meaning here |
|---|---|
| 200 | OK (also used for login-with-MFA challenge) |
| 201 | Created (register, project/client create) |
| 400 | Validation failure — see message/errors for the reason |
| 401 | Not authenticated / bad credentials / bad bearer or MFA code |
| 403 | Forbidden (e.g. archived project) |
| 404 | Unknown resource (ownership-checked — foreign resources look identical to missing ones) |
| 409 | Conflict (account already exists) |
| 429 | Rate limited: "too many requests, try again later" |
| 500 | Internal failure |
Request formats
- JSON bodies: send
Content-Type: application/json - Exception:
/oauth/tokenrequiresapplication/x-www-form-urlencoded
Cross-cutting behavior
Request IDs
Every response carries an X-Request-Id header (echoes yours if provided, otherwise generates one). Include it in bug reports.
CORS
Enabled per-origin by server config (CORS_ALLOWED_ORIGINS). Allowed origins receive:
Access-Control-Allow-Origin: <your origin>
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POST, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-Id
Access-Control-Max-Age: 86400Preflight OPTIONS → 204 No Content. Note PUT is not allowed — use PATCH. If CORS is not configured on the deployment, browser calls from other origins will fail even though server-to-server calls work.
Timeouts & limits
Per-request timeout of 30 s ({"success":false,"message":"request timed out"}), gzip compression on, request body limit ~1 MiB where applicable. There is no pagination anywhere: list endpoints return full arrays (security events capped at newest 100; audit logs at newest 100).
Rate limits
Redis fixed-window counters keyed by client IP (first X-Forwarded-For hop if present). If Redis is down, limiting fails open — requests are served.
| Limiter | Budget | Applies to |
|---|---|---|
| register | 5 / min / IP | POST /auth/register |
| login | 10 / min / IP | POST /auth/login |
| social-login | 10 / min / IP | GET /auth/social/{provider}/begin |
| token | 30 / min / IP | POST /oauth/token |
| otp-send | 3 / hour / IP | verify-email/send · verify-phone/send · passkeys/login/begin |
| admin-login | 10 / min / IP | admin login |
| console-project-create | 5 / hour / IP | POST /console/projects |
| console-client-create | 20 / hour / IP | POST /console/projects/{id}/clients |
Plus service-level caps independent of IP: max 3 OTP sends/hour per user per channel, max 5 verification attempts per OTP.
Not rate-limited: forgot/reset-password, mfa/*, logout, security-events, profile/connections/sessions reads, console reads.
Authentication models
| Model | Mechanism | Used by |
|---|---|---|
| Browser session | camel_session HttpOnly cookie (30-day sliding) | All /auth/*, consent, revoke, connections, sessions, console |
| Bearer token | RS256 JWT via Authorization: Bearer … (15 min) | /oauth/userinfo; also accepted by /profile |
401 envelope messages you'll meet: "not authenticated" (no cookie), "session expired or invalid" (bad cookie), "invalid or expired access token" (bearer).
Health
curl -s "$ISSUER/healthz"
# {"success":true,"data":{"status":"ok"}}