Skip to content
mxHeadless
REST API gateway for headless frontends on MODX 3. Resources, objects, OpenAPI, API keys, and OAuth
  1. Extras
  2. mxHeadless
  3. Authentication
  4. Scopes and ACL

Authorization

After authentication, mxHeadless checks whether the action is allowed in four steps:

  1. Route permission: public or requires identity
  2. Key or token scope: string like {object}.{action}
  3. MODX ACL: context, resource view, view_unpublished
  4. Field policy: hidden and protected fields

Missing scope on API key or OAuth → 403 scope_denied.

How scopes are checked

IdentityCheck
API key (mxh_*)Key scope list. * grants all actions
OAuth (mxt_*)Token scopes (intersection with client scopes)
SessionmodX->hasPermission() with the same string (resources.read, etc.)
AnonymousPublic GET only. Scopes do not apply

For integrations an API key is usually enough. Session fits mgr or same-origin UI with CSRF.

Core scopes (fixed routes)

ScopeRoutes
resources.readGET /resources, GET /resources/{id}, GET /pages/{uri}
resources.createPOST /resources
resources.updatePUT / PATCH /resources/{id}
resources.deleteDELETE /resources/{id}
contexts.readGET /contexts, GET /contexts/{key}, GET /contexts/{key}/settings
chunks.readGET /chunks, GET /chunks/{id}
templates.readGET /templates, GET /templates/{id}
snippets.readGET /snippets, GET /snippets/{id}
tvs.readGET /tvs, GET /tvs/{id}
categories.readGET /categories, GET /categories/{id}
content_types.readGET /content_types, GET /content_types/{id}
preview?preview=true without session view_unpublished. Also used for include_deleted checks
*All scopes (keys and tokens only)

Meta routes (/, /health, /schema, /docs, /meta/*) and POST /auth/token do not require a scope.

Scopes for /objects/{name}

Pattern from code: {name}.{action}, where {name} is the registry name, not a PHP class and not an objects. prefix.

ScopeMethodPath
{name}.readGET/objects/{name}, /objects/{name}/{id}
{name}.createPOST/objects/{name}
{name}.updatePUT, PATCH/objects/{name}/{id}
{name}.deleteDELETE/objects/{name}/{id}

Examples after MiniShop3 objects are registered:

ScopeMeaning
products.readProduct catalog
categories.readCategories
orders.readOrders (usually not public, plus ACL)
orders.updateOrder update when the object is writable

Registered names: GET /schema or GET /meta/endpoints on a live site.

Example scope sets for a key

Public frontends (read-only content) often work without a key.

CI / preview:

text
resources.read,preview,chunks.read,templates.read

MS3 catalog + CMS:

text
resources.read,products.read,categories.read

Admin API (narrow, no *):

text
resources.read,resources.create,resources.update,orders.read

Create a key: API keys. OAuth: OAuth.

Public vs protected

Anonymous callers can read discovery, health, schema, docs, meta, GET /resources, and GET /pages/{uri} within ACL for published resources.

Elements, contexts, write operations, and /objects/* require credentials.

Context

Bootstrap: mxheadless_context (default web) sets the MODX context when the gateway or api.php initializes. Value mgr is ignored.

Per request: header X-Context or query ?context=. Value must be in mxheadless_allowed_contexts (default web,mgr). Otherwise 422 Invalid context.

Mutations by id find the row across contexts, then check context.{key} / context_{key} access. Writing context_key to an unknown or unloadable context returns 422, not 500.

Fields

Hidden fields never appear in JSON. Protected fields require a separate permission in the definition. Requesting fields= for an unknown or forbidden field → 422.

Preview and deleted

QueryWho
preview=trueSession with view_unpublished or scope preview
include_deleted=1Not for anonymous. Needs preview, resources.update, resources.delete, or matching MODX permissions

See also