A walkthrough for the team running an external ERP that needs to consumeconfigs from this service.
/apps/site-builder/user/{you}/).cluj-erp-prod).
- Allowed repos — comma-separated repoId list. Leave empty to
grant access to all your projects (not recommended).
- Expires at — optional date. Leave empty for non-expiring tokens.
- Require fingerprint — turn on for production ERPs (see below).Use commit.short_id as an ETag-style cache key. Pseudocode:
let lastShortId = null;
let cachedTree = null;
let cachedFiles = {};
let resolvedRef = null; // discovered on first poll, cached afterwards
async function refreshConfigIfChanged() {
const branches = await fetch(
'/site-builder/api/erp-config/projects/' + REPO_ID +
'/repository/branches',
{ headers: { 'PRIVATE-TOKEN': TOKEN } }
).then(r => r.json());
// Pick the branch to track. Don't hard-code `master` — modern repos
// use `main`, and some projects use custom branch names. The server
// marks one branch as `default: true` (priority: master → main →
// alphabetical first) — that's the one a client without a hard
// preference should follow.
const branch =
branches.find(b => b.default) ||
branches.find(b => b.name === 'master') ||
branches.find(b => b.name === 'main') ||
branches[0];
if (!branch) throw new Error('No branches in this project');
resolvedRef = branch.name;
if (branch.commit.short_id === lastShortId) {
return cachedFiles; // no change
}
// Content changed — re-fetch tree + files.
lastShortId = branch.commit.short_id;
cachedTree = await fetch(
'/site-builder/api/erp-config/projects/' + REPO_ID +
'/repository/tree?ref=' + encodeURIComponent(resolvedRef) + '&recursive=1',
{ headers: { 'PRIVATE-TOKEN': TOKEN } }
).then(r => r.json());
cachedFiles = {};
for (const entry of cachedTree.filter(e => e.type === 'blob')) {
cachedFiles[entry.path] = await fetch(
'/site-builder/api/erp-config/projects/' + REPO_ID +
'/repository/files/' + encodeURIComponent(entry.path) +
'/raw?ref=' + encodeURIComponent(resolvedRef),
{ headers: { 'PRIVATE-TOKEN': TOKEN } }
).then(r => r.text());
}
return cachedFiles;
}
// Call from your existing refresh tick (every N seconds is fine — the
// branches endpoint is cheap; full re-fetch only fires when short_id changes).Common pitfall — 404 on file read. If your client hard-codes?ref=masterand the project usesmain, every file fetch returns404 File Not Found(path includes the branch name, so a wrongrefresolves to a non-existent folder under the project root). Always discover the ref from the branches listing first, or rely on thedefault: trueflag.
| ERP role | Refresh interval | Why |
|---|---|---|
| User-facing app (pricing, taxonomy) | 30s – 5min | Quick reaction to operator changes |
| Background batch (BOM expansion) | At job start + every 15min | Cold reads, no need to react fast |
| Reporting / analytics | At job start only | Configs are reference data; one read per run is enough |
For production ERPs, turn on Require X-Instance-Id fingerprint matchwhen issuing the token. Then on the client:
instanceId for the ERP host. Good sources:
- Container ID + image digest (Docker).
- Hardware-bound UUID (/etc/machine-id on Linux).
- Cloud instance ID (AWS instance-id, GCP instance UUID).X-Instance-Id: <id>.crypto.timingSafeEqual — a leaked token used
from any other host returns 401 FINGERPRINT_MISMATCH.If you migrate the ERP to a new host, you have two options:
fingerprint_required on the token
(PATCH /site-builder/api/tokens/{id}), let the new instance bind, then
re-enable. Requires a session with the owner's credentials.| Status | Recovery |
|---|---|
401 (token expired / fingerprint mismatch) | Page the on-call. Don't retry — won't change. Issue a fresh token from the webapp. |
403 (token doesn't cover repo) | The webapp operator added a new repo but forgot to extend the token. PATCH /tokens/{id} with the new repos[]. |
404 (project missing) | Webapp operator deleted the project. Don't auto-create — alert. |
5xx | Transient. Retry with exponential backoff. Keep serving the last-known-good cache to keep the ERP alive. |
Rule of thumb. Treat the config-store as eventually-consistent reference data. Always have a last-known-good in-memory cache so a brief service outage on this side doesn't take your ERP down.
Periodic rotation (e.g. every 90 days):
-v2 is fine
for tracking.If you need zero-downtime rotation across many ERPs:
401 and pages the operator (your detection signal).