Skip to content

Reference

alter design

Validate the ALTER_INTEGRATION.md design document before provisioning.

The design document (ALTER_INTEGRATION.md) records how an integration uses Alter — the goal, the runtime identity (app vs agent), the key type, principals, providers, and how grants are acquired — in machine-checkable YAML frontmatter. alter design validate runs a deterministic rule set over it so identity mistakes (for example, an app runtime key serving an agent workload) fail before anything is provisioned. Pair with alter verify, which re-validates the design and then checks the implementation against it.

alter design validate [--design <path>] [--remote] [--app <app-id-or-name>] [--output json|jsonl|table]

ALTER_INTEGRATION.md is a file committed at the repository root. YAML frontmatter carries the machine-checkable fields; the prose below it explains the intent. The Alter Docs MCP server returns a complete, already-valid starter template for each setup flow, so the frontmatter never has to be written from scratch: get_started includes it in the flow plan, and next_step repeats it at the design step.

Each template below is the exact artifact that server returns. Copy the one matching the goal and replace its values.

goal: user-data — call an API as the signed-in user

Section titled “goal: user-data — call an API as the signed-in user”
---
use_case: "Post as our Slack app bot in a workspace a user connects"
goal: user-data # user-data | backend-secret | agent
runtime_identity: app # app | agent — WHO makes the provider call
key_type: runtime # runtime (alter_rk_…) | agent (alter_ak_…) — must match runtime_identity
principals: [user] # user | group | system | agent — never mix user and agent.
# Names the Alter grant OWNER, not the upstream token principal.
providers: [slack] # the OAuth providers this design calls
grant_acquisition: connect-consent # user-data always acquires grants via browser consent
idp: none # none | jwt-resolution (jwt-resolution must be listed in dashboard_steps)
dashboard_steps: [] # operator-owned human actions, listed honestly
language: typescript # python | typescript — the two languages with SDK wiring
---
Why: the signed-in user approves the workspace installation, and Alter binds
the grant to that user. Slack API calls use the installed Slack app bot; this
flow does not request a Slack user token or post as the human.

Here, principals: [user] identifies the Alter grant owner. It does not claim that every provider issues a human-user token: Slack’s standard Alter adapter stores an installed app-bot token, while other providers may issue a credential whose upstream principal is the user. The provider reference is authoritative.

goal: backend-secret — a service holds its own credential

Section titled “goal: backend-secret — a service holds its own credential”
---
use_case: "Call the Stripe API from our billing worker"
goal: backend-secret # user-data | backend-secret | agent
runtime_identity: app # app | agent — WHO makes the provider call
key_type: runtime # runtime (alter_rk_…) | agent (alter_ak_…) — must match runtime_identity
principals: [system] # system | user | group — an agent-bound secret is the agent flow
providers: [] # OAuth providers only; a managed secret is not listed here
grant_acquisition: managed-secret-grant # backend-secret always acquires via a managed-secret grant
idp: none # none | jwt-resolution (jwt-resolution must be listed in dashboard_steps)
dashboard_steps: [] # operator-owned human actions, listed honestly
language: typescript # python | typescript — the two languages with SDK wiring
---
Why: the billing worker calls Stripe as itself. There is no end user in the
loop, so the credential is a managed secret bound to the workload.

goal: agent — an agent calls under its own identity

Section titled “goal: agent — an agent calls under its own identity”
---
use_case: "Let our support agent read threads available to an installed Slack app bot"
goal: agent # user-data | backend-secret | agent
runtime_identity: agent # app | agent — WHO makes the provider call
key_type: agent # agent (alter_ak_…) — an agent identity must NOT use an app runtime key
principals: [agent] # never mix user and agent on one credential path
providers: [slack] # the OAuth providers this design calls
grant_acquisition: agent-delegation # agent-delegation | managed-secret-grant (the latter is CLI/portal-only)
idp: none # none | jwt-resolution (jwt-resolution must be listed in dashboard_steps)
dashboard_steps: [] # operator-owned human actions, listed honestly
language: typescript # python | typescript — the two languages with SDK wiring
---
Why: the agent acts under its own Alter identity so the audit trail attributes
each call to the agent, not to the application. The delegated Slack credential
still represents the installed app bot, not the human who approved it.

Each template’s inline comments are scoped to its own goal, because the accepted value depends on the goal. The tables below give the full range.

FieldRequiredAccepted values
use_caseyesFree text — one sentence describing the integration.
goalyesuser-data, backend-secret, agent
runtime_identityyesapp, agent — which identity makes the provider call.
key_typeyesruntime, agent — must match runtime_identity.
grant_acquisitionyesconnect-consent, managed-secret-grant, agent-delegation
idpyesnone, jwt-resolution
languageyespython, typescript
principalslist — required for every goaluser, group, system, agentuser and agent never share one credential path. Each goal constrains the set; see the table below.
providerslistOAuth provider ids this design calls. Required for goal: user-data. A managed secret is not listed here.
dashboard_stepslistHuman, operator-owned steps (for example identity-provider setup).

The goal decides which identity makes the provider call, which key that identity carries, and how the credential is acquired. Every combination outside this table is a rule violation:

goalruntime_identitykey_typeprincipalsgrant_acquisition
user-dataappruntimemust include userconnect-consent
backend-secretappruntimeat least one of system, user, group; never agentmanaged-secret-grant
agentagentagentexactly [agent]agent-delegation, or managed-secret-grant — the latter is valid but emits a warning, because an agent-bound managed-secret grant can only be created with alter managed-secrets grants create --principal-type agent or the dashboard, never the SDK. Exit code stays 0.

A user-delegated OAuth grant consumed by an agent is still goal: agent with principals: [agent]. principals records the identity on the runtime credential path, not who consented — the user’s consent is recorded by grant_acquisition: agent-delegation.

Validates the design document. Local by default — no network and no login required, so it can gate automation.

Offline exit codes: 0 passed (warnings allowed); 1 rule violations, or a design file that exists but cannot be read; 2 a bad flag or --output value; 4 the design file was not found. Adding --remote can also produce the standard authenticated codes — 3 not signed in, 7 forbidden (e.g. insufficient PAT scope) — plus 5/6 from the backend, and 2/4 when the --remote app itself cannot be resolved or found. See exit codes before branching on them in CI.

FlagDefaultDescription
--design <path>./ALTER_INTEGRATION.mdPath to the design document.
--remoteoffAdditionally verify the design’s providers against the app’s actual configuration, when the design lists any. With an empty providers list the remote check is a no-op and nothing is contacted. Otherwise requires alter auth login and an app (pass --app, link a workspace, or set ALTER_APP_ID).
--app <app-id-or-name>resolved appApp for --remote; falls back to ALTER_APP_ID or the linked workspace.
--output <format>jsonjson, jsonl, or table. Because the report is one object, jsonl falls back to pretty-printed JSON.

The rules enforced include:

  • Identity ↔ key typeruntime_identity: agent requires key_type: agent, a scoped agent key (alter_ak_…) from alter keys mint --key-type agent; runtime_identity: app requires key_type: runtime (alter_rk_…, from alter keys mint). A managed-agent-bound key from alter agents mint-key is a separate credential with a legacy prefix and does not satisfy key_type: agent.
  • Goal ↔ runtime identityuser-data and backend-secret run as the app; agent runs as the named agent. Identity and key agreeing with each other is not enough: an internally consistent app + runtime pair still cannot implement goal: agent.
  • Agent principalgoal: agent requires exactly principals: [agent]. User, group, and system principals are separate credential paths and belong to their own design.
  • Backend-secret principalgoal: backend-secret requires at least one system, user, or group principal and never agent. A design that names no principal never says who holds the secret.
  • Goal ↔ grant acquisitionuser-data acquires grants via browser consent; backend-secret via managed-secret grants; agent via delegation or an agent-bound managed-secret grant (created via the CLI or dashboard — never the SDK).
  • Supported SDK languagelanguage must be python or typescript. Any other value is an error because the next onboarding step cannot produce SDK wiring for it.
  • Principal hygieneuser and agent principals never share one credential path.
  • Human-owned trust steps declaredidp: jwt-resolution requires identity-provider setup to be listed in dashboard_steps. The field records that an operator must confirm this trust decision; the operator may run alter identity-providers create or use the dashboard. Identity-provider update and deletion remain dashboard-only.

JSON output reports status, design_path, the rules_version, each violation’s rule, severity, and message, and — only when --remote could not complete — a remote_note explaining why. A --remote run whose authenticated phase failed reports status: "fail" with no remote-provider-configured violation, so check remote_note before treating that as a design problem.

Two rule ids in the violations array come from the command rather than the versioned rule set: parse (the file has no readable YAML frontmatter) and remote-provider-configured (a designed provider is not configured on the app). A consumer switching on rule ids should expect them alongside the rule-set ids below.

rules_version identifies the rule set that produced a report. A stored report is only comparable against the version that emitted it:

rules_versionCLI versionRules
1v0.7.0 – v0.7.3Initial set: identity ↔ key type, goal ↔ grant acquisition, principal hygiene, IDP dashboard step, user-data principal and provider, the backend-secret agent exclusion (already reporting as backend-secret-principal), and the agent managed-secret acquisition warning.
2never releasedlanguage and Gate B’s empty-SDK-scan became errors. Superseded before any release, so no published binary reports 2.
30.8.0Adds goal ↔ runtime identity, the agent principal requirement, and the backend-secret principal presence requirement (its agent-exclusion half has reported as backend-secret-principal since 1). Goal-coherence rules now stay silent for a field that already failed its required or enum check, so one typo reports once.

Rules that reject a previously-passing design ship in a new CLI version, so pin the CLI version in CI if a design must keep validating unchanged.

Report an issue with this page

Necessary

Required for sign-in, security, authorization, and remembering your choices.

Always active

Analytics

Helps us understand which product and documentation features are useful.

Performance diagnostics

Uses performance tracing and privacy-masked session replay to diagnose problems.

You can change these choices at any time from Cookie settings.