apd configuration
Configuration is a JSON file (--config, default apd.json) with environment
overrides. apd example-config prints a starting point. Unknown fields are
rejected, so typos fail loudly at startup.
Fields
| Field | Type | Default | Notes |
|---|---|---|---|
issuer |
string | — (required) | The AP’s server identifier. Must be https://host (lowercase, no port/path/trailing slash) unless insecure_dev_mode. This exact URL must serve the well-known documents. Goes into every token’s iss, and its host is the domain of issued agent identifiers. |
listen |
string | 127.0.0.1:8420 |
TCP bind address. |
keys_file |
string | apd-keys.json |
AP Ed25519 signing keys (see apd keygen). Secret; share across instances. |
storage |
object | memory | See below. |
agent_token_ttl_secs |
int | 3600 |
Agent-token lifetime. Must be 1..=86400 (spec ceiling 24h). |
subscribe_token_ttl_secs |
int | 86400 |
Subscribe-token registration window. |
signature_window_secs |
int | 60 |
Allowed skew for the HTTP-signature created timestamp. |
naming_jwt_max_lifetime_secs |
int | 300 |
Max accepted exp-iat on two-key refresh naming JWTs; also the replay-guard TTL. |
enrollment.methods |
string[] | ["token"] |
Enabled enrollment gates, any of token, federated, allowlist, open. Evaluated per request as assertion → token → allowlist → open; a presented-but-invalid credential never falls through. (Legacy enrollment.mode string still accepted.) |
enrollment.trusted_issuers |
object[] | [] |
Trusted assertion issuers for the federated method — OIDC discovery, direct/inline/file JWKS, or x5c CA bundles, with audience/claim/SAN/cnf policy and embed_claims. Full field reference and per-environment recipes: federated-enrollment.md. |
enrollment.static_tokens |
object[] | [] |
Predefined static enrollment tokens for the token method — { "token": "...", "ps"?: url, "label"?: string }. Reusable (unlike minted tokens) and live as long as the config: a dev/staging convenience so agents can enroll with a known token (docker-compose, CI, local runs) without a runtime mint step. ≥16 chars enforced; compared constant-time; presence announced with a startup warning + static_enrollment_tokens_active audit event; enrollments audit as token_kind: "static". Prefer minted or federated enrollment in production. |
enrollment.default_ps |
url | — | ps bound into tokens when neither the enrollment nor the request sets one. |
admin_token |
string | — | Enables the /admin API. Prefer the APD_ADMIN_TOKEN env var. |
allow_ps_override |
bool | true |
Allow a token request to override the enrollment’s bound ps. |
metadata.* |
strings | — | name, description, logo_uri, logo_dark_uri, documentation_uri, tos_uri, policy_uri — surfaced in aauth-agent.json. |
events.enabled |
bool | true |
Enable subscribe tokens, /events, /inbox, and the event_endpoint in metadata. |
events.inbox_ttl_secs |
int | 604800 |
How long undelivered inbox events / subscription records are retained. |
events.max_pending_per_agent |
int | 1000 |
Inbox cap per agent (oldest dropped). |
events.max_payload_bytes |
int | 65536 |
Max event payload accepted at /events. |
max_body_bytes |
int | 65536 |
Global request-body cap. |
jwks_cross_origin_hosts |
string[] | [] |
Hosts explicitly admitted as cross-origin JWKS hosts when verifying foreign (event) tokens — i.e. a resource whose metadata points jwks_uri at a different host than its issuer (e.g. a CDN). Empty means same-origin JWKS only, per the Signature-Key draft’s requirement that cross-origin JWKS URLs need explicit deployment admission. List bare hostnames, e.g. ["jwks.cdn.example"]. |
audit_log_file |
string | — | Append structured JSON audit events (enrollments, denials, issuance, revocation, allowed-key changes) to this file, in addition to stderr. |
telemetry.enabled |
bool | false |
Enable OpenTelemetry export. Also APD_TELEMETRY_ENABLED=1. See Observability. |
telemetry.endpoint |
url | http://localhost:4318 |
OTLP/HTTP base endpoint of an OTEL Collector; signals go to {endpoint}/v1/traces and /v1/metrics. Env OTEL_EXPORTER_OTLP_ENDPOINT. |
telemetry.service_name |
string | apd |
service.name resource attribute. Env OTEL_SERVICE_NAME. |
telemetry.metric_interval_secs |
int | 30 |
Metric export interval. |
insecure_dev_mode |
bool | false |
Dev only. Allows http:// issuer + ports, and outbound fetches over http / to private/loopback addresses. Never enable in production. |
Storage
"storage": { "backend": "memory" }
"storage": { "backend": "file", "path": "/var/lib/apd/state.json" }
"storage": { "backend": "redis", "redis_addr": "127.0.0.1:6379", "key_prefix": "apd:" }
- memory — per-process; nothing persists. Dev, tests, or a stateless single instance where losing enrollments on restart is acceptable.
- file — memory plus a crash-safe JSON snapshot (atomic tmp+rename) on every mutation. Single host only.
- redis — required for multi-instance. All atomic operations map to Redis
primitives (
SET NX,GETDEL,INCR,RPUSH/LTRIM,MULTI/EXEC). Uses a minimal built-in RESP2 client over plain TCP — run Redis on localhost, a trusted network, or behind a TLS tunnel (stunnel/service mesh). Requires Redis ≥ 6.2 (GETDEL).
Environment overrides
Applied after the file loads: APD_ISSUER, APD_LISTEN, APD_KEYS_FILE,
APD_ADMIN_TOKEN, APD_REDIS_ADDR (setting the last switches the backend to
redis), and APD_STATIC_ENROLL_TOKEN (appends one static enrollment token,
labeled env — keeps dev tokens out of committed config files). Useful for
containers and secret injection — keep admin_token, Redis addresses, and
static tokens out of the committed config.
Validation
At startup apd rejects: a non-conforming issuer, agent_token_ttl_secs
outside 1..=86400, a storage backend missing its required path/address, an
unknown enrollment.mode, and a malformed enrollment.default_ps. Fix the
reported field and restart.
Assurance tiers
Every issued agent token carries an assurance claim so Person Servers and
resources can apply policy proportional to how the agent enrolled. The tier is
derived from the enrollment method:
| Enrollment | assurance |
|---|---|
open |
none |
| static enrollment token | low |
admin-minted token / allowlist |
medium |
federated OIDC / JWKS |
medium |
federated x5c / spiffe |
high |
Override per federated issuer with "assurance": "<tier>" (lowercase
[a-z0-9_], ≤32 chars). Sub-agents inherit their parent’s tier. The claim is
protected — a trusted issuer’s embed_claims cannot forge it.
Admin API authentication
The admin API accepts either a shared bearer token or a token from your identity
provider. Configure both during a migration; configure only admin_oidc once
everyone has moved.
| Field | Default | Meaning |
|---|---|---|
admin_token |
(none) | Shared bearer token. Prefer APD_ADMIN_TOKEN. |
admin_oidc.issuer |
(required) | Your IdP’s issuer URL. The token’s iss must equal it exactly, and it is checked before any key is fetched. |
admin_oidc.audience |
(required) | Required aud. Without it, a token your IdP minted for any other application would administer this provider. |
admin_oidc.required_claims |
(required, non-empty) | Claim path → matcher. This is the authorization gate. |
admin_oidc.principal_claim |
sub |
Which claim names the operator in the audit log. email reads better in a review. |
admin_oidc.jwks_uri |
(discovery) | Explicit JWKS URL, skipping discovery. |
"admin_oidc": {
"issuer": "https://acme.okta.com",
"audience": "apd-admin",
"required_claims": { "groups": "apd-admins" },
"principal_claim": "email"
}
Operators then call the API with a token from the IdP:
curl -X POST https://ap.example.com/admin/agents/<local>/revoke \
-H "Authorization: Bearer $(your-idp-cli token)"
Why this exists. A shared token proves only that the caller holds the secret.
Every action looks identical afterwards, it cannot be withdrawn from one person,
and offboarding means rotating it for everyone at once. With admin_oidc each
action carries the operator’s name:
{"event":"agent_revoked","actor":"oidc:alice@acme.example","local":"k7q3p9n2"}
{"event":"agent_revoked","actor":"static-token","local":"m4x8b1c5"}
The shared-token case is labelled static-token rather than admin, so a
reviewer can see at a glance which actions carried no operator identity.
Per-provider setup — Okta, Entra, Google, Keycloak, Auth0 — is in identity-providers.md, including the Okta groups claim that is absent by default and denies everyone until you add it.
required_claims may not be empty. Authenticating against the company IdP
proves employment, not entitlement — an empty gate would make every account with
a login an administrator. apd refuses to start rather than accept that. Matchers
use the same syntax as trusted issuers: exact, an array of allowed values, or a
trailing * prefix. A multi-valued claim such as groups matches when any of
its values does.
Revocation
| Field | Default | Meaning |
|---|---|---|
revocation.notify_ps |
true |
On POST /admin/agents/{local}/revoke, also call the agent’s Person Server revocation_endpoint. |
revocation.max_tracked_tokens |
64 |
Safety cap on outstanding token identifiers tracked per agent. |
Revocation in AAuth names a token, not an agent: recipients key revocation
state by (iss, jti). apd therefore records each issued jti with a TTL equal
to the token’s remaining life — the index self-prunes, and there is no reaper.
On revoke it sends one signed POST {revocation_endpoint} per outstanding
token, signing as the AP itself with the jwks_uri scheme so the PS can confirm
the caller is the token’s iss.
Local revocation is authoritative. Refusing to re-issue always takes effect,
whatever the PS does. The notification is best effort and its outcome
(sent / disabled / no_ps / no_endpoint / failed) appears in the admin
response and the audit log. Where no revocation reaches a holder, access is
bounded by the token lifetime — which is the argument for keeping
agent_token_ttl_secs short.
Set notify_ps: false to keep revocation purely local; apd then skips the jti
index entirely.
Observability
Set telemetry.enabled (or APD_TELEMETRY_ENABLED=1) to export metrics and
traces over OTLP/HTTP (protobuf) to an OpenTelemetry Collector. Disabled by
default with zero overhead (instruments are no-ops). Standard OTEL_* env vars
(OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_SERVICE_NAME) are honored.
Metrics (scope apd): apd.enroll.total (dimensioned by method,
assurance, result), apd.agent_token.total, apd.subagent_token.total,
apd.verify_fail.total (by route), apd.requests.total (route + status class),
and the apd.request.duration histogram (seconds, by route). Traces: one
SERVER span per request tagged with method, route template, and status code.
Route templates keep per-agent paths from exploding cardinality.
"telemetry": { "enabled": true, "endpoint": "http://otel-collector:4318", "service_name": "apd" }