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.

Assumed API host: https://api.blunix.io/v1

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.

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.

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.

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.

Design of /v1. No server is listening.
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.