Skip to content

Enrollment Keys

Enrollment keys are short-lived, usage-limited tokens that authorize new agents to register with the Breeze API. Each key is scoped to an organization and optionally pinned to a specific site, ensuring that agents land in the correct place in the multi-tenant hierarchy without exposing long-lived credentials.


Concept Description
Enrollment key A 64-character hex string (32 random bytes) presented by the agent during the POST /api/v1/agents/enroll call.
SHA-256 + pepper The raw key is never stored. It is hashed with a server-side pepper before being written to the database.
TTL (expiresAt) Time-to-live. After this timestamp the key is rejected. Default: 60 minutes from creation.
Max usage (maxUsage) Maximum number of successful enrollments allowed. Default: 1. Range: 1 – 100,000.
Usage count (usageCount) Incremented atomically on each successful enrollment. Once it reaches maxUsage, the key is exhausted.
Site pinning (siteId) When set, every agent that enrolls with this key is placed into the specified site. The enrollment endpoint requires a siteId to be present on the key.
Enrollment secret A separate, static secret (AGENT_ENROLLMENT_SECRET environment variable) that gates the enrollment endpoint in production. This is independent of the enrollment key itself.

Each enrollment key can optionally have its own enrollment secret, separate from the global AGENT_ENROLLMENT_SECRET environment variable. When a per-key secret is set, it takes priority over the global secret during enrollment.

This is useful when you want to restrict enrollment to specific keys with unique credentials – for example, giving each customer site its own secret so that a leaked key from one site cannot be used with another site’s installer.

Per-key secrets are set when creating or rotating a key. If no per-key secret is set, the global AGENT_ENROLLMENT_SECRET is used as usual.


Each enrollment key can generate pre-configured Windows MSI and macOS PKG installers. These installers are pre-loaded with the enrollment key and site assignment, so agents enroll automatically when the installer runs.

When MSI signing is configured on the server, Windows installer downloads are automatically code-signed before delivery — Windows SmartScreen and antivirus tools will recognize them as trusted. See Code Signing for setup details.

From the enrollment key list or detail view, click Download Installer and choose the platform (Windows or macOS). The installer is generated on the fly and downloaded to your browser.

Terminal window
# Download a pre-configured installer for a specific key
curl -OJ https://breeze.yourdomain.com/api/v1/enrollment-keys/KEY_UUID/installer/windows \
-H "Authorization: Bearer $TOKEN"
curl -OJ https://breeze.yourdomain.com/api/v1/enrollment-keys/KEY_UUID/installer/macos \
-H "Authorization: Bearer $TOKEN"

You can generate a shareable, token-authenticated URL so that site contacts can download the installer without needing dashboard access.

Terminal window
# Generate a public download link
curl -X POST https://breeze.yourdomain.com/api/v1/enrollment-keys/KEY_UUID/installer-link \
-H "Authorization: Bearer $TOKEN"
# The response includes a URL like:
# /api/v1/enrollment-keys/public-download/windows?token=...

Share the resulting URL with on-site contacts. Public download links are rate-limited to prevent abuse.

Public installer links are also issued with a short, human-friendly URL of the form https://breeze.yourdomain.com/s/<code>. The short URL automatically detects the requester’s platform and serves the correct installer (Windows MSI or macOS PKG), and increments the enrollment key’s usageCount atomically — so the same maxUsage limit applies whether the installer is downloaded via the long-form link or the short link.

Short codes are convenient for sharing in email, chat, or printed runbooks. The platform is selected per-request from the User-Agent and can be overridden with a ?platform=windows or ?platform=macos query parameter.

In the dashboard, the enrollment key list shows each key’s short code in a Short code column with a copy-to-clipboard button — this replaces the previous always-masked KEY column, which never revealed anything useful. A Hide expired toggle above the list filters out keys that have already expired.


  1. Authenticate with a user session that has the organizations:write permission and has completed MFA.
  2. Send a POST request to /api/v1/enrollment-keys with the desired scope.
  3. Copy the key field from the 201 response. This is the only time the raw key is returned.
  4. Embed the key in your agent installer, deployment script, or MDM payload.
Terminal window
curl -X POST https://breeze.yourdomain.com/api/v1/enrollment-keys \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orgId": "ORG_UUID",
"siteId": "SITE_UUID",
"name": "Chicago Office Q1 Deploy",
"maxUsage": 50,
"expiresAt": "2026-04-01T00:00:00Z"
}'

Request body:

Field Type Required Default Description
orgId uuid Depends Inferred for org-scoped users Target organization. Required for partner and system scopes.
siteId uuid No null Pin enrolled agents to a specific site. The enrollment endpoint will reject keys without a siteId.
name string Yes Human-readable label (1–255 characters).
maxUsage integer No 1 Maximum enrollments allowed (1–100,000).
expiresAt datetime No Now + TTL ISO 8601 absolute expiration. Mutually exclusive with ttlMinutes.
ttlMinutes integer No Now + TTL Lifetime in minutes from creation (1 to 525600, i.e. up to 365 days). Mutually exclusive with expiresAt.

When neither expiresAt nor ttlMinutes is supplied, the key expires after ENROLLMENT_KEY_DEFAULT_TTL_MINUTES (default 60). Supplying both is rejected so the resolved expiry is unambiguous.

POST and PATCH on /enrollment-keys reject any field that isn’t in the table above. A typo like maxUses (instead of maxUsage) now returns 400 Bad Request with a precise field-name error rather than silently dropping the value — useful when scripting key creation against earlier API examples that used different field names.

Response (201):

{
"id": "a1b2c3d4-...",
"orgId": "ORG_UUID",
"siteId": "SITE_UUID",
"name": "Chicago Office Q1 Deploy",
"usageCount": 0,
"maxUsage": 50,
"expiresAt": "2026-04-01T00:00:00.000Z",
"createdBy": "USER_UUID",
"createdAt": "2026-03-02T12:00:00.000Z",
"key": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2"
}

Terminal window
# All non-expired keys for the authenticated org
curl https://breeze.yourdomain.com/api/v1/enrollment-keys?expired=false \
-H "Authorization: Bearer $TOKEN"
# Filter by org (partner/system scope)
curl "https://breeze.yourdomain.com/api/v1/enrollment-keys?orgId=ORG_UUID&page=1&limit=25" \
-H "Authorization: Bearer $TOKEN"

Query parameters:

Parameter Type Description
page string Page number (default 1).
limit string Results per page (default 50, max 100).
orgId uuid Filter by organization.
expired true or false Filter by expiration status.

The response includes a pagination object with page, limit, and total fields.


Rotation generates new key material for an existing record while resetting usageCount to zero. Use it to extend a deployment window or reissue a compromised key without changing the key ID.

Terminal window
curl -X POST https://breeze.yourdomain.com/api/v1/enrollment-keys/KEY_UUID/rotate \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"maxUsage": 100,
"expiresAt": "2026-06-01T00:00:00Z"
}'
Field Type Required Description
maxUsage integer or null No New limit. Pass null for unlimited. Omit to keep the current value.
expiresAt datetime No New expiration. Omit to keep the current value.

The old key value is immediately invalidated. The response includes the new key field.


Enrollment keys are hard deleted from the database. This is irreversible.

Terminal window
curl -X DELETE https://breeze.yourdomain.com/api/v1/enrollment-keys/KEY_UUID \
-H "Authorization: Bearer $TOKEN"

Expired enrollment keys are hard-deleted automatically by a daily sweep that runs at 04:00 UTC. A key is only purged once it has been expired for a grace period of ENROLLMENT_KEY_PURGE_AFTER_DAYS days (default 7), so a recently expired key remains visible long enough to audit or rotate. Keys with no expiration date are never auto-purged. Set ENROLLMENT_KEY_CLEANUP_ENABLED=false to disable the sweep entirely.

Installer bootstrap tokens and deployment invites that reference a purged key are cascade-deleted along with it.

You can also delete all expired keys on demand, without waiting for the grace period:

Terminal window
curl -X POST https://breeze.yourdomain.com/api/v1/enrollment-keys/purge-expired \
-H "Authorization: Bearer $TOKEN"

The endpoint requires the same organizations:write permission + MFA as single-key deletion, deletes every expired key in the caller’s scope (the caller’s org, all accessible orgs for partner scope, or all orgs for system scope), and returns { "success": true, "deletedCount": N }. Keys without an expiration are never matched.

In the dashboard, this is surfaced as a Delete expired button above the key list, with a confirmation dialog before anything is removed.


When an agent starts for the first time, it presents the enrollment key to the API. The full flow is:

  1. The agent sends POST /api/v1/agents/enroll with the raw enrollment key, hostname, OS type, architecture, and agent version.
  2. In production, the API first validates the static enrollment secret (from the AGENT_ENROLLMENT_SECRET env var or x-agent-enrollment-secret header).
  3. The API hashes the enrollment key with SHA-256 + pepper and looks up the matching record.
  4. The API checks that the key has not expired (expiresAt > NOW()) and has remaining usage (usageCount < maxUsage).
  5. usageCount is atomically incremented.
  6. The API verifies the key has a siteId. If not, the enrollment is rejected and the usage increment is rolled back.
  7. A device record is created (or updated if the hostname already exists in the same org + site) with a new agentId and agentTokenHash.
  8. Hardware and network inventory from the enrollment payload is stored.
  9. An mTLS certificate is issued if Cloudflare mTLS is configured for the organization.
  10. The API returns the agentId, deviceId, authToken (a brz_-prefixed bearer token), orgId, siteId, and heartbeat configuration.
// Successful enrollment response (201)
{
"agentId": "hex-agent-id",
"deviceId": "uuid",
"authToken": "brz_a1b2c3d4...",
"orgId": "uuid",
"siteId": "uuid",
"config": {
"heartbeatIntervalSeconds": 60,
"metricsCollectionIntervalSeconds": 30
},
"mtls": null
}

Enrollment keys are hashed using SHA-256 with a server-side pepper before storage. The pepper is read from environment variables in this priority order:

  1. ENROLLMENT_KEY_PEPPER
  2. APP_ENCRYPTION_KEY
  3. SECRET_ENCRYPTION_KEY
  4. JWT_SECRET

In production, at least one of these must be set or the server will refuse to start.

The hash is computed as:

SHA-256( pepper + ":" + rawKey )

This means that even if the database is compromised, the raw enrollment keys cannot be recovered without knowledge of the pepper.

The enrollment endpoint uses two layers of protection:

Layer Purpose Scope
Enrollment secret (AGENT_ENROLLMENT_SECRET) Static gating token validated at the start of the request. Required in production if set. Global – same for all enrollments.
Enrollment key Per-deployment token that determines org, site, and usage limits. Per-key – each deployment batch gets its own key.

The enrollment secret can be passed as the enrollmentSecret field in the request body or via the x-agent-enrollment-secret header.

If an agent with the same hostname already exists in the same org and site, the enrollment endpoint updates the existing device record rather than creating a duplicate. This supports scenarios like OS reinstalls or agent upgrades. However, if the existing device has been decommissioned, re-enrollment is blocked with a 403 error.

Every enrollment key operation is recorded in the audit log:

Action Trigger
enrollment_key.create New key created.
enrollment_key.rotate Key material regenerated. Includes previous and new maxUsage, expiresAt, and usageCount.
enrollment_key.delete Key permanently deleted.
enrollment_key.purge_expired Bulk deletion of expired keys. Includes the deletedCount.
agent.enroll Agent successfully enrolls using a key. Logged with actorType: agent.

Enrollment key management respects the Breeze multi-tenant hierarchy:

User Scope Behavior
Organization Can only manage keys for their own organization. orgId is inferred automatically.
Partner Can manage keys for any organization they have access to. Must provide orgId when managing multiple orgs. If the partner has exactly one org, orgId is inferred.
System Can manage keys for any organization. Must provide orgId.

All management endpoints require organizations:read for listing/viewing and organizations:write + MFA for creating, rotating, and deleting.


Variable Default Description
ENROLLMENT_KEY_DEFAULT_TTL_MINUTES 60 Default time-to-live for new enrollment keys when expiresAt is not specified.
ENROLLMENT_KEY_PEPPER Pepper used for SHA-256 hashing of enrollment keys. Falls back to APP_ENCRYPTION_KEY, SECRET_ENCRYPTION_KEY, or JWT_SECRET.
AGENT_ENROLLMENT_SECRET Static secret that gates the enrollment endpoint in production. If empty or unset, the gate is skipped in non-production environments.
ENROLLMENT_KEY_CLEANUP_ENABLED true Set to false to disable the daily sweep that purges expired keys.
ENROLLMENT_KEY_PURGE_AFTER_DAYS 7 Grace period (in days past expiry) before the daily sweep hard-deletes an expired key.

All routes are prefixed with /api/v1/enrollment-keys and require JWT authentication.

Method Path Permission Description
GET /enrollment-keys organizations:read List enrollment keys with pagination and filters.
POST /enrollment-keys organizations:write + MFA Create a new enrollment key. Returns the raw key once.
GET /enrollment-keys/:id organizations:read Get metadata for a single enrollment key (raw key not included).
POST /enrollment-keys/:id/rotate organizations:write + MFA Regenerate key material, reset usage count. Returns the new raw key.
DELETE /enrollment-keys/:id organizations:write + MFA Permanently delete an enrollment key.
POST /enrollment-keys/purge-expired organizations:write + MFA Delete all expired enrollment keys in the caller’s scope. Returns the number of keys deleted.
GET /enrollment-keys/:id/installer/:platform organizations:read Download a pre-configured MSI or PKG installer.
POST /enrollment-keys/:id/installer-link organizations:write Generate a public download link for an installer.
GET /enrollment-keys/public-download/:platform Token (public) Download an installer via a public link token.
GET /s/:code Public (short code) Auto-detects platform and redirects to the matching installer download. Increments enrollment-key usage atomically.

The agent enrollment endpoint is separate:

Method Path Auth Description
POST /api/v1/agents/enroll Enrollment secret + enrollment key Register a new agent. No JWT required.

“Invalid or expired enrollment key” (401)

Section titled ““Invalid or expired enrollment key” (401)”

The SHA-256 hash of the provided key did not match any active record, or the key has expired or reached its usage limit. Possible causes:

  • The key was rotated and the old value is being used in the installer.
  • The expiresAt timestamp has passed. The default TTL is 60 minutes.
  • usageCount has reached maxUsage. Check the key details via GET /enrollment-keys/:id.
  • The server pepper changed since the key was created. Rotate or recreate the key.

The AGENT_ENROLLMENT_SECRET environment variable is set in production but the agent did not provide it. Pass the secret via the enrollmentSecret field in the JSON body or the x-agent-enrollment-secret HTTP header.

The provided enrollment secret does not match the configured AGENT_ENROLLMENT_SECRET. The comparison uses timing-safe equality to prevent timing attacks. Verify the secret value in your deployment configuration.

“Enrollment key must be associated with a site” (400)

Section titled ““Enrollment key must be associated with a site” (400)”

The enrollment key was created without a siteId. The enrollment endpoint requires every key to specify which site agents should be placed into. Delete this key and create a new one with a siteId.

“Device has been decommissioned” (403)

Section titled ““Device has been decommissioned” (403)”

An agent with the same hostname exists in the same org and site, but its status is decommissioned. Contact an administrator to either remove the decommissioned record or assign the agent to a different site.

An organization-scoped user attempted a key management operation without a valid orgId in their session. This usually indicates a misconfigured user account.

“No enrollment key pepper configured” (server startup)

Section titled ““No enrollment key pepper configured” (server startup)”

In production, at least one of ENROLLMENT_KEY_PEPPER, APP_ENCRYPTION_KEY, SECRET_ENCRYPTION_KEY, or JWT_SECRET must be set. Without a pepper, enrollment key hashes are vulnerable to rainbow table attacks.

Key appears valid but agent fails to connect after enrollment

Section titled “Key appears valid but agent fails to connect after enrollment”
  • Verify the authToken returned by enrollment is stored correctly in the agent config file.
  • Check that secrets.yaml permissions are 0600 (owner read/write only) and agent.yaml is 0640.
  • Ensure the agent is connecting to the correct WebSocket URL (/api/v1/agents/:id/ws) with the Authorization: Bearer brz_... header.