API
Keys for programs. Issuers for people.
The same portal the account page describes, called by a program. The installer itself does not use a key. It fetches the label URL. This API is not running.
Three names
The label host, the API host, and the update host are different names so a build URL cannot be mistaken for the API.
- Installer. https://{label}.build.blunix.io/ returns the current age ciphertext, or 404. No query string. TLS required. Cap 256KiB. No key.
- API. https://api.blunix.io/v1 is where accounts and publishers write. This host name is an assumption.
- Updates. https://updates.blunix.io/blunix is the sysupdate manifest generated from the log.
API keys
A key is for a program. A person keeps using the issuer. The account page is where a human mints a key, once that page is open.
- Drawn from the OS CSPRNG. Shown once. Stored only as a SHA-256 hash, compared in constant time. A key is high entropy, so it is not stretched the way a password is.
- Prefix
blx_. Logs may store the hash as a fingerprint. They must not store the key. The exampleblx_exampleon this page is not a key. - Default scope.
hosts:writeandlog:read.log:publishis the image build. An ordinary key cannot append a version.machines:readlists inventory and cannot publish ciphertext.hosts:writemints join tokens.troubleshoot:requestandtroubleshoot:readare not on the default key. - Revocation is immediate. A revoked hash fails closed. Rotation is a new key, then revoke the old one.
- Header only.
Authorization: Bearer. Ablx_token is an account key. Ablx_join_token is a join token for one enrollment, and it is rejected as an account key. A JWT is an access token from an allowlisted issuer. A machine call is an Ed25519 signature, not a bearer key. Anything else is a 401. The key is never accepted in a query string.
OAuth, ready to bind
Ready means the checks are fixed before the client ids exist. It does not mean Authentik, Google, or GitHub are connected.
- Allowlist. Authentik at adsas.id, Google, GitHub. The adsas.id discovery URL is recorded at bind time. Google's issuer is https://accounts.google.com. GitHub user login uses that product's OAuth 2.0 authorization-code endpoints.
- Browser. Authorization code with PKCE (S256). Redirect URLs are an exact https match. No wildcard redirect.
- Token. Issuer, audience, and expiry are checked. The algorithm has to be on that issuer's allowlist.
noneis rejected. A failed check is a refusal, including a missing discovery document. - No client secret in the tree. Those values land when the three apps are created. They do not go in this website.
Routes
Bodies that fail the check are refused with a fixed error. The server does not echo the upload back, and it does not decrypt.
| Call | Who | Result |
|---|---|---|
GET /v1/me |
account | The signed-in subject. No secrets. |
POST /v1/hosts |
account | Register a label. Unique. Empty until publish. |
PUT /v1/hosts/{label}/config |
account | Replace ciphertext. Age armor or age binary only. Plaintext YAML and a shell script are refused. Cap 256KiB. |
GET /v1/hosts/{label} |
account | sha256, size, and time of the current ciphertext. |
DELETE /v1/hosts/{label} |
account | The label stops serving. Machines already installed keep the document they applied. |
POST /v1/keys |
account | Mint a key. The response is the only time the key appears. |
DELETE /v1/keys/{fingerprint} |
account | Revoke that hash. |
GET /v1/log |
public | Rows for a channel. Same record as the log page. |
POST /v1/log |
publisher | Append one version, or one withdrawal. The body is a log row, including https mirrors. An existing version id is refused. A script, a Dockerfile, and a git URL are refused. Ordinary keys are refused. |
PUT /v1/hosts/{label} |
account | Set visibility to public or enrolled. Public is the spoken install. Enrolled serves ciphertext only to a bound machine key. |
POST /v1/hosts/{label}/join-tokens |
account | Mint a blx_join_ token. Shown once. Stored as a SHA-256 hash. Expires. Use cap. Not an account key. |
DELETE /v1/join-tokens/{fingerprint} |
account | Revoke that hash. Immediate. |
POST /v1/machines |
join token, once | Bind an Ed25519 public key to a hostname and a profile. The token is consumed. |
POST /v1/machines/{hostname}/checkin |
machine signature | Report the document sha256 and the image digest. Return the desired generation. No plaintext. |
GET /v1/machines/{hostname}/desired |
machine signature | The same generation, for a machine that woke up. |
GET /v1/machines |
account | Inventory: hostname, profile, key fingerprint, document sha256, image digest, last check-in. No secrets. ?channel=stable&behind=1 lists machines not on the channel head. |
POST /v1/machines/{hostname}/troubleshoots |
troubleshoot:request |
Open a collect. A shell scope and a command field are refused. The machine hears the question on the service page. |
GET /v1/machines/{hostname}/troubleshoots/{id} |
troubleshoot:read, or the machine |
Status. The report body is not in the status line. |
DELETE /v1/machines/{hostname}/troubleshoots/{id} |
troubleshoot:request |
Cancel. A later result is refused. |
POST /v1/machines/{hostname}/troubleshoots/{id}/result |
machine signature | One report, cap 64KiB. Closes the job. |
GET /v1/troubleshoots |
troubleshoot:read |
Open jobs for the account. No report bodies. |
A stolen key can replace your ciphertext. It cannot produce a document that decrypts under a passphrase it does not hold. Revoke the key. The passphrase is never a field on any of these calls. Machine routes and join tokens are the service contract. Build, deploy, and the collect are the platform contract. Nothing is listening.