API reference

Two HTTP surfaces, mounted on the same SGApps.IO host:

Surface Prefix Auth Audience
Read API /site-builder/api/erp-config/ PRIVATE-TOKEN header External ERPs
Admin API /site-builder/api/ Session cookie The webapp UI

Read API (external — ERP clients)

GitLab v4-compatible shape — existing GitLab API clients work unmodified.
<div class="endpoint-table">

Method Endpoint Returns
GET /site-builder/api/erp-config/projects/&#x7B;repoId&#x7D;/repository/branches&#x3F;search= [&#x7B;name, commit:&#x7B;short_id&#x7D;&#x7D;]
GET /site-builder/api/erp-config/projects/&#x7B;repoId&#x7D;/repository/tree&#x3F;ref=&path=&recursive=1 [&#x7B;id, name, type, path, mode&#x7D;]
GET /site-builder/api/erp-config/projects/&#x7B;repoId&#x7D;/repository/files/&#x7B;rawencoded-path&#x7D;/raw&#x3F;ref= Raw file body
GET /site-builder/api/erp-config/health &#x7B;status:&#x22;ok&#x22;, time&#x7D;

</div>

Example — fetching a config

curl -H "PRIVATE-TOKEN: abc...xyz" \
     "https://sgapps.io/site-builder/api/erp-config/projects/b2b-cnc.erp-config/repository/files/operations.config.json/raw?ref=master"

Example — listing branches

curl -H "PRIVATE-TOKEN: abc...xyz" \
     "https://sgapps.io/site-builder/api/erp-config/projects/b2b-cnc.erp-config/repository/branches"

Returns (GitLab v4 compatible shape):

[
  {
    "name": "main",
    "commit": {
      "id": "ac800ee0ab12cd34ef56789012345678901abcde",
      "short_id": "ac800ee0"
    },
    "default": true,
    "protected": false,
    "merged": false
  }
]

Default branch resolution

The default: true flag is set on the first branch matching this priority:

  1. master, if it exists
  2. else main, if it exists
  3. else the alphabetically-first branch
  4. else (no branches) — no default

GitLab API clients that fall back to "the default branch" when no ref isspecified will follow this resolution. Explicit &#x3F;ref= always wins.

Choosing the right ref

Files live under a specific branch. A 404 File Not Found on/files/&#x7B;path&#x7D;/raw&#x3F;ref=master when the file actually exists almostalways means the project uses main (or a custom branch name) instead.List branches first to discover the right ref, or rely on default: true.

Admin API (internal — webapp)

All endpoints are gated by the SGApps session cookie. &#x7B;owner&#x7D; is implicit(taken from session.user._id); requests trying to touch another user'sprojects return 403.
<div class="endpoint-table">

Method Endpoint Body / Query Description
GET /site-builder/api/projects List your projects
POST /site-builder/api/projects &#x7B;name, type&#x7D; Create new project
GET /site-builder/api/projects/&#x7B;repoId&#x7D;/branches List branches
POST /site-builder/api/projects/&#x7B;repoId&#x7D;/branches &#x7B;name&#x7D; Create branch (empty folder)
GET /site-builder/api/projects/&#x7B;repoId&#x7D;/branches/&#x7B;ref&#x7D;/tree&#x3F;path=&recursive=1 Tree listing
GET /site-builder/api/projects/&#x7B;repoId&#x7D;/branches/&#x7B;ref&#x7D;/files/&#x7B;path&#x7D; Raw file body
PUT /site-builder/api/projects/&#x7B;repoId&#x7D;/branches/&#x7B;ref&#x7D;/files/&#x7B;path&#x7D; Raw body (octet-stream) Save file, snapshot first
GET /site-builder/api/projects/&#x7B;repoId&#x7D;/branches/&#x7B;ref&#x7D;/history List snapshots
GET /site-builder/api/projects/&#x7B;repoId&#x7D;/branches/&#x7B;ref&#x7D;/snapshots/&#x7B;version&#x7D;/tree&#x3F;path=&recursive=1 List files inside a snapshot (read-only)
GET /site-builder/api/projects/&#x7B;repoId&#x7D;/branches/&#x7B;ref&#x7D;/snapshots/&#x7B;version&#x7D;/files/&#x7B;path&#x7D; Raw file body inside a snapshot
POST /site-builder/api/projects/&#x7B;repoId&#x7D;/branches/&#x7B;ref&#x7D;/rollback&#x3F;to=v&#x7B;NNNN&#x7D; Restore snapshot (creates pre-rollback snapshot too)
GET /site-builder/api/tokens List your tokens (suffix only — full value never re-exposed)
POST /site-builder/api/tokens &#x7B;name, repos[], expires_at, fingerprint_required&#x7D; Create token (returns tokenString once)
PATCH /site-builder/api/tokens/&#x7B;tokenId&#x7D; partial body Update repos / expiry / fingerprint flag
DELETE /site-builder/api/tokens/&#x7B;tokenId&#x7D; Revoke token

</div>

Authentication

PRIVATE-TOKEN (external read API)

Set on every request:

PRIVATE-TOKEN: <64-char hex token>

The server resolves the token to a record `{owner, repos[], expires_at,fingerprint_required}. Expired tokens reject with 401. The repos[]`field controls project scope:

Choose strict allowlist for production integrations (principle of leastprivilege); wildcard is convenient for one-operator setups or short-livedexploration tokens where the owner trusts the holder with everything.

X-Instance-Id fingerprint binding

Optional defense against token leaks. When a token has `fingerprint_required:true`:

  1. On first use, the client sends X-Instance-Id: &#x3C;stable-id&#x3E; along with PRIVATE-TOKEN. The server stores the fingerprint.
  2. On subsequent requests, the same X-Instance-Id must be sent. The server compares with crypto.timingSafeEqual. Mismatches reject with 401 FINGERPRINT_MISMATCH.

A leaked token is then useless without the matching fingerprint. Pick afingerprint that's stable per instance (machine UUID, container ID, etc.)but unguessable from outside.

Role-based access (filesystem layer)

Beyond the auth surfaces above, file-level access in the SGApps virtualfilesystem (where projects live) is gated by these roles. They're set onuser accounts via the user-management UI and stack additively — grantinga higher-privilege role doesn't revoke a lower one.

Role Scope Grants
ROLE_SITEBUILDER_USER own folder /apps/site-builder/user/&#x7B;me&#x7D;/ full RW (idempotent with the default owner-access today; becomes a gating role if owner-default is removed)
ROLE_SITEBUILDER_USER__&#x7B;prefix&#x7D; projects with repoId matching &#x7B;prefix&#x7D;, &#x7B;prefix&#x7D;.&#x7B;type&#x7D;, or &#x7B;prefix&#x7D;/... — in any user's folder full RW (cross-user collaboration role)
ROLE_SITEBUILDER_EDITOR__PUBLIC /apps/site-builder/public/ write/remove (read is world-public)
ROLE_SITEBUILDER_EDITOR__READONLY entire /apps/site-builder/* tree, cross-user read + list (audit / monitoring)
ROLE_SITEBUILDER_EDITOR__ROOT entire /apps/site-builder/* tree, cross-user full RW + metadata + remove (platform admin)

Example — ROLE_SITEBUILDER_USER__b2b-cnc granted on user Alice gives Aliceedit access to b2b-cnc.erp-config, b2b-cnc.fiscal-mev, etc. inside thefolder of any user that hosts such projects. Use full repoIds(ROLE_SITEBUILDER_USER__b2b-cnc.erp-config) to restrict to one specificproject instead of the whole group.
These roles control the underlying SGApps virtual-FS — the API endpointsabove honour the result automatically (a 403 from the storage layerbubbles up as a 403 from the API).

Session cookie (admin API)

The webapp authenticates via the standard SGApps session cookie set by/user/login. There's nothing the API has to do — the existingSessionManager middleware drops req.session.data.loginData.local intothe request before the handler runs.

Cache versioning

The read API exposes commit.short_id per branch in the branches listingresponse. It's the first 8 chars of `sha256(concat sortat după path de"{path}|{size}|{md5}\n")` over all files in the live branch.

Property Value
Stability Identical for unchanged content (deterministic)
Invalidation Recomputed on next read after a PUT
Cost One GridFS list per branch — no content reads

Clients should use this as an ETag-like cache key — fetch the tree once,remember the short_id, and skip re-fetching files until the short_idchanges.

Error codes

Status When Body
400 Malformed path / branch name / missing required field &#x7B;message&#x7D;
401 Missing / invalid / expired token; session not logged in &#x7B;message&#x7D;
403 Token doesn't cover requested repo; owner mismatch on admin route &#x7B;message&#x7D;
404 Project / branch / file / snapshot not found &#x7B;message&#x7D;
409 (Reserved for future optimistic-lock writes) &#x7B;message&#x7D;
500 Storage error &#x7B;message&#x7D;

Errors are always JSON. The message field is human-readable but stableenough to switch on (e.g. 401 FINGERPRINT_MISMATCH for the fingerprint case).