mobieusCore API — Authentication, scopes, rate limits
Authentication, scopes, and rate limits
API keys
Every API request must send an Authorization: Bearer <key> header. Keys look like:
mc_live_YJs9gvYaRhL-6An-hdHlmQb0pTqfGC38hbAT3WwGrs4
mc_test_abcdefghijklmnopqrstuvwxyz0123456789ABCD
The prefix tells you (and any code reviewer) whether the key is live or test data.
How keys are stored
- Mobieus stores only a SHA-256 hash of the key — the plaintext is shown once at creation and is unrecoverable.
- Lookup uses constant-time comparison so attackers can't grind hashes by timing.
- The first ~12 characters are stored separately so the admin UI can identify the key by prefix without ever seeing the secret.
Managing keys
Tenant super-admins manage keys at /admin/api-keys. Settings live at /admin/api/settings.
| Action | What happens |
|---|---|
| Create key | Generates a fresh key. The plaintext is shown once on the next page — copy it now, you cannot recover it. |
| Edit | Change the label or set a per-key rate-limit override. |
| Revoke | Immediate. The next request with that key returns 401 invalid_api_key. Not reversible. |
| Expire | Optional expires_at. Keys past their expiry start returning 401 automatically. |
Every create, edit, and revoke is audit-logged.
Scopes
Every key has one or more scopes. Endpoints check the scope before processing — a key without events:read calling GET /api/v1/events gets 403 insufficient_scope.
Core, webhooks, marketplace, files, moderation
| Scope | Lets the key… |
|---|---|
events:read |
Read /api/v1/events. |
users:read |
Read /api/v1/users and /api/v1/users/{id}. |
posts:read |
Read /api/v1/posts and /api/v1/posts/{id}. |
listings:read |
Read /api/v1/listings and /api/v1/listings/{id}. |
files:read |
Read /api/v1/files and /api/v1/files/{id}. |
reports:read |
Read /api/v1/reports, /reports/{id}, and /moderation/actions. |
reports:manage |
Dismiss or resolve reports via POST /api/v1/reports/{id}/dismiss and /resolve. |
webhooks:read |
List webhook endpoints + delivery history. |
webhooks:manage |
Create / update / delete webhook endpoints. |
mobieusHelp
| Scope | Lets the key… |
|---|---|
helpdesk:read |
Read tickets, queues, agents, canned responses, tags, help topics, notification prefs, and the read-side AI hooks. |
helpdesk:write |
Create + update tickets, reply, change status, manage canned responses + tags, and the write-side AI hooks. |
helpdesk:admin |
Set notification prefs, read the helpdesk audit log, and the admin-only AI hooks. |
mobieusLearn
| Scope | Lets the key… |
|---|---|
learn:read |
Read every Learn resource (courses, enrollments, attempts, certificates, templates, SCORM packages). |
learn:write |
Create / update / publish / archive courses, enroll users, cancel enrollments, revoke + regenerate certificates. |
learn:cohorts:grant |
Grant cohort access via POST /api/v1/learn/cohorts/grant. |
learn:xapi:read |
Query the native xAPI statement store. |
learn:xapi:write |
Store xAPI statements. |
mobieusKnow
| Scope | Lets the key… |
|---|---|
know:read |
List pages, get a page or revision, run search. |
know:write |
Create / update / delete pages, approve / reject revisions. |
Principle of least privilege: only grant the scopes the integration actually needs.
Plan gate
The public REST API is gated to Pro, Creator Plus, and Sovereign tenants. Tenants below Pro get 403 plan_gated:
{ "error": { "code": "plan_gated",
"message": "The public REST API is available on Pro and higher plans. Upgrade in /admin/billing.",
"current_plan": "starter",
"required_plans": ["pro", "creator-plus", "sovereign"],
"request_id": "req_…" } }
Upgrade in /admin/billing and the API surface lights up immediately — no key re-mint needed.
Tenant isolation
A key is bound to the tenant it was minted in. A mc_live_… key from one community will not authenticate against another, and there is no API surface to query a different tenant's data with it. Tenant scoping happens at the database connection layer, so cross-tenant queries are structurally impossible.
Rate limits
Three layers, evaluated in order:
- Per-key override —
api_keys.rate_limit_per_minuteset on/admin/api-keys/{id}/edit. Highest precedence. - Tenant default —
api_settings.default_rate_limit_per_minuteset on/admin/api/settings. Applies when the per-key override is blank. - Platform default —
api.rate_limit_per_minuteinapp.ini(currently 600). Fallback when both overrides are blank.
Every response carries:
X-RateLimit-Limit— the ceiling actually applied to this requestX-RateLimit-Remaining— what's left in the current 60-second windowX-RateLimit-Reset— Unix timestamp when the window resets
On exceed you get 429 Too Many Requests plus a Retry-After: <seconds> header. Back off, then retry.
Request IDs
Every response carries X-Request-Id: req_xxxxxxxxxxxxxxxx. The same id appears in the JSON body as request_id. Include it whenever you contact support — it lets us correlate your call with the server log instantly.
Error envelope
Every error response uses the same JSON shape:
{
"error": {
"code": "invalid_api_key",
"message": "The API key is invalid, revoked, or expired.",
"request_id": "req_3f2b8d9c10ab7c92"
}
}
Common codes:
| HTTP | error.code |
Meaning |
|---|---|---|
| 401 | missing_authorization |
No Authorization header. |
| 401 | invalid_authorization |
Header wasn't a Bearer token. |
| 401 | invalid_api_key |
Bad key, revoked, or expired. |
| 403 | plan_gated |
Tenant is on a plan below Pro. |
| 403 | insufficient_scope |
Key valid but missing the required scope. |
| 400 | invalid_since / invalid_until |
Date filter wasn't ISO-8601. |
| 400 | already_resolved |
POST /reports/{id}/dismiss or /resolve on a report that is already resolved. |
| 404 | user_not_found / post_not_found / listing_not_found / file_not_found / report_not_found |
Resource doesn't exist or isn't visible. |
| 429 | rate_limited |
Slow down — see Retry-After. |
| 500 | internal_error |
Server error. Include the request_id when reporting. |
Discussion
Sign in to start the discussion.