Interfaces
Every interface CCC exposes today, handler by handler, with its guard, input, response shape and error behaviour.
What CCC exposes today, as opposed to what is proposed. These are not a general business-query interface: they are eight specific handlers, an embeddable dashboard, a CSV export and a health endpoint.
They do not share a response envelope. Three different shapes exist across the eight handlers, and a consumer must not assume one. Each is listed below.
The guards
| Surface | Guard |
|---|---|
| The eight API handlers | ?token= equal to CCC_API_TOKEN. Nothing else |
| Embeddable dashboards | A separate token, CCC_PUBLIC_TOKEN. Framing deliberately open |
| CSV export | Session, and a page permission, and a module flag. Three gates |
| Health endpoint | None. Unauthenticated |
The API guard is a plain equality check on a query parameter. A missing or wrong token answers Invalid token. as plain text, not JSON, so a client parsing the response as JSON fails on the error path rather than reading the error. An unknown route answers 404, also plain text.
The token travels in the URL, so it lands in browser history, proxy logs and shell history. Treat it as exposed wherever the URL has been.
The product handlers, six of them
| Handler | Returns | Fields per row |
|---|---|---|
products/products.php | Products | id, cid, name, productbrand_id, mainproductcategory_id, productcategorytoplevel_id |
products/productskus.php | SKUs | id, product_id, cid, sku, isvalidated |
products/productbrands.php | Brands | id, name, code, sortorder |
products/productbundles.php | Bundles | id, cid, name |
products/productcategories.php | Categories | id, code, name, parentcode, productcategorytoplevel_id |
products/productcategoriestoplevel.php | Top level categories only | id, code, name, akeneocode |
None of the six takes a parameter. There is no filter, no limit and no paging: each returns its whole table every time, and the field list above is the whole row a consumer gets. Adding a column to the underlying table does not add it here, and removing one of these fields breaks every consumer at once.
All six share one shape:
{ "data": [ ... ] }
A list under data, without a total, paging cursor or status field. There is no reliable error envelope: a database failure can yield missing data or PHP output rather than a structured error. Validate the response shape as well as HTTP success.
These read the product tables. Products and SKUs use staged refreshes; brands and categories use upserts. Separate requests do not form a consistent catalogue snapshot. See Akeneo.
classify/transaction.php
A read-only calculation over a supplied transaction, rather than an export of stored rows.
- Input: a JSON request body, conventionally sent with POST. The handler reads
php://inputwithout enforcing the HTTP method. Supplyns_saleschannel_idand a nonemptylinesarray whose entries namesku. Each SKU must resolve through the item lookup. - Returns:
{ "classification": ... }, a third shape distinct from bothdataand the health payload. - Errors are plain text and specific:
No data received,Invalid saleschannel,No lines received, andInvalid line: <sku>naming the offending SKU.
It classifies a transaction handed to it rather than one CCC imported, so it is the way another system can ask "what would CCC call this". It writes nothing.
healthcheck.php, and public/healthcheck.php
The API handler returns gethealthchecks() bare: no wrapper key at all, a third shape again.
The unauthenticated entry point at public/healthcheck.php is the one read outside this repository, and it is a contract:
- Key on the
jobfield. It is stable, and holds the batch type or, for a queue check, the table name. - Never key on the numeric ids. The table is dropped and re-seeded on every deploy in configuration order, so reordering the inventory renumbers every check.
- The entries are not all the same shape, and never were. An age check reports
lastrun,lastrunhumanandlastrunhours; a queue check reportsqueuedagainst athresholdand ages nothing. - The
objectfield is unused inside CCC and is kept because a consumer may key on it.
See data freshness and history for what the values mean, including the cost of computing them.
CSV export
Streams a CSV from a statement supplied by the client. It requires a session, the export page permission and the export module flag, and refuses anything its statement-shape check does not read as a single read.
That it accepts a client-supplied statement at all is recorded posture in AGENTS.md, a deliberate position rather than an oversight. See the export screen.
Embeddable dashboards
Served through a token-guarded entry point that builds its own page rather than using the shared layout. Frame ancestors are deliberately open, so it can be embedded anywhere.
Anyone holding the token can open it, which is a different access boundary from the equivalent in-app screen.
Known consumers
Tableau, Power BI and several own-IP tools read from CCC, plus a third-party marketing attribution service.
None of them reads through the handlers above. They read the database directly, so the tables and columns are the contract for them, not the JSON shapes on this page. A schema change reaches them with nothing in this repository to warn you, which is why consumption contracts exists and why a renamed column is a breaking change even when no code here refers to it.
Related
- Consumption contracts, the rules that make an aggregate correct
- Proposed machine retrieval, none of which exists
- Data model
Evidence
Handlers enumerated from app/api/, response shapes from the json_encode call in each. Guard, plain-text error and 404 behaviour from public/api.php. Export and dashboard guards, and the health payload's stable job and unstable ids, from AGENTS.md, Entry points and Jobs and the batches queue. Per-handler field lists read from the response array each handler builds, and the absence of any parameter from the same files: none of them reads $_GET. Basis: code-checked at d2bbb42.
What the downstream consumers query is not recorded in this repository and is not discoverable from it: they connect to the database directly, so nothing here sees their statements. The consequence is in the section above.
- Type
reference - Status
active - Updated
2026-09-14 - Created
2026-09-13