The web API
Everything under /api/web/v1/ belongs to the firm whose hostname the
request carries. There is no organization parameter and there never will be
one: the host decides, so a door cannot be tricked into answering for
somebody else's firm.
These doors serve the public website and a firm's own second server. They are not the ERP's API — that is the API hub.
Releases
| Door | Answers |
|---|---|
GET /api/web/v1/releases/latest |
the newest published build's facts |
GET /api/web/v1/releases/latest/download |
the file, counted |
Both take an optional ?product=. Without one they mean the firm's default
program — never „whatever release is newest", which would become a
different program the day the firm publishes a second one.
The facts door answers anyone and returns no link to the file:
{
"release": {
"label": "ctrl32 1.00.045", "version": "1.00.045",
"size": 30117888, "sha256": "…", "filename": "ctrl32-setup-1.00.045.exe",
"platform": "Windows", "published_at": "2026-09-11",
"product": "ctrl32-editor",
"archive_name": "ctrl32-1.00.045.zip", "archive_size": 29884416
}
}
release is null when the firm has no published build of that product. The
size beside a name always belongs to that same file — a page showing the
archive's name with the installer's size reads right and is wrong.
The fixed addresses
These are not under /api/, because a program that was installed a year ago
carries them in a manifest that cannot be edited.
| Address | Gives |
|---|---|
GET /download |
the newest installer of the default product |
GET /download/<product> |
that program's newest archive |
GET /download/<file>.exe |
that exact published build |
GET /update.txt?p=<product> |
the signed manifest, byte for byte |
A product name carries no dot and a file name does, which is how
/download/<segment> tells them apart. Asking /download with
Accept: text/html is a browser, and it is redirected to the download page.
/update.txt also counts installations. The check carries
i=<32 hex>&v=<version>&l=<language>, and one row is written per
installation per product per day.
`Cache-Control: no-cache` on the manifest is load-bearing
The count is taken at the origin. A cached manifest is an update check that never arrives, and the firm's installation count quietly becomes fiction.
The customer's own doors
/api/web/v1/portal/… needs a signed-in customer and answers only about
them. GET portal/downloads lists the firm's published builds, each with its
product, ordered so one program's builds sit together and the newest comes
first.
A download may be gated by an agreement. The door then answers 409 with
agreement_required and what is missing, so the site can show the document;
a POST records the acceptance and the next GET streams the file.
The machine door
POST /api/web/v1/portal/machine-login lets a firm's other server sign a
customer in. It sends the shared secret in X-Machine-Secret and the
customer's credentials in the body, and gets back who they are, which
partners they may act for, and the licences those partners hold.
{
"authenticated": true,
"username": "jan@example.com", "email": "jan@example.com", "name": "Ján Novák",
"accesses": [ … ],
"licenses": [
{"tier": "cloud", "key": "…", "active": true,
"issued_at": "2026-09-11T08:00:00+00:00", "until": "2027-09-11"}
]
}
The key travels with the answer because the asking server verifies the
signature itself against the firm's public key. active is the platform's
own answer — not revoked and not run out — so the other server never has to
re-derive it.
| Refusal | Means |
|---|---|
403 forbidden |
the secret is wrong |
400 refused |
the customer is wrong |
429 throttled |
too many attempts, with Retry-After |
Three rules this door cannot bend
The secret is checked before any credential work, so a caller who cannot
prove who it is learns nothing — not even whether an e-mail address exists.
No session cookie is ever set, because the other server holds its own session
and a second one here would be a second truth. And it never answers 401:
the operator reading the log needs 403 and 400 to wake different people.
A staff member who signs in through this door acts for nobody and gets an
empty accesses list. That is an answer, not a refusal — the door does not
judge who a person is.
Every machine sign-in writes a trail row with the caller's address. The secret is never part of it.