Skip to main content

The BOM REST API — the API the site *exposes*

Audience: developers & AI agents, and anyone integrating against Beta-Calco · Scope: the outbound "CSI REST API" — auth, endpoints, response shape · Last reviewed: 2026-07-25

TL;DR — The site publishes a small REST API ("Beta Calco CSI REST API", v0.9) so other systems can ask for a flat BOM without a login: authenticate with client credentials that are ordinary Joomla site user credentials, get a bearer token good for 24 hours, then request materials by Infor item name or by configuration code. It serves its own OpenAPI 3.0.3 document. Note that the token lives in APCu, so it does not survive a PHP restart and is not shared across web servers.

Everything here is served by one controller: CUST/controllers/csimaterials.php.

What it is & why it exists

The BOM logic — cache, CPQ, explosion — is the site's, but the consumers are not all the site's. Costing tools, spreadsheets and internal automation need the same answer the quote line gets. Rather than duplicate the logic, the site exposes it.

The API is deliberately small: two useful operations, one token endpoint, and a self-describing schema.

How it fits together

TaskPurpose
getTokenexchange client credentials for a bearer token
getMaterialsForItemsflat BOM for up to 200 Infor item names + quantities — fresh, straight from Infor
getMaterialsFromConfigurationCodesflat BOM for configuration codes, resolved through CPQ
getDocumentationthe OpenAPI 3.0.3 document (documents only getToken and getMaterialsForItems)

Base path is whatever KLink::getRoute('index.php?option=com_configbox&view=customview&viewname=csimaterials') resolves to — i.e. it depends on the menu item alias configured for that view (see SEF links). The code's own examples use /csi-rest-api/.

Integrations & contracts

Authentication

Client-credentials, where the "client" is a Joomla site user:

# Credentials in the body (or as Basic Auth — PHP_AUTH_USER / PHP_AUTH_PW are accepted)
curl -s -X POST 'https://betacalco.com/csi-rest-api/getToken' \
-d 'client_id=<username>' -d 'client_secret=<password>'

# → {"token_type":"Bearer","access_token":"<64 hex chars>","expires_in":86400}

The token is 32 random bytes hex-encoded, stored in APCu under csi.oauth.tokens.<token> with a 24-hour TTL. Failures return 401 with {"error":"invalid_client"|"invalid_grant", "error_description":"…"}.

Then send it as a bearer token:

curl -s 'https://betacalco.com/csi-rest-api/getMaterialsForItems/json/PH-130000-40/10/PH-130010-CP-F1/2' \
-H 'Authorization: Bearer <access_token>'

isAuthorized() accepts either an Authorization header (any prefix is stripped — Bearer <token> and a bare token both work), or an access_token query parameter, or simply being logged into the site in that browser session. Any logged-in Joomla user passes; there is no permission check beyond authentication.

getMaterialsForItems

ParameterRequiredNotes
output_typeyes (documented); defaults to json in codejson or xlsx
item_1item_200at least oneInfor item name
qty_1qty_200nodefaults to 1; must be numeric and non-zero

Item/qty pairs are read until the first gap, so numbering must be contiguous from 1. Passing more than 200 (or supplying item_201) returns 422. An empty item name or a zero/non-numeric quantity also returns 422 with validationIssues.

json returns the envelope below; xlsx streams a spreadsheet as BOM.xlsx with the columns Root Item · Material Item name · PMT Code · UOM · Qty · Path · Signature.

Every call is logged with the requesting username to the custom_flat_bom_requests log, and its duration to custom_syteline_performance.

This endpoint is fresh, not cached — it calls getMaterialsForItems() directly, which runs the five-level explosion against Infor. It is therefore slow (seconds to minutes) and load-bearing on the Infor connection. It does not read the BOM cache.

getMaterialsFromConfigurationCodes

Takes item_1item_N as configuration codes (spaces stripped), splits each on /, and resolves it through getCpqMaterialsFromCode() — product lookup via the linear price list, then CPQ. The response maps each configuration code to its material list, or null where resolution failed.

This task's authorization check is wrong. It calls parent::isAuthorized()KenedoController's base implementation — instead of $this->isAuthorized(). The base returns true unconditionally for any controller whose name does not begin with admin, so the token check is bypassed and the endpoint is reachable unauthenticated. Its sibling getMaterialsForItems calls $this->isAuthorized() and is correctly protected. Established by reading the two call sites against KenedoController::isAuthorized(); recorded in docs/_known-issues.md with the rest of the auth cluster.

Response envelope

All JSON responses share the ConfigboxJsonResponse envelope:

{
"success": true,
"feedback": "",
"errors": [],
"validationIssues": [],
"timeMs": 1234.5,
"data": [
{
"rootItem": "PH-130000-40",
"item": "SOME-PART",
"pmtCode": "P",
"um": "EA",
"qty": "24.00000000",
"path": "[\"PH-130000-40\",\"SUB\",\"SOME-PART\"]",
"signature": "d41d8cd98f00b204e9800998ecf8427e"
}
]
}

Status codes in use: 200, 401 (missing/invalid token), 422 (validation), 400 (any thrown exception — the message is returned verbatim).

SEF path form

getSegmentMatching() maps positional URL segments to parameters:

TaskSegment structure
getToken/getToken/<client_id>/<client_secret>
getMaterialsForItems/getMaterialsForItems/<output_type>/<item_1>/<qty_1>/…

Do not use the path form of getToken in production. It puts a username and password in the URL, where they land in web-server access logs, proxy logs and browser history. Use the body/Basic-Auth form.

Admin settings

None. The API has no settings of its own — no enable flag, no allow-list, no rate limit, and no separate credential store. Access is governed entirely by Joomla user accounts, and the data it returns depends on the Infor DB and CPQ settings documented in the Infor hub and cpq.md.

Data model

The API owns no tables. Tokens live only in APCu; materials are read live from Infor (or, for the configuration-code endpoint, produced by CPQ). Nothing is persisted apart from log entries.

Deployment runbook (manual steps)

  1. Nothing to migrate — the controller ships with the code and is always reachable.

  2. Create (or identify) the Joomla user account the consumer will authenticate as. There is no API-key concept: the account's password is the client secret, so use a dedicated account rather than a person's.

  3. Ensure the Infor DB settings are configured — every response depends on them — and CPQ settings if the consumer will use configuration codes.

  4. Confirm APCu is enabled for the web SAPI. Without it token storage silently fails and every authenticated call returns 401.

  5. If a clean URL is wanted, add the menu item for the csimaterials view — see SEF links. Otherwise the index.php?option=…&task=… form works.

  6. Smoke test: request a token, then request one known item and confirm rows come back:

    TOKEN=$(curl -s -X POST "$BASE/getToken" -d "client_id=$U" -d "client_secret=$P" | jq -r .access_token)
    curl -s "$BASE/getMaterialsForItems?output_type=json&item_1=PH-130000-40&qty_1=1" \
    -H "Authorization: Bearer $TOKEN" | jq '.success, (.data | length)'

Turning it off: there is no switch. Disabling the consumer's Joomla account revokes access at the next token request (existing tokens stay valid for up to 24 hours). Blocking the route at the web server is the only immediate stop.

Testing

No automated coverage. The OpenAPI document (getDocumentation) can be pasted into any Swagger UI to explore the two documented operations interactively.

Gotchas & caveats

  • Tokens are APCu-only. A PHP-FPM restart, an APCu flush, or a second web server invalidates them — consumers must handle a sudden 401 by re-authenticating, not by treating 24 hours as guaranteed.
  • Any logged-in user is authorized. Authentication is the only gate; there is no role or permission check, so any site account can read BOM data through the API even though the on-site BOM screens require com_configbox.core.manage.
  • A browser session authenticates too. isLoggedIn() short-circuits the token check, so these endpoints are reachable from a logged-in browser with no token at all — convenient for debugging, easy to forget when reasoning about exposure.
  • getMaterialsFromConfigurationCodes is not token-protected (above).
  • No rate limiting. Each call can trigger a multi-minute Infor query; a loop over many items is an effective self-inflicted denial of service on the ERP connection.
  • The 200-item cap is maxItemRequests, shared with the admin tools — change it and both move.
  • exit() in a finally ends the request inside getMaterialsForItems, so nothing after the controller runs. Don't expect the usual response pipeline.

Possible follow-ups

  • Fix getMaterialsFromConfigurationCodes to call $this->isAuthorized().
  • Gate the API on a dedicated permission rather than "any logged-in user".
  • Move tokens out of APCu (a table with a TTL) so they survive restarts and work multi-server.
  • Rate-limit per token, given each call can hit Infor for minutes.