verify-access response contract
Authoritative shape of the POST /api/agents/verify-access response — verified-agent and anonymous branches.
Authoritative shape of the POST /api/agents/verify-access response. Two branches:
verified-agent (request body included an agentId matching a registered agent) and
anonymous (no agentId — endpoint policy resolves the call). Both branches return 200 OK on
success and emit a verification.* event for the activity feed and blockchain audit trail.
Why two linkage keys (sessionId vs correlationId)? They are not two implementations of the
same concept. sessionId identifies a verified agent's tracked session and threads through PDLSS,
runtime challenge, and token guidance. correlationId pairs anonymous events for activity-feed
linkage where no agent identity exists. Both serve the same role on their branch — the linkage key
for downstream paired events — but they represent two genuinely different identity states by
design.
Common fields (both branches)
{
"success": true,
"access": {
"allowed": boolean,
"accessLevel": "none" | "restricted" | "read-only" | "standard" | "full",
"reason": string
},
"recommendation": "grant" | "deny" | "step_up_required" | "audit",
"recommendationReasons": string[],
"advisory"?: { // present on anonymous responses
"ial": "unverified",
"registrationUrl": string,
"docsUrl": string,
"policy": "deny" | "audit" | "allow_partial" | "allow_full",
"restrictionsExplained": string[]
},
"warningHeader"?: { // present when policy = 'audit'
"name": "X-Astra-Unverified-Warning",
"value": string
}
}access.accessLevel is the band the SDK enforces routes against. v2.3.9 renamed guidance →
restricted to remove the value-name collision with the guidance: {} response object that never
existed (denial branches return none).
recommendation is the four-bucket activity-feed bucket the event is counted under. audit
(v2.3.8+) is a soft-launch grant that allows the call but stamps an X-Astra-Unverified-Warning
header on the merchant's response — distinct from grant (verified) and deny.
Verified-agent branch — request body included agentId
{
"success": true,
"access": { "allowed": true, "accessLevel": "standard", "reason": "..." },
"recommendation": "grant",
"recommendationReasons": ["..."],
"agent": {
"astraId": "ASTRA-...",
"name": string,
"trustScore": number,
"agentStatus": "active",
"blockchainStatus": "verified",
"runtimeChallengeSupported": boolean
},
"sessionId": "uuid", // ← key field (see below)
"tokenGuidance"?: { ... }, // present when SDK opted in
"runtimeChallenge"?: { ... } // present when challenge was issued
}sessionId is the verified-traffic correlation key — it ties this verification event to any
follow-up activity for the same attempt, and is used to record the counterparty's decision via
POST /api/agents/verify-access/{sessionId}/decision.
Anonymous branch — no agentId in request body
Triggered when the calling agent is not registered (or the SDK chose not to send an ASTRA-id). The
endpoint's unverifiedAgentPolicy resolves the response. All four policy values produce the same
wire shape but populate access, recommendation, and advisory.policy differently.
{
"success": true,
"access": { "allowed": boolean, "accessLevel": "...", "reason": "..." },
"correlationId": "uuid", // ← key field, anonymous-only (see below)
"advisory": {
"ial": "unverified",
"registrationUrl": "https://astrasync.ai/register",
"docsUrl": "https://astrasync.ai/docs/agent-access",
"policy": "deny" | "audit" | "allow_partial" | "allow_full",
"restrictionsExplained": ["..."]
},
"warningHeader"?: { ... }, // only when policy = 'audit'
"agent"?: { ... }, // only when caller fingerprint matched a known platform
"recommendation": "grant" | "deny" | "step_up_required" | "audit",
"recommendationReasons": ["..."]
}correlationId is the anonymous-traffic linkage key. The backend mints a fresh
correlationId per anonymous request, persists it on the emitted event, and surfaces it on the
response. SDK reads it onto EnhancedVerificationResult.correlationId. correlationId plays the
same event-pairing role on the anonymous branch that sessionId plays on the verified branch —
they are mutually exclusive by design, not transitional.
Activity-feed event types you'll see
| eventType | When it fires | Carries |
|---|---|---|
verification.completed | Verified-agent grant. | sessionId, recommendation: grant, trustScoreAtRequest |
verification.unverified_audit | Anonymous call, endpoint policy = audit. | correlationId, recommendation: audit, decision: audit |
verification.unverified_allow_partial / verification.unverified_allow_full / verification.unverified_deny | Anonymous call, matching the endpoint's unverifiedAgentPolicy. | correlationId, recommendation matching policy |
verification.agent_not_found | Request body included agentId that doesn't exist. | errorMessage, no sessionId / correlationId |
counterparty.discovery_blocked | Auto-register URL validation rejected a placeholder/non-FQDN URL (v2.3.10). | reason: url_hostname_*, counterpartyUrl |
Click any row in /activity > Recent Activity to expand the full eventData payload — useful
for inspecting the correlationId/sessionId that links events for one attempt.
Contract stability
The shape above is the v2.3.10+ contract. Older SDK releases may see narrower field sets. Specifically:
correlationIdon anonymous responses — added v2.3.10 (defect #34 fix). Mutually exclusive withsessionIdby design (anonymous vs verified branch); not a transitional field.recommendation: 'audit'— added v2.3.8 (defect #26).access.accessLevel: 'restricted'(replaces'guidance') — added v2.3.9 (defect #30).warningHeaderon audit responses — added v2.3.8.
Field additions are non-breaking — older SDKs ignore unknown keys. Removing or renaming a documented field is a breaking change and ships in a major bump.

