# The four ways in

> CBX can be driven from four directions. They look different, authenticate differently and are documented separately — but below a certain line they are the s…

Source: CBX documentation, version 3.x (released). Canonical page: https://docs.configbox.at/docs/technical/api_surfaces. Last updated 2026-08-25.

---
CBX can be driven from four directions. They look different, authenticate differently and are
documented separately — but below a certain line they are the same code, and knowing where that line
falls is most of what you need to reason about any of them.

This is the map. Each surface has its own detailed document; this one says which to read and why.

> A diagram of the same thing: **`com_configbox_api_wiring.html`** — open it in a browser. The AI
> surface has its own picture page, **`com_configbox_ai_surface.html`**: what MCP is, how a
> conversation runs, the two transports converging, and the scope grid.

---

## 1. The four surfaces

| Surface | Entry point | Who calls it | Authenticates with |
|---|---|---|---|
| **Browser (admin)** | `index.php?option=com_configbox&controller=admin*&task=…` | The admin UI's own JavaScript | Host session + `core.manage` |
| **Browser (storefront)** | `/cb-api/{controller}/{task}` | The storefront's JavaScript | Host session (public tasks) |
| **HTTP API** | `/cb-api/v1/{entity}[/{id}]` | Any program, anywhere | Bearer token + `area:level` scopes |
| **MCP** | `configbox:mcp` over stdio, or `POST /cb-api/v1/mcp` | An AI assistant — locally, or anywhere | stdio: nothing, shell access is the boundary · HTTP: the same bearer token and scopes as the HTTP API |
| **CLI** | `configbox:*` commands | Deploys, cron, an operator | Nothing — shell access is the boundary |

Five rows for four directions, because the browser arrives as two different things: an authenticated
operator editing the catalog, and an anonymous shopper configuring a product. They share a router
and share nothing else.

> Deliberately off this map: `controllers/testautomation.php`, the E2E suite's fixture-seeding
> surface. It authenticates per request with superuser credentials and exists only for test
> automation — not a way in that anything besides the suite may use.

### Which to reach for

- **Integrating another system** — the HTTP API. It is the only surface designed to be called by
  something that is not on the same machine and not a browser.
- **Letting an assistant author a catalog** — MCP. Same pipeline, tool-shaped, no HTTP exposure.
- **A deploy step, a migration, a scripted fix** — the CLI.
- **Building admin or storefront UI** — the controller tasks, through
  [`server.request()`](https://docs.configbox.at/docs/technical/frontend_requests).

---

## 2. Where they converge

```
  browser (admin)      browser (storefront)      HTTP API         MCP          CLI
        │                      │                    │              │            │
   KenedoController      KenedoController    ConfigboxController  Configbox   Configbox
   ::store/delete/…      (public tasks)          Apiv1            McpHelper   CliHelper
        │                      │                    │              │            │
        └──────────────────────┴────────┬───────────┴──────────────┘            │
                                        │                                       │
                        ConfigboxEntityApiHelper                                │
                        · the entity registry                                   │
                        · runStorePipeline()  ←─────────────────────────────────┘
                                        │              (run-task reaches a controller)
                                   KenedoModel
                        prepareForStorage → isInsert → validateData → store → afterStore
```

**Everything below `ConfigboxEntityApiHelper` is shared code, not merely shared behaviour.** A fix to
storage semantics made there reaches every surface at once. If you find yourself making the same fix
twice, one of the two places is wrong.

**Everything above it is the surface's own business**, and legitimately differs: a form post has a
request to read, an API call has a JSON body, an MCP tool has an arguments map. What must not differ
is what happens to the resulting `$data` object.

The rule, stated once: **a surface may translate and may refuse; it may not re-implement.**

---

## 3. What each surface adds, and nothing more

| Surface | Adds |
|---|---|
| Browser (admin) | `core.manage` check, reads the form post, renders or answers JSON |
| Browser (storefront) | Session-scoped cart and configuration, no permission model |
| HTTP API | Bearer token and scope, HTTP status codes, RFC 9457 problem bodies |
| MCP | A narrower writable entity set, `--read-only`, prose errors instead of typed ones |
| CLI | Argument parsing, exit codes |

---

## 4. Authorization, compared

This is the part most worth understanding, because the four models are genuinely different and the
differences are deliberate.

**Browser (admin)** — `KenedoController::isAuthorized()` returns true for any controller whose name
does *not* start with `admin`. For those that do, it asks the host for `com_configbox.core.manage`.
There is no global gate: **every task authorizes itself**, so a missing call is silent.

**Browser (storefront)** — no permission model. The cart and the configuration belong to the session,
and a foreign cart position answers `404` rather than `403`, because "it exists but is not yours" is
a way to count the store's carts.

**HTTP API** — a bearer token, checked before anything is read or written, with coarse `read`/`write`
scopes. Insufficient scope is `403`, not `401`: the caller *is* authenticated and retrying with the
same credentials will never help.

**MCP and CLI** — **nothing, deliberately.** Their trust boundary is *shell access to the site*.
Whoever can start the MCP server or run a `configbox:*` command can already read the database and
edit the files; a credential prompt in front of that is theatre. This is why
`ConfigboxEntityApiHelper` contains no authorization at all — a gate there would break those callers
while adding nothing to the HTTP one, which authenticates before it ever arrives.

> **The rule covers the admin gate too — decided 2026-07-31.** This box used to record an
> incoherence: admin controllers ask the *platform* for `core.manage`, a CLI process has no host
> session user, so `configbox:run-task` was refused on every `admin*` controller however `--user`
> was set — while MCP, on the same shell-access boundary, wrote freely. Nobody had decided that;
> it fell out of `isAuthorized()` consulting a host that has no CLI concept of a user. The
> decision: **a CLI process passes the admin gate** (`KenedoController::isAuthorized()`), for the
> same reason MCP carries no gate at all. `--user` is unaffected — it names the acting *customer*,
> not an admin identity. A CLI test pins the rule so it cannot regress silently.

---

## 5. The response contract

Every migrated surface answers the same two shapes, and the HTTP status carries the outcome — there
is no `success` field. See [the API contract](https://docs.configbox.at/docs/technical/api_contract) for the argument.

- `2xx` → `{data, meta}`, as `application/json`
- `4xx`/`5xx` → RFC 9457 problem details, as `application/problem+json`

"Migrated" is doing a little work in that sentence: the checkout tasks still render pages, the
storefront's stock link tasks survive as deprecated JS-off fallbacks, and `getMissingSelections*`
keeps its bare array — [the contract's §8](https://docs.configbox.at/docs/technical/api_contract) is the ledger of what is
done and what remains.

MCP is the exception, and only at its own boundary: a JSON-RPC tool result has no status code and no
field slot, so `ConfigboxMcpHelper::flattenForTool()` turns the typed exceptions back into prose.
The three types map the same way everywhere else:

| Thrown | Means | HTTP |
|---|---|---|
| `KenedoValidationException` | the caller's data was refused | 422 |
| `KenedoNotFoundException` | no such entity or record | 404 |
| `KenedoSystemException` | something broke on our side | 500 |

---

## 6. What is generated, and from where

One source describes every surface's data. A model's property definitions feed the type generator,
which writes the committed artifacts, from which the API description is assembled:

```
  models (getPropertyDefinitions, incl. apiTitle / apiDescription)
        │
   ConfigboxTypeGenerator
        ├── generated/records/read/*.php     PHP record stubs (IDE hover)
        ├── generated/schemas/read|write/    JSON Schema 2020-12
        └── generated/js/records.d.ts        TypeScript declarations
                    │
        ConfigboxOpenApiHelper  (inlines the schemas)
                    ├── openapi.json
                    ├── configbox.postman_collection.json
                    └── api-reference.html
```

`configbox:generate-types` writes the first three; `configbox:api:export` writes the last three.
Both are committed artifacts — see [type generation](https://docs.configbox.at/docs/technical/type_generation).

---

## 7. Where to read next

| You want | Read |
|---|---|
| To call the HTTP API | [com_configbox_entity_api.md](https://docs.configbox.at/docs/technical/entity_api) |
| How the HTTP API is built | [com_configbox_http_api.md](https://docs.configbox.at/docs/technical/http_api) |
| To run or extend the MCP server | [com_configbox_mcp_server.md](https://docs.configbox.at/docs/technical/mcp_server) |
| How MCP is built | [com_configbox_mcp_server_internals.md](https://docs.configbox.at/docs/technical/mcp_server_internals) |
| The CLI suite | [com_configbox_cli_commands.md](https://docs.configbox.at/docs/technical/cli_commands) |
| Browser controller tasks, in detail | [com_configbox_mvc_tasks.md](https://docs.configbox.at/docs/technical/mvc_tasks) |
| Calling them from JavaScript | [com_configbox_frontend_requests.md](https://docs.configbox.at/docs/technical/frontend_requests) |
| The configurator/cart runtime calls | [com_configbox_runtime_api.md](https://docs.configbox.at/docs/technical/runtime_api) |
| The response contract | [com_configbox_api_contract.md](https://docs.configbox.at/docs/technical/api_contract) |
| Where field descriptions come from | [com_configbox_type_generation.md](https://docs.configbox.at/docs/technical/type_generation) |
