Provision a Server from config-as-code

A Server's setup — organizations, agents, the API keys they authenticate with, webhook subscriptions, and custom contract types — can be declared in a directory of YAML files and reconciled on every boot. This is the repeatable, reviewable, GitOps-friendly alternative to clicking through admin API calls by hand, and it is what makes AGLedger installable by an agent or an automation pipeline rather than a human at a terminal: point the Server at a directory, and the identities and types it needs come up with it.

Provisioning is additive to the admin API, not a replacement. Anything you can declare here you can also create with POST /v1/admin/orgs, POST /v1/admin/agents, POST /v1/admin/api-keys, and the schema and webhook endpoints — provisioning just makes the whole set declarative and idempotent. The two coexist: provisioned resources are tagged managed_by = provisioning; resources you create through the API directly are not touched by reconciliation.

Validated against API v0.26.1 on 2026-05-27 (Developer Edition, Docker Compose).

The directory

Set PROVISIONING_CONFIG_PATH to a directory and the Server reads it at startup. The layout groups resources by kind:

provisioning/
  orgs/       # orgs, with inline agents and API keys
  agents/     # standalone agents (reference their org by name)
  webhooks/   # webhook subscriptions (reference orgs/agents by name)
  schemas/    # custom contract types (inline, or referencing a JSON file)

A starter set ships in the agledger-ai/install repository under examples/provisioning/. Copy it, edit it, and point the Server at it:

$ cp -r examples/provisioning /etc/agledger/provisioning
$ export PROVISIONING_CONFIG_PATH=/etc/agledger/provisioning

On Docker Compose, set PROVISIONING_CONFIG_PATH in compose/.env and mount the directory into the agledger-api and agledger-worker services. On Kubernetes, declare the content under provisioning.* in the Helm chart's values and it renders and mounts the ConfigMap for you — do not hand-roll the ConfigMap.

Declaring resources

An org can carry its agents and their keys inline, so one file stands up a complete working identity. The natural key is the resource name — keep it stable across reconciles, because renaming creates a new resource.

orgs/example.yaml:

orgs:
  - name: Acme Corp
    # Admin-role key for external integrations. The generated key is returned
    # once in the reconciliation result (and logged at startup) — capture it then.
    apiKeys:
      - role: admin
        label: primary-api-key
        scopeProfile: admin-standard
    # Agents owned by this org (they inherit org_id from the parent).
    agents:
      - displayName: Acme Task Processor
        agentClass: system
        apiKeys:
          - role: agent
            label: processor-key
            scopeProfile: agent-full

Custom contract types are declared the same way — inline, or referencing a JSON Schema file relative to the provisioning root (schemas/). Webhook subscriptions reference their owner by name (ownerType: org matches an org name; ownerType: agent matches an agent's displayName, with ownerOrgName to disambiguate). See the example files for the full shape of each kind.

Reconciliation on boot

The Server reconciles the directory every time it starts. Each resource is created if absent and updated to match the file if present; nothing is deleted unless you opt into pruning (below). The startup log reports exactly what was applied:

{
  "orgs":    { "created": 1, "updated": 0 },
  "agents":  { "created": 2, "updated": 0 },
  "webhooks":{ "created": 4, "updated": 0 },
  "schemas": { "created": 2, "updated": 0 },
  "apiKeys": {
    "created": 3,
    "generated": [
      { "ownerType": "agent", "label": "processor-key",  "apiKey": "agl_agt_…" },
      { "ownerType": "org",   "label": "primary-api-key", "apiKey": "agl_adm_…" }
    ]
  }
}

The generated API keys appear in plaintext exactly once — in apiKeys.generated[] of this result, and in the startup log line that records each key. They cannot be retrieved afterward. Capture them at boot (an init container that reads the log, a Helm --wait hook, or the reload response below) and hand them to whatever consumes them.

Status and hot reload

Confirm what is under management with the status endpoint (platform key):

$ curl -s -H "Authorization: Bearer $PLATFORM_KEY" "$U/v1/admin/provisioning/status"
{"configured":true,"configPath":"/etc/agledger/provisioning","dryRun":false,"prune":false,
 "lastReloadAt":"2026-05-27T21:40:07.141Z","managed":{"orgs":1,"agents":2,"webhooks":4,"schemas":2}}

Edit the YAML and apply the change without a restart — send SIGHUP, or call the reload endpoint:

$ curl -s -X POST -H "Authorization: Bearer $PLATFORM_KEY" "$U/v1/admin/provisioning/reload"
{
  "orgs":    { "created": 0, "updated": 1, "pruned": 0 },
  "agents":  { "created": 0, "updated": 2, "pruned": 0 },
  "webhooks":{ "created": 0, "updated": 4, "pruned": 0 },
  "schemas": { "created": 0, "updated": 2, "pruned": 0 },
  "apiKeys": { "created": 0, "skipped": 3, "generated": [] },
  "errors": [],
  "nextSteps": [
    { "action": "Inspect provisioning status", "method": "GET", "href": "/v1/admin/provisioning/status" },
    { "action": "Capture raw API keys from THIS response", "method": "GET", "href": "/v1/admin/api-keys" }
  ]
}

Reload is idempotent: unchanged resources count as updated, existing keys as skipped, and only genuinely new keys appear in apiKeys.generated[]. Like every AGLedger mutation, the response carries nextSteps so an agent driving the install knows what to do next without reading docs — here, that a non-empty generated[] must be captured now because the plaintext is not retrievable later.

Secrets, dry run, and pruning

Where this fits

Provisioning is the natural next step after installing a Server and minting the platform key: instead of creating each org and agent by hand, declare the whole set as code and let the Server reconcile it. The request/response shapes for the underlying admin endpoints (/v1/admin/orgs, /v1/admin/agents, /v1/admin/api-keys, /v1/admin/provisioning/*) are in the API reference. For rotating and reloading config as a recurring operator task, see day-2 operations.


Validated against API v0.26.1 on 2026-05-27 (Developer Edition, Docker Compose).