Skip to content

Deutsch

Local API reference

How your own software on the gateway reads its configuration from the Bootstrap-Agent. This page is a summary; the full contract is local-api-v1.openapi.yaml in the release archive (contract version 1.6, interface_version 1).

Transport and access

  • HTTP/1.1 over a Unix socket, /run/bootstrap-agent/api.sock. The agent opens no network port. The host name in a request is ignored.
  • Access is the socket's file permission: mode 0660, group bootstrap-agent. Membership in that group is the whole grant. Put in it only the processes that need the configuration: it contains the credentials for your target platform, and a call to the read endpoints returns them.
  • The agent logs the calling user ID on every call that changes something.
curl -s --unix-socket /run/bootstrap-agent/api.sock http://localhost/v1/status | jq .

The agent answers with one line of JSON; | jq . indents it.

Example output of cproto-v0.2.0-rc.2 (the shape is real, the values are replaced). Every call with its real answer is on Local API examples.

{
  "interface_version": 1,
  "interface_minor_version": 6,
  "agent_version": "cproto-v0.2.0-rc.2",
  "state": "ready",
  "attestation_mode": "tpm",
  "profile_version": 42,
  "last_checkin": "2026-01-01T12:00:00Z",
  "tpm_transient_avail": 3,
  "tpm_loaded_sessions": 0,
  "tpm_transient_capacity": 3,
  "tpm_transient_used": 0,
  "identity_cert_state": "not_configured",
  "auth_mode": "tpm",
  "auth_mode_source": "server",
  "activation": "listen",
  "profile_ready_marker": "/etc/iot-config/profile-ready"
}

Endpoints

Call Answers
GET /v1/status Always 200 with the agent's state, see below. It never fails: an unanswered status looks like a crashed agent.
GET /v1/profile The whole current configuration, exactly as the platform rendered it. 404 with NO_CONFIG before there is one. If-None-Match with the ETag gives 304.
GET /v1/profile/packages The list of packages in it, with version and hash. Same 404 and 304 rules.
GET /v1/profile/packages/{id} The content of one package, without an envelope. 404 if the package or the configuration does not exist.
POST /v1/profile/packages/{id}/ack Tells the agent that you applied the configuration. Body: {"profile_version": 42, "applied": true} (reason is free text when applied is false; it is accepted, not stored), see applied: false. 204 when recorded, 400 for a malformed body, 409 when the version is not the current one, 404 for an unknown package. The version is checked first: an unknown package with an old version gets 409. A body without applied counts as applied: false.
GET /v1/events Long poll, see Without polling.

The profile can contain secrets

GET /v1/profile and GET /v1/profile/packages/{id} return the configuration unredacted, including the credentials for your target platform, private key included. Only the agent's user, root and members of the group bootstrap-agent can open the socket (mode 0660). Treat everything these calls return as confidential: never write it to a log, a ticket, a chat or a bug report, and never copy it off the gateway. To show a configuration to someone, use the package list (/v1/profile/packages), which carries only ids, versions and hashes. ?pretty=1 does not protect you: it adds certificate summaries, and still prints the private key in full (checked on the lab gateway). Safe calls to copy are on Local API examples; the ones that print secrets are at the end of that page.

Example of the shape of GET /v1/profile (every value is invented and deliberately not real; the structure of content depends on your template):

{"profile_version":42,"packages":[{"id":"example-package","version":42,"sha256":"EXAMPLE-NOT-A-REAL-HASH","content":{"example_key":"EXAMPLE-VALUE-NOT-REAL"}}]}

?pretty=1 on the three read calls adds a hex form of each hash (sha256_hex) and a summary of every certificate (decoded_certs). It does not indent (use | jq .), and it does not hide anything: a private key is printed in full, as without it. The answer is re-assembled, so hash or compare a configuration only in its plain form. sha256 itself is Base64, as the platform sends it.

Two ways to confirm

Your software tells the agent that the configuration is applied in one of two ways. Use the marker file.

The marker file (recommended) ack over this API
What your software does Writes {"state":"connected"} to /run/wlpc/target-confirmed once it is connected to its target, see Confirm the target. POST /v1/profile/packages/{id}/ack with {"profile_version": 42, "applied": true}, once for every package.
What the agent does Acknowledges every package that is not yet acknowledged with applied: true. Records that one package.
What your software needs The group wlpc. The group bootstrap-agent, the package IDs and the current profile_version.

The file is one write and covers every package of every configuration. Call ack yourself only if your software applies packages one by one and must confirm each one separately.

What sends confirmed. Neither way sends it directly. The agent sends confirmed as soon as every package of the current configuration is acknowledged with applied: true, whichever way acknowledged the last one. confirmed sets the device to ACTIVE, and the first confirmed is the one billed step, once per device: not complete, not check-ins, and a repeated confirmed never again (see Ready and confirmed). One agent process sends it once per configuration. A package set back to false and acknowledged again does not make that process send it again; after a restart, the new process sends it once more for the same configuration when a package becomes acknowledged again, on an early check-in (the last two rows below).

When both are used, measured with the agent of cproto-v0.2.0-rc.2 against a test service (cproto/e2e/confirmpaths.sh), each case on a fresh device:

Case What happened
All packages acknowledged over the API, no file confirmed was sent once. A file written afterwards changed nothing: no second confirmation.
Two of five packages over the API, then the file No confirmation after the two. The file acknowledged the other three, and confirmed was sent once.
Four with applied: true, one with applied: false, then the file No confirmation, and no request at all went to the platform. The file then overwrote the false with true, and confirmed was sent once.
applied: false after the confirmation, the file still there, then a restart Nothing was sent for the false. At the start the agent read the file, acknowledged the package with true again, and sent the confirmation once more on an early check-in.
One package set to false and back to true over the API, by the process that had confirmed No second confirmation.
The same after a restart The confirmation was sent once more.

On the lab gateway the file acknowledged all seven packages, and in an agent started on a stored configuration, a package acknowledged again over the API led to an early check-in that carried the confirmation.

An ack for an older profile_version is refused with 409, so software that is still applying an old configuration cannot mark the new one as delivered. Acks are idempotent.

applied: false

  • On the device: the package counts as not acknowledged ("acked": false in the package list), and as long as one package is not acknowledged, no confirmed is sent. The value is stored and survives a restart. A reason is accepted but neither stored nor sent.
  • At the platform: nothing. The agent sends no request for an ack, with true or with false; it only sends confirmed once all are true. The platform cannot tell a configuration that failed from one that is still being applied: it only sees that confirmed does not come. This is a known gap, see Known gaps and limits.
  • The file wins. Whenever the agent reads a connected file (when it is written, after a new configuration, and at every start), it acknowledges every package that is not acknowledged with applied: true, including one your software set to false. Software that cannot apply a configuration must not write the file, and must remove it if it is there.
  • Leaving out applied counts as false. On the lab gateway an ack with only profile_version was answered 204 and the package showed as not acknowledged.

State

state in GET /v1/status is one of:

state Meaning
no_config There is no configuration (yet). This is a normal state, not an error, and it can last for months: a device on a shelf that nobody has claimed stays here. It is also the state after the platform declined the device (not known, not claimed, locked); the reason is in last_error_code. Your software must tolerate it indefinitely, without crashing, blocking its start or raising alarms.
bootstrapping A first setup is running.
ready A configuration is stored and served.
error A local fault: a broken TPM, storage that cannot be written, a missing platform address. A verdict of the platform never puts the agent here.

Other fields: profile_version (an opaque value, compare it only with == and !=; a higher number does not mean newer), last_checkin, last_error_code (left out while there is none), tpm_transient_avail and tpm_transient_capacity (free and total transient TPM slots), tpm_transient_used, tpm_loaded_sessions, identity_cert_state, auth_mode, auth_mode_source, activation (always listen in this agent) and profile_ready_marker (the path of the marker file). Test for a newer field with interface_minor_version.

last_error_code carries the platform's public error code unchanged, or one of the agent's own TPM fault codes, for example TPM_STUCK_POWER_CYCLE_REQUIRED, see TPM slots. A refusal that the platform sends during a later check-in sets it too, and a device that holds a configuration stays ready. The console does not show such a refused contact, see Known gaps and limits; read it here or in the agent's log.

Errors

An error answer is {"code": "..."} with one of NO_CONFIG, NOT_FOUND, MALFORMED_REQUEST, STALE_PROFILE_VERSION or INTERNAL. An unknown path gets 404 with NOT_FOUND. So does a method that a path does not take (for example POST or DELETE on /v1/status, GET on .../ack): 404, not 405. For what to do when a device stays in no_config, see Troubleshooting.

Without polling

You do not need to ask GET /v1/status every second or two to learn about a new configuration. Two ways instead:

  • Long poll. GET /v1/events?profile_version=PROFILE_VERSION waits until the version changes (answer 200 with the new profile_version) or 60 seconds pass (204). If the version you pass is already out of date, it answers at once, so a change between two polls is not missed. A closed keep-alive connection is normal; connect again.
  • A marker file. After every newly stored configuration, and only after it is safely written, the agent replaces the file /etc/iot-config/profile-ready. A systemd path unit reacts to that, see Start your software and confirm. An unchanged check-in does not touch the file.