Skip to main content

check_request

check_request is the status half of the request lane. Give it a request_id and it tells you where the request stands, how a steward resolved it, and — when an exception was granted — the exception_id to cite on a retry.

It is an operationally pure read: it records nothing, and it requires only the read scope. Protocol alias: check-request.

Subject-verifiedDirect link to Subject-verified

A token sees only its own requests. A request belonging to another token, another workspace, or nothing at all is asset_not_found — all three are indistinguishable, so the tool cannot be used to probe what other agents have asked for.

This is also why the tool needs only read: a token that never held the request scope simply owns no requests, so it fails safe to not-found rather than needing the write scope to be told "no".

Because the subject is a specific token id, a rotated token cannot see its predecessor's requests. Resolve or re-file after a rotation.

InputDirect link to Input

FieldTypeRequiredDescription
request_idstring (UUID)YesThe request_id returned by request_access.

Strict — unknown keys are rejected.

OutputDirect link to Output

A single object (no state discriminator — this is a status read, not a decision):

FieldDescription
request_idEcho.
statusopen, resolved, withdrawn, or expired. Derived at read time — see below.
eligibilityexception_grantable or not_waiverable, as derived when the request was filed.
resolution_kindexception_granted, denied, policy_change_required, or null while open.
exception{exception_id, expires_at, state} when an exception was granted; null otherwise.
assetThe canonical db.schema.table[.column] key.
scenario_keyThe scenario, or null.
purpose_categoryThe purpose category recorded on the cited authorization, or null.
created_atWhen the request was filed.
resolved_atWhen it was resolved, or null.
request_expires_atThe request's own 30-day expiry. Distinct from the exception's expiry.

Never returned: the request note, the steward's resolution note, and the steward's identity. Notes exist only in a protected admin/owner store, and no MCP surface echoes them.

Statuses are derived at read timeDirect link to Statuses are derived at read time

An open request past its request_expires_at reports expired, whether or not a background sweep has stamped the row yet. You always get the truth, not a stale stored value.

Exception state is derived at read time tooDirect link to Exception state is derived at read time too

When resolution_kind is exception_granted, exception.state is one of:

StateMeaning
activeLive and consumable right now.
expiredPast its expires_at. A retry citing it will not verify.
revokedA steward revoked it. Immediate and terminal; there is no un-revoke.

This is read from the live exception row, never from an assumption stored at grant time.

Two expiries, do not confuse themDirect link to Two expiries, do not confuse them

ExpiryFieldSet byDefault
The request expiryrequest_expires_atServer, at creation30 days, not configurable
The exception expiryexception.expires_atThe steward, at grant30 days by default in the app, 90 days maximum

A request can expire while unattended; an exception expires after it was granted. They are independent.

Consuming a granted exceptionDirect link to Consuming a granted exception

A granted exception is consumable. Retry the original authorize_use call with:

"satisfied_conditions": [{ "kind": "approval", "exception_id": "<exception_id>" }]

The server verifies it in SQL before anything is satisfied: subject must be the calling token, it must be live, and its asset, scenario, and purpose scope must match. Gate your automation on the retry's can_proceed_now, never on the mere presence of an exception_id here.

Two resolutions produce no exception at all, by design:

  • denied — terminal. Re-filing later is allowed; published state may legitimately have changed.
  • policy_change_required — the remedy is authoring and republishing. Retry authorize_use after the next publication. It is a first-class outcome rather than a dead end, and it is the constructive resolution available for answers a waiver can never fix: conflicted published state, missing or unresolved coverage, and hard denies. (A steward may still choose denied for any of those instead.)

ExampleDirect link to Example

{ "request_id": "b1d4c0a7-5e88-4d21-8a0e-3c6b9f2e1d55" }

While a steward has not looked at it yet:

{
"request_id": "b1d4c0a7-5e88-4d21-8a0e-3c6b9f2e1d55",
"status": "open",
"eligibility": "exception_grantable",
"resolution_kind": null,
"exception": null,
"asset": "master.public.customers",
"scenario_key": "residency.cross_border_transfer",
"purpose_category": null,
"created_at": "2026-07-29T10:20:00.000Z",
"resolved_at": null,
"request_expires_at": "2026-08-28T10:20:00.000Z"
}

After a grant:

{
"request_id": "b1d4c0a7-5e88-4d21-8a0e-3c6b9f2e1d55",
"status": "resolved",
"eligibility": "exception_grantable",
"resolution_kind": "exception_granted",
"exception": {
"exception_id": "0b7a9e2c-6c1f-4f5f-9f2e-2a2d0c9f4a11",
"expires_at": "2026-08-28T12:00:00.000Z",
"state": "active"
},
"asset": "master.public.customers",
"scenario_key": "residency.cross_border_transfer",
"purpose_category": null,
"created_at": "2026-07-29T10:20:00.000Z",
"resolved_at": "2026-07-29T12:00:00.000Z",
"request_expires_at": "2026-08-28T10:20:00.000Z"
}

After a revocation, the same call returns the same shape with exception.state: "revoked" — and a retry citing that id answers not_verified.

Polling guidanceDirect link to Polling guidance

A request is a human review, so poll on a human timescale — minutes, not seconds. Every call consumes one metered MCP call. Terminal states (resolved, withdrawn, expired) never change again, so stop polling once you reach one; the only state worth re-reading after that is a granted exception's state, which can move from active to expired or revoked.

ErrorsDirect link to Errors

CodeWhen
asset_not_foundThe request does not resolve for this token — unknown, foreign-workspace, and foreign-token cases are indistinguishable.
invalid_parametersA non-UUID request_id or an unknown key. Not metered.