Skip to main content

Agent Passports

An Agent Passport is a verifiable identity and, once approved, a verifiable credential for one specific agent version. It lets a governance officer decide which configurations are authorised to run, and lets the platform (and any third party who can resolve the credential) verify that an invoked agent was actually approved.

Whether a passport is required at all, for which agents, is controlled per environment by the passport environment setting. This page covers the passport object itself: how it's provisioned, the governance decisions that move it through its lifecycle, how it's checked at invocation, and how it interacts with agent deletion.

Agent Passport vs. passport policy

Environment Settings documents passportMode, the per-environment policy that decides whether a passport is required. This page documents the passport itself: the /v1/passports endpoints, the governance decisions available on it, and its lifecycle.

Prerequisites​

Provision the environment before enabling passports

Setting passportMode to anything other than DISABLED assumes that the environment has already been provisioned for the Credentials API (Passport Service) integration. If it hasn't, agent creation, version creation, and passport governance operations will fail as soon as a passport is requested.

Provisioning establishes the environment's issuing identity and the secure credentials Agent Builder uses to create and manage passports. This is an environment-level prerequisite and can't be completed through the Agent Builder API.

After the integration is provisioned, an Agent Builder Administrator must enable passports for the tenant in Environment Settings. The default passportMode is DISABLED, so no passports are provisioned or enforced until the setting is changed. See Environment Settings for the available modes, permissions, impact preview, and instructions for changing the setting.

Identity and lifecycle​

One DID per agent version​

A DID (Decentralized Identifier) is provisioned for each agent version at the moment it's created (when the tenant's passportMode requires one). Identity is strictly per-version, not per-agent: a new version is a new artifact for governance to approve, so it gets its own DID and, ultimately, its own passport — approving v1 has no bearing on v2. A freshly provisioned version is not invocable yet; it sits at pending_approval until a governance officer decides.

Passport status​

StatusMeaningInvocable?
pending_approvalA DID exists, no credential yet. Waiting for a governance decision.No
issuedApproved; credential issued and verified.Yes — the only invocable status
rejectedGovernance declined. No credential was ever issued.No
issuance_failedApproved, but issuing or verifying the credential failed. The DID is still valid, so approval is retryable.No
suspendedThe credential is temporarily invalid. Reversible: unsuspending restores the same credential.No
revokedThe credential was invalidated, either because governance decided to halt invocations permanently or the agent was deleted. Not reversible, a new version must be created on the agent and reapproved.No

Whether an upstream revocation still needs local reconciliation is tracked separately, as reconciliationRequired on the passport — see Governance decision endpoints.

Valid transitions​

Only pending_approval and issuance_failed are decidable (approve/reject); issued and suspended are the only statuses with a live credential to act on (revoke/suspend/unsuspend):

FromActionTo
(new version, mode ≠ DISABLED)DID provisionedpending_approval
pending_approvalapproveissued
pending_approvalrejectrejected (final)
issuance_failedapprove (retry)issued
issuance_failedrejectrejected (final)
issuedsuspendsuspended
issuedrevokerevoked (final)
suspendedunsuspendissued
suspendedrevokerevoked (final)

An approval that issues a credential but fails to verify it lands in issuance_failed automatically rather than issued — it's retryable because re-issuing against the same DID supersedes the failed attempt.

revoked and rejected are the terminal statuses. The remedy for both is a new agent version, which provisions its own DID and starts a fresh pending_approval passport. Versions are immutable, so there is no "re-review the same artifact" path, and there is currently no renewal/recertification flow for an issued passport that expires or is revoked — a new version is required there too.

Any attempt at an invalid transition (for example, approving a revoked passport) returns 409 Conflict with the message Invalid Passport Transition.

Governance decision endpoints​

Passports are a top-level collection, /v1/passports, keyed by version_id, not agent_id: a passport belongs to one agent version, and version_id is its public identifier.

MethodPathPermissionDescription
GET/v1/passportscin-agent-builder.agents.readList passports for the tenant, oldest-waiting-first by default.
GET/v1/passports/{version_id}cin-agent-builder.agents.readGet the passport for one version, whatever its status.
POST/v1/passports/{version_id}/approvecin-agent-builder.passports.approveApprove and issue a credential.
POST/v1/passports/{version_id}/rejectcin-agent-builder.passports.approveReject. Terminal; no credential is issued.
POST/v1/passports/{version_id}/revokecin-agent-builder.passports.revokeRevoke the credential. Terminal.
POST/v1/passports/{version_id}/suspendcin-agent-builder.passports.revokeTemporarily invalidate the credential. Reversible.
POST/v1/passports/{version_id}/unsuspendcin-agent-builder.passports.revokeLift a suspension, restoring the same credential.
These decision endpoints require specific governance permissions,

cin-agent-builder.passports.approve and cin-agent-builder.passports.revoke

List passports​

GET /v1/passports?status=pending_approval&oldestFirst=true
Query parameterDefaultDescription
statusevery "live" status (all but revoked)Repeatable. Revoked passports are excluded unless requested explicitly with ?status=revoked.
agentId(none)Restrict to one agent's versions.
oldestFirsttrueLongest-waiting-first, for a worklist.
offset, limitstandard pagination

Get one passport​

GET /v1/passports/{version_id}

Returns the passport whatever its status, including terminal (revoked) rows while the parent version still exists. 404 means the version doesn't exist (the agent was deleted) or never had a passport. Under REQUIRED_FOR_NEW, a version without a passport is invocable only when its agent has never had a passport on any version; if another version has one, invocation is refused.

Example response:

{
"agentId": "e992554c-9284-4fe1-8b9c-3e98a717c30e",
"agentName": "claims-processing-agent",
"versionId": "17385687-48d3-4cc7-ac43-d59195d8d35a",
"versionNumber": 2,
"versionLabel": "v2.0",
"isCurrentVersion": true,
"status": "issued",
"did": "did:webvh:QmbeYVaER...:86c13ec1-0f38-442c-9038-73da71532062",
"createdAt": "2026-09-08T20:26:09.573000+00:00",
"credentialUrl": "https://agent-passport.credentials.dev.app.hyland.com/credentials/...",
"issuedAt": "2026-09-09T09:10:00+00:00",
"expiresAt": "2027-09-09T23:59:59.999999+00:00",
"reconciliationRequired": false,
"decidedBy": "Jane Smith",
"decidedAt": "2026-09-09T09:10:00+00:00",
"decisionReason": "Committee approved with conditions",
"config": { "...": "the version's AgentConfig" },
"configHash": "b7e2...",
"modelId": "anthropic.claude-haiku-4-5-20251001-v1:0",
"provider": "anthropic",
"submittedBy": "John Doe",
"notes": "Added claims-lookup tool"
}

reconciliationRequired is true when this platform's record no longer reliably matches what Passport Service considers the credential's true state — for example after a same-instant race between an approval and a concurrent rejection/deletion that this platform could not cleanly undo upstream.

Approve​

POST /v1/passports/{version_id}/approve
{
"expiresAt": "2027-10-09",
"reason": "Committee approved with conditions",
"governanceRecord": {
"autonomyLevel": "L1",
"enforcementMode": "blocking",
"sponsor": "Outpatients Service Owner",
"issuingAuthorityUnit": "AI Governance Committee",
"scopeTags": ["draft-only"],
"complianceClaims": ["dpia"],
"conditions": { "humanReviewRequiredFor": ["patient-facing output"] }
},
"extensions": {
"operatingConstraints": "High-risk actions require human approval before execution."
}
}
FieldRequiredNotes
expiresAtYesDate the credential stops being valid. Bounded by the platform's configured minimum and maximum credential lifetime (defaults: 1–365 days from today).
reasonNoAudit only; returned as decisionReason. Not embedded in the credential.
governanceRecordNoValidated, defined vocabulary terms — see Governance record & credential contents.
extensionsNoOpaque, caller-defined metadata with no vocabulary term of its own.

Approval is issue-then-verify: the credential is issued and then verified before the decision is recorded locally. A credential that signs but fails verification lands the passport in issuance_failed instead of issued, and the call fails with 502 Bad Gateway — retryable, since re-issuing supersedes the failed attempt.

Before issuing anything, approval also checks that the version's stored config still matches the hash recorded when the passport was provisioned. If the config changed since then, the request fails with 409 Conflict ("Agent Config Changed") rather than signing a credential over something nobody actually reviewed.

StatusMeaning
200Approved; credential issued.
403Caller lacks passports.approve.
404Version doesn't exist or never had a passport.
409Not decidable from its current status (including revoked, which is final), its status changed concurrently, or its config changed since the passport was provisioned.
422Missing body, or expiresAt outside the configured bounds.
502Credential issuance failed, or the issued credential didn't verify; the passport is now issuance_failed and approval is retryable.
503No usable Passport Service credentials for this environment — see Prerequisites.

Reject​

POST /v1/passports/{version_id}/reject
{ "reason": "Tool grant exceeds the scope approved for this team" }

Terminal: no credential is ever issued, so reason is required — it's the only durable record the decision produces. 409 for the same reasons as approve (not decidable, or status changed concurrently).

Revoke​

POST /v1/passports/{version_id}/revoke
{ "reason": "Superseded by v3 after the Q3 model refresh" }

Accepts issued and suspended passports (both hold a real, resolvable credential); the other statuses answer 409 since there's nothing to invalidate. Revocation is permanent and invalidates a credential third parties may already have relied on, so reason is required, forwarded to Passport Service, and returned as revocationReason.

Revoking is also the prerequisite for deleting an agent that holds a credential-bearing passport — see Deleting an agent with a passport. Passport Service is asked first and the local status is only flipped once it confirms; a 502 leaves the passport unchanged and retryable.

Suspend / unsuspend​

POST /v1/passports/{version_id}/suspend
{ "reason": "Suspended pending review of incident INC-0000" }
POST /v1/passports/{version_id}/unsuspend
{ "reason": "Incident INC-0000 resolved; no governance concern" }

Only an issued passport can be suspended, and only a suspended one can be unsuspended. Suspension is reversible: the DID and credential both survive, and unsuspending restores the same credential (no re-issuance, credentialUrl/issuedAt unchanged) rather than minting a new one. The approval record (decidedBy/decidedAt/decisionReason) is untouched by either call.

reason is required for both. While a suspension is active it's returned as suspensionReason/suspendedBy/suspendedAt; a successful unsuspend clears those three fields. 409 if the passport isn't in the expected starting status; 502 if Passport Service doesn't confirm the change (retryable, row unchanged).

Governance record & credential contents​

An approved passport's credential embeds two kinds of data under credentialSubject:

Platform-derived, never caller-supplied:

TermWhat it is
tenantId, environmentThe tenant and environment that own this passport.
iamSubjectThe approving principal's verified IAM subject (not a display name) — taken from the token, never the request body, so the credential can't name someone who didn't actually approve it.
passportIdThe version id this credential vouches for.
configHashsha256 over the canonicalised config as it stood at approval.
modelId, providerPinned from the config's llmModelId, when one is set.
capabilities, guardrailsThe tool names and guardrail names the version was granted, listed term-by-term so a reviewer can see what was approved without decoding configHash.

Caller-supplied on approve, via two different fields with different guarantees:

  • governanceRecord — validated, defined vocabulary terms (autonomyLevel, enforcementMode, sponsor, issuingAuthorityUnit, guardrailProfileId, scopeTags, complianceClaims, conditions). An unrecognized key here is rejected outright (422) rather than silently dropped, because it's almost always a governance claim the officer believes is being recorded.
  • extensions — any JSON structure, under the credential's extensions term, which is declared an opaque literal. Nothing here is validated, interpreted, or enforced by the platform — it's a record of what the agent was approved to do, not a control on what it does. Behavior still has to be configured on the agent itself (for example a tool's humanInTheLoopPolicy); the credential doesn't cause anything.
Descriptive, not enforcing

capabilities, guardrails, and configHash make configuration drift detectable by anyone who fetches the credential and re-hashes (or re-reads) the live config — they do not prevent it. The invocation admission gate checks passport status alone; it never re-reads or re-compares the live config or its hash at invoke time. Editing a version's stored config directly (bypassing the API) doesn't block invocation; it only makes the previously-issued credential's claims stop matching reality. The config-hash check described under Approve only protects the moment of approval itself — it stops an officer from unknowingly approving a config that changed after they reviewed it, between provisioning and the approve call.

Passport status on agent responses​

GET /v1/agents and the version endpoints surface a lightweight passport indicator inline, so a client doesn't need a second call just to know if a version is invocable:

  • AgentModel.currentVersionPassport — the agent's current version's passport.
  • VersionModel.passport — that specific version's own passport (so a reviewer can see an issued v1 next to a pending v2).

Both are just { "status": "..." } and are absent (not null) when the version has no passport row at all (legacy, pre-passport version). Full detail — DID, credential URL, decision history — is only on GET /v1/passports/{version_id}.

Invocation enforcement​

When a tenant's passportMode isn't DISABLED, every invocation path (/invoke, /invoke-stream, /invoke-task, /invoke-task-stream, and async runs) checks the resolved version's passport before executing — and does the same, in the same call, for any configured sub agents, however deeply nested. If any one of them is refused, the whole invocation is refused, and the error names the agent that caused it.

A refusal is 403 with a machine-readable errorCode:

{
"status": 403,
"error": "Forbidden",
"message": "This agent version's passport is suspended. It can be reinstated by unsuspending the passport.",
"errorCode": "PASSPORT_SUSPENDED"
}
errorCodeMeaning
PASSPORT_REQUIREDREQUIRED_FOR_ALL, and the agent has never had a passport on any version.
PASSPORT_NOT_PROVISIONEDThis version has no passport, but the agent has one on another version.
PASSPORT_PENDING_APPROVALWaiting for a governance decision.
PASSPORT_ISSUANCE_FAILEDApproved, but credential issuance failed. Retry the approval.
PASSPORT_REJECTEDRejected by governance. Create a new version.
PASSPORT_REVOKEDRevoked. Create a new version.
PASSPORT_SUSPENDEDTemporarily suspended. Ask governance to unsuspend.
PASSPORT_EXPIREDPast its expiresAt. Needs to be approved again (today: via a new version — see Valid transitions).
PASSPORT_REFERENCE_UNPINNEDThe agent references another agent by latest rather than a pinned version id; save a new version to pin it.

Treat any 403 from an invocation endpoint as a refusal even for an errorCode you don't recognize — new codes are added over time. A 403 for a missing permission is a different, unrelated shape: {"detail": "..."}, with no errorCode.

Deleting an agent with a passport​

Deleting an agent is refused while any of its versions holds a credential-bearing passport (issued or suspended) — both statuses have a real credential circulating that third parties may resolve, and letting deletion silently discard that record would leave a dangling, still-valid credential with no way to look it up through this API again (the join that serves GET /v1/passports/{version_id} requires the parent agent/version to still exist).

{
"status": 409,
"error": "Agent Has Issued Passport",
"message": "This agent cannot be deleted while a version holds an issued passport. Revoke it first, then delete the agent. Blocking versions: 17385687-48d3-4cc7-ac43-d59195d8d35a.",
"errorCode": "AGENT_HAS_ISSUED_PASSPORT"
}

To delete such an agent: revoke every blocking version's passport first (POST /v1/passports/{version_id}/revoke, requires cin-agent-builder.passports.revoke), then retry the delete.

Passports in every other status (pending_approval, rejected, issuance_failed) do not block deletion; they're automatically marked revoked as part of deleting the agent, as an audit record.

Permissions summary​

PermissionGrants
cin-agent-builder.agents.readList and read passports (GET /v1/passports, GET /v1/passports/{version_id}).
cin-agent-builder.passports.approveApprove and reject.
cin-agent-builder.passports.revokeRevoke, suspend, and unsuspend.
cin-agent-builder.environment-settings.readRead the tenant's passportMode and preview its impact.
cin-agent-builder.environment-settings.editChange the tenant's passportMode.
  • Environment Settings — the passportMode policy, the generic settings envelope (ETag/If-Match), and the impact preview.
  • Endpoints — the full endpoint reference, including invocation and async runs.