TRQP: POST /v1/trqp/authorization and POST /v1/trqp/recognition
AgentValet answers Trust Registry Query Protocol v2.0 queries over its own grants. A partner, an auditor, or another organisation’s gateway can ask “is this agent authorised by this authority for this action on this resource” without an AgentValet SDK, and get the effective answer: the one the governed call would get, computed at query time.
Both endpoints implement the v2.0 HTTPS binding as we read it. ToIP publishes no conformance suite, so “TRQP-conformant” is a self-assessed claim by every implementer, including us. What you can rely on is the shape below.
Identifiers
| TRQP field | What AgentValet accepts | Meaning |
|---|---|---|
entity_id | did:web:api.agentvalet.ai:agents:<agent_id>, a bare agt_… id, or a spiffe://… id | The agent |
authority_id | did:web:api.agentvalet.ai (this registry) or did:web:api.agentvalet.ai:orgs:<organisation uuid> (one governing organisation) | Who is being asked |
action | A scope id, verbatim as granted: chat:write, Mail.Read, github:contents.read | What |
resource | A platform id: slack, microsoft-outlook, mcp-… | On what |
The agent’s own DID document (see did:web identity documents) carries #trqp and #trqp-recognition service entries pointing here, which is TRQP’s only discovery mechanism. Because a document never names its organisation, a partner holding only an agent DID uses the root DID as authority_id.
Authorization
POST /v1/trqp/authorization
Content-Type: application/json
{
"entity_id": "did:web:api.agentvalet.ai:agents:agt_7o00saqajyahinuuc6g83",
"authority_id": "did:web:api.agentvalet.ai",
"action": "chat:write",
"resource": "slack",
"context": { "time": "2026-09-16T08:00:00Z" }
}{
"entity_id": "did:web:api.agentvalet.ai:agents:agt_7o00saqajyahinuuc6g83",
"authority_id": "did:web:api.agentvalet.ai",
"action": "chat:write",
"resource": "slack",
"authorized": true,
"time_requested": "2026-09-16T08:00:00Z",
"time_evaluated": "2026-09-16T08:00:01.204Z",
"message": "authorized; endpoint, recipient and external-reach controls apply at call time",
"context": {
"decision": "allow",
"policy_id": "…",
"policy_version": 7,
"evaluated": ["status", "grant", "ttl", "breaker", "child_liveness", "guardrails", "policy", "scope"],
"not_evaluated": ["endpoint", "recipient_constraints", "reach"]
}
}authorized is exactly the answer the broker’s permission check gives for that agent, platform and scope: agent status is active, a grant row exists and is not suspended or expired, the circuit breaker is closed, a child agent’s parent is still live and still covers the scope, no organisation guardrail or policy denies, and the scope is in the grant verbatim.
An approval requirement is authorized: false. TRQP has no third state, and a party asking “may this agent act unilaterally” must hear no. The reason is in message and context.decision is require_approval. Every false carries the broker’s reason verbatim: scope_not_granted, denied_by_policy, denied_by_guardrail, grant_expired, circuit_breaker_open, agent_suspended, agent_revoked, approval_required_by_policy, and so on: the same vocabulary a 403 from POST /v1/actions uses.
What is deliberately not evaluated
resource is a platform, not an endpoint, so the oracle never sees a method, a path or a request body. Three controls that need those are therefore skipped by construction and named in context.not_evaluated on every answer:
- Endpoint patterns: whether
chat:writecoversPOST /api/chat.postMessagespecifically. - Recipient constraints: the grant’s channel, address or domain allow-list.
- External reach: the organisation’s Ask-me-first or Block control on messages leaving the organisation.
They all still apply when the real call arrives. If you need the call-time answer, make the call through POST /v1/actions. There is no context.endpoint or context.body extension to upgrade this endpoint; that would make a coarse answer look precise.
Optional context
context.time: echoed astime_requested. The registry only evaluates now; if the value is more than 60 seconds from the evaluation time the response addscontext.time_requested_ignored: true.context.connection_id: selects a per-connection grant when the agent has more than one account on the platform.
Recognition
Same request shape. recognized is true when entity_id resolves to an agent governed by authority_id, its status is active, and, for a child agent, its parent is still active and its own expiry has not passed. action and resource are required by the protocol, echoed, and not evaluated; * is the conventional value.
{ "recognized": false, "message": "agent_suspended", "…": "…" }Recognition is the cheap “does this agent exist and is it alive” probe; recognized: true with authorized: false is a normal pair.
Who may ask
| Caller | Answered for | Notes |
|---|---|---|
| No bearer (public) | Agents whose organisation has turned on Agent identity documents in Settings, the same toggle that publishes their did.json | Rate-limited to 60 requests per minute per IP. Everything else is a uniform authorized: false / recognized: false with message: "not_public", never a 404, so the public surface reveals nothing about an id or its organisation |
| Agent bearer | Itself only | Same JWT as /v1/actions |
| Owner bearer | Any agent in its organisation | The dashboard’s session token |
An unknown or org-mismatched authority answers not_public on the public path and entity_not_governed_by_authority on a bearer path. A bearer that does not verify is 401.
Every answered query writes an audit row with action: "trqp_eval", the four fields, the answer, the reason and the caller kind. Refusals (not_public), 400, 401 and 429 write nothing.
Errors
RFC 7807 Problem Details, Content-Type: application/problem+json:
| Status | When |
|---|---|
| 400 | A required field is missing or empty, authority_id is not a did:web on this host, entity_id is not a recognised form, or context.time is not RFC 3339 |
| 401 | A bearer was presented and it may not query this entity |
| 429 | Public path rate limit; Retry-After is set |
Freshness
There is no cache. A revocation, grant edit, policy edit, expiry or breaker trip is reflected on the next query, because none of those paths are memoised anywhere in the broker. That is a stronger statement than a stored-tuple registry can make, and it is the reason to point a gateway here rather than at a copy.
Related
- did:web identity documents carry the service entries that point here
- Scopes and permissions for what
actionandresourcemean - The same check answers AuthZEN 1.0 at
POST /v1/authzen/access(agent or owner bearer required) withdecision,reasonandrequire_approval - The full OpenAPI document is held to the live route table by a contract test on every push