mobieusCore API — Authentication, scopes, rate limits

6 min read · 1,169 words · 5 revisions · Updated Jul 28, 2026
#api #developers #security

Authentication, scopes, and rate limits

API keys

Every API request must send an Authorization: Bearer <key> header. Keys look like:

mc_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
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.

mobieusHealth

Scope Lets the key…
health:read Read the key owner's own mobieusHealth record — daily summary, weight, vitals, water, and activity.
health:write Log the key owner's own weight, vitals, and water.

A mobieusHealth key only ever reads or writes its own owner's data. Across every /api/v1/health/* endpoint there is no parameter — path, query, or body — that names another member, and the owner must hold an active mobieusHealth seat or the call returns 403 health_no_access. Full reference: mobieusCore API — mobieusHealth. Added in 1.19.0.

One deliberate exception, and it is worth understanding before you build against it: GET /api/v1/buddy/{ownerId}/data/{category} returns another member's health data. It is gated by that member's own explicit sharing grant — no grant, 403 not_shared — the read is audited, and the response is shaped to the detail level they granted. health:read is required. See mobieusBuddy & mobieusChallenge.

Client telemetry

Scope Lets the key…
analytics:write Submit first-party client telemetry (POST /api/v1/analytics/events).

This exists for the mobile apps, which send it on every session. An integration almost never needs it, and it is not granted by default. Added in 1.23.0; before that the endpoint required no scope at all.

Principle of least privilege: only grant the scopes the integration actually needs.

Response headers

Every /api/v1 response carries two headers worth logging:

Header Meaning
X-Request-Id Unique per request. Quote it in a support ticket and we can find the exact call.
X-API-Version The API's semantic version, e.g. 1.23.0. New in 1.23.0 — before that the API reported no version at all, so a client had no way to ask what it was talking to.

X-API-Version tracks info.version in the OpenAPI spec. Pin against it if you want to detect a change in the surface you built on.

Plan gate

The public REST API is gated to Pro, Creator, 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", "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:

  1. Per-key overrideapi_keys.rate_limit_per_minute set on /admin/api-keys/{id}/edit. Highest precedence.
  2. Tenant defaultapi_settings.default_rate_limit_per_minute set on /admin/api/settings. Applies when the per-key override is blank.
  3. Platform defaultapi.rate_limit_per_minute in app.ini (currently 600). Fallback when both overrides are blank.

Every response carries:

  • X-RateLimit-Limit — the ceiling actually applied to this request
  • X-RateLimit-Remaining — what's left in the current 60-second window
  • X-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.
Contributors:
S
Last edited by mobieus · API reconciliation to 1.23.0 — full surface coverage, corrected plan gate, X-API-Version
Created May 28, 2026
Was this article helpful?