Skip to main content
Version: 3.x

API tokens and scopes

Version
3.x
Updated
View markdown

Audience: developers integrating with CBX, and the admins who hand them tokens · Scope: the one credential every API caller presents, and the vocabulary that says what it may do · Since: 3.8.17 (the <area>:<level> vocabulary; tokens themselves since 3.7.2)

TL;DR — A caller of the REST API (/cbx-api/v1/…) or of MCP over HTTP (/cbx-api/mcp) presents a bearer token. A token carries scopes, each <area>:<level> — catalog:write, orders:read. Seven areas say what the token may touch; two levels say how. Write covers read within an area; nothing carries across areas; every token can read store configuration; and permission to run PHP (code:write) is never part of any bundle. Tokens are minted in the admin (ConfigBox → API Tokens) or on the CLI (configbox:token:mint), shown once, and revoked permanently. configbox:token:scopes prints this whole vocabulary from the running code.


1. What a token is​

cbx_<id>_<secret>
| | |
| | +-- 43 characters of CSPRNG output. Never stored - only its SHA-256 is.
| +-------- the row id, so verifying is one indexed read and not a table scan
+------------- a fixed prefix, so a leaked token is RECOGNISABLE as one by a secret scanner

A token is presented as a standard bearer credential (RFC 6750):

Authorization: Bearer cbx_12_XNySf3kCGeGmN4TUkU6gzkDEzgEUWzmQwtm9fww
  • Shown once. Only the hash is kept, so nothing can display it again — not the admin screen, not the CLI, not a support engineer with database access. Lost it? Revoke and mint another.
  • Expires optionally, at the start of a UTC day. Prefer an expiry for anything run by an outside party: a token that ends by itself does not rely on anyone remembering to revoke it.
  • Revoked permanently. The row stays, so the record of what existed survives; the caller is shut out on its next request.
  • Last used is recorded (best-effort) so the "nobody uses this any more" conversation has data.

A token is for software. Staff log in to the admin with their own account; a token never opens an admin screen.

2. Where tokens are minted​

In the admin — ConfigBox → API Tokens → Add. Name it after its holder, tick the scopes in the grid (or start from a preset button), optionally set an expiry, save. The screen shows the new token once in a warning panel; copy it there. The admin-guide article docs/admin-guide/settings/manage-api-tokens.md walks a store admin through it in plain language, and the same screen's help drawer shows it in place.

On the CLI — from the site root, under the site's own PHP:

php cli/joomla.php configbox:token:mint "catalog assistant" --preset=author
php cli/joomla.php configbox:token:mint "fulfilment bot" --scope=orders:write --scope=customers:read
php cli/joomla.php configbox:token:mint "pricing dev" --preset=author --scope=code:write --expires="+90 days"

php cli/joomla.php configbox:token:scopes # the vocabulary, from the code
php cli/joomla.php configbox:token:list # what exists, with last use
php cli/joomla.php configbox:token:revoke 12

The token is the last line of mint's output, unstyled, so … | tail -1 captures exactly it. Both routes call the same ConfigboxApiTokenHelper::mint(); there is no third way to make one.

3. The seven areas​

A scope is <area>:<level>. The area is what a token touches; the level is how.

AreaHoldsEntitiesGrantableIf a mistake happens
catalogWhat the shop sells: products, pages, questions, answers, lists and assignments, detail panes, examples, rules, formula and matrix calculations.product, page, question, answer, product-list, product-list-assignment, calculation, calculation-code, calculation-matrix, product-detail-pane, example, plus any customization model that does not say otherwiseread, writerecoverable — a typo someone fixes; the storefront shows all of it anyway
storeHow the shop charges and delivers: settings, store identity, currencies, tax classes, shippers and shipping methods, payment methods and providers, connectors, notifications, salutations, geography.settings, store, currency, tax-class, shipper, shipping-method, payment-method, payment-provider, connector, notification, salutation, country, state, county, city, zonewrite only — every token reads it (§4, rule 3)costly — a wrong tax rate is an invoice that already went out
customersPeople: customer records, addresses, reviews, parties and their relationships.customer, address, review, party, party-relationshipread, writeirreversible — personal data; a leak cannot be un-leaked
ordersMoney that moved: sales orders, quotes, their lines and tax rates, payments. Frozen checkout snapshots — readable, and changed only through operations.sales-order, order-line, order-line-config, order-tax-rate, quote, quote-line, quote-line-config, quote-tax-rate, paymentread, writeirreversible — who bought what, plus their address
conversationsWhat visitors said to the advisor: conversations, turns, events, recommendations, outreaches.chat-conversation, chat-turn, chat-event, chat-outreach, chat-recommendation, chat-recommendation-evidence, chat-recommendation-noteread, writeirreversible — conversational personal data
codePHP the pricing engine evaluates: code calculations, and transfer packages that carry them.— (a permission, not a record set)write only — reading a code calculation is catalog:readdangerous — code execution on the server
systemThe install itself: migrations, charset, system variables, orphaned strings.— (operations, not records)read, writecostly — can freeze updates or skip migrations; recoverable from a backup

Twelve scopes are grantable: catalog:read, catalog:write, store:write, customers:read, customers:write, orders:read, orders:write, conversations:read, conversations:write, code:write, system:read, system:write.

Why areas and not verbs. The first vocabulary was read / write / admin. It split by what a token does, and the boundaries that matter run by what it touches: a catalog browser must not dump customer addresses, an order-fulfilment token must not run migrations, and authoring a product must not include writing PHP the engine evaluates. Every one of those is a subject boundary a verb vocabulary cannot express. The pattern is Shopify's (read_products / write_products) and Stripe's restricted keys (a matrix of resources × none/read/write) — the closest prior art for a shop.

4. The four rules​

These are the entire model. They live in one function, ConfigboxApiTokenHelper::grantIncludes(), which the REST controller and the MCP server both consult — so HTTP tokens and stdio runs cannot disagree about what a grant means.

  1. Within an area, write implies read. catalog:write can describe, list and get the catalog. A token is stored as the highest level per area: minting catalog:read,catalog:write stores catalog:write.
  2. Across areas, nothing implies anything. Seven independent switches. read,admin-style ladders are gone: orders:write says nothing about the catalog, and customers:write nothing about orders.
  3. store:read is the floor. Every token has it, ungranted and unrevokable — it is not a scope mint accepts. Currencies, tax classes, shipping methods and countries are the vocabulary every other area is expressed in; a token that could not read them could not make sense of a price, a product or an address. The floor is safe only because credential-bearing fields are apiSensitive and never returned by any read — a payment method's provider parameters, a payment provider's settings, the licence key, the AI and MaxMind keys. A model that adds a secret to a store-area entity must mark it, or the floor hands it to every token.
  4. code:write is never enough on its own. Writing a code calculation needs catalog:write and code:write; importing a transfer package that carries code calculations needs both too. cbx_check_product_package reports carriesCode and the scopes still needed before anything is written. No preset includes code:write — it is always spelled out by hand.

5. Presets​

A preset is a name mint expands to a scope list and then forgets. The token row stores the explicit scopes, so a preset can be renamed or retired without touching a token, and there is never a second vocabulary to keep in sync.

PresetScopesFor
authorcatalog:writeBuilding and maintaining the catalog.
fulfilmentorders:write, customers:readWorking orders, and seeing who they belong to.
insightscatalog:read, conversations:readLearning from the advisor: what visitors ask, against what the catalog offers.
operatorstore:write, system:writeKeeping the install healthy: migrations, charset, shipping and tax configuration.
everythingevery area but code, at writeA supervised session. Still cannot write PHP.

--preset combines with --scope: --preset=author --scope=code:write is the whole ceremony for a token that may author PHP calculations, and it is exactly enough.

6. What each token actually gets​

The same token means the same thing on both surfaces. Over REST, an entity endpoint needs the entity's area at the request's level (GET → <area>:read, POST/PATCH/DELETE → <area>:write); over MCP, the generic entity tools are opened by any scope at their level and then gated per entity, and every entity enum a token is handed lists only what that token may touch.

TokenOver RESTOver MCP
catalog:write (author)Full CRUD on the 13 catalog entities; GET on store entities; 403 on customers, orders, conversations16 tools; the write enums list the 9 writable catalog entities; refused reading a customer (customers:read) and writing a code body (code:write)
orders:write + customers:read (fulfilment)GET on orders, customers, store; 403 on products6 tools — describe/list/get, cbx_set_order_status, cbx_poll_payments, cbx_clear_cache; the generic writers are withheld because no order entity is writable
catalog:read + conversations:read (insights)GET on catalog, conversations, storeThe read tools with both areas in their enum; no writers
store:write + system:write (operator)CRUD on store entities; 403 on the catalogcbx_apply_migrations, cbx_convert_charset, …; refused cbx_get_entity product
everything + code:writeEverythingAll 25 tools
only the floor (e.g. conversations:read)GET /tax-class 200; GET /payment-method/1 200 without paramsStore entities readable; no credentials in any of them

The discovery index is filtered too: GET /cbx-api/v1/ lists only the entities the presenting token may read. Any valid token may call discovery, openapi.json and the schemas — they describe structure, not data.

7. Refusals​

SituationRESTMCP
No token / bad token401 with WWW-Authenticate: Bearer, one message for missing, malformed, revoked and expired401 at the HTTP layer, before any JSON-RPC conversation
Token lacks the scope403 INSUFFICIENT_SCOPE naming the exact scope: This token does not carry the "orders:read" scope.A tool error the model can act on: Reading "customer" needs the "customers:read" scope, and this connection carries only: catalog:write.
A tool the token is not offered, called by name—Refused just the same — listing is a hint, the gate is next to the work
Code body without code:write—Writing a code calculation is running PHP on this server, and needs the "code:write" scope on top of catalog:write. …

403 rather than 401 when the token is valid but too narrow, because retrying with the same credentials can never help — that distinction is what a client's retry logic reads.

8. The audit trail​

Every MCP tools/call writes one line to configbox_authorization.log (the authorization KLog category, alongside every other who-was-allowed-what decision): the tool name, the caller (a token's id and name over HTTP, stdio on the CLI), and the granted scopes — never the arguments, which can carry a customer's address. Last used on the token row is the coarse version of the same record.

9. The legacy names, and migration 3.8.17​

read, write and admin are accepted by mint for one release, expanded with a printed notice, and never stored:

LegacyExpands toWhy this mapping
readcatalog:read, customers:read, orders:read, conversations:read, system:readIt was "everything readable". Narrow it afterwards.
writecatalog:write, code:writeA write token could author PHP calculations; keeping that is what makes the migration lossless. Revoke code:write from tokens that do not need it — most.
adminstore:write, customers:write, orders:write, system:writeThe grab-bag, unpacked into its four jobs.

Migration 3.8.17 widens the scopes column to VARCHAR(512) and rewrites every stored token through the same normalisation, idempotently. It logs a warning naming each token that came out holding code:write, because that is the one line an admin should act on. A write token that had no read widens slightly — catalog:write now reads the catalog, by rule 1.

10. For customization authors​

A model that opts into the API with getEntityName() belongs to an area. The central map (ConfigboxApiTokenHelper::getEntityAreas()) covers every stock entity and falls back to catalog for one it does not know — right for the customization models that exist today (product content such as summary-panel images), and wrong for a custom model holding customer data, which would then be readable by any catalog token. Override getApiArea() on the model and return one of ConfigboxApiTokenHelper::AREA_*. See customization/com_configbox_overriding_controllers_and_models.md.

11. Where the pieces live​

WhatWhere
The vocabulary — areas, scopes, floor, presets, entity map, grantIncludes()helpers/apitokens.php (ConfigboxApiTokenHelper)
Mint / verify / revoke, the token formatsame file
REST enforcement per entitycontrollers/apiv1.php (authenticateForEntity())
MCP enforcement per tool and per entity, tool annotationshelpers/mcp.php (getToolScopes(), getToolAnnotations(), getModelForEntity())
The admin screenmodels/apitokens.php, property type apitokenscopes (the grid)
The CLIconfigbox:token:mint, token:list, token:revoke, token:scopes — com_configbox_cli_commands.md
The migrationhelpers/updates/3.8.17.php
The pinning teststests/specs/api/mcp-http-transport.spec.ts, entity-api.spec.ts, cli/cli-commands.spec.ts

Related reading: com_configbox_api_surfaces.md (the four ways in), com_configbox_entity_api.md (using REST), com_configbox_mcp_server.md (using MCP), admin-guide/settings/manage-api-tokens.md (the store admin's view of the same screen).