Outcome
List a bounded page of live grants for one exact application resource the caller can manage.
These examples prove local handler behavior with real isolated SQL and explicitly simulated identity/permission dependencies.
Before you start
-
Verify identity and the current credential-scoped action contract before preparing input. Resource-grant operations authorize the target organization/app at runtime; metadata discovery does not prove execution permission.
-
Discover the recipient and existing application resource. Use one exact organization/app/type/id tuple consistently; a grant never creates the target resource.
Discover the target
topolo whoami --json
topolo apps --json
topolo actions get app_topolo_auth.resource_grants.list_resource --detail --json
Discover the recipient and target resource using owning-application actions. Read app_topolo_auth.resource_grants.list_resource with the exact resource tuple and follow all pages.
Input fields
| Field | Required | Meaning |
|---|---|---|
organization_id | Yes | Exact organization owning the resource. Use the same organization/app/type/id tuple as the grant; this is not an organization-wide recipient listing. |
app_id | Yes | Exact stored application identifier to filter grants. App id and slug are not interchangeable in grant storage or list filters. |
resource_type | Yes | Exact resource kind paired with resource_id. For list_mine both filters must be supplied together; list_resource requires both. |
resource_id | Yes | Exact resource id paired with resource_type; no wildcard discovery of recipients. |
limit | No | Page size from 1 to 100, default 50. Preserve the same filters when following nextCursor. |
cursor | No | Opaque continuation copied from the previous page.nextCursor; do not construct one from examples. Pagination is keyset-based on createdAt/id and is not a snapshot of concurrent grant changes. |
Effects
- Reads a bounded page of recipients for one exact resource the caller may manage.
Interpret the response
The raw target success envelope is success/message/data/timestamp. Create returns HTTP 201; all other successes return 200. CLI and MCP may wrap or project this envelope.
Grant id and createdAt remain stable when create updates the same user/organization/app/resourceType/resourceId key or reactivates a revoked grant. Omitted scope/type/metadata/expiry reset to create defaults rather than preserve previous values. Take a snapshot before replacing an existing grant.
Live lists omit revoked and expired rows. page.hasMore controls continuation; nextCursor is null at the end. list_mine contains only the authenticated subject, never a supplied userId. list_resource additionally returns recipient id/email/name or null when identity lookup is unavailable.
Create data.provisioned means this call created an unverified identity. It does not imply membership, invitation, sign-in, recipient acceptance or resource access. A create with past expiry can succeed with data.grant null because its stored row is already absent from live access.
Grant createdAt, expiresAt and revokedAt are epoch seconds; response timestamp is ISO text. Legacy create expiry strings can remain strings in stored output. Convert dates before planning; never infer live access from a successful storage response.
Update preserves identity and can extend an expired unrevoked grant. A revoked row is refused. Revoke returns only data.id, including on repeated revocation, and does not delete the recipient identity or affect separate membership/other grants.
Promote creates organization membership before revoking all non-workspace resource grants for that user and organization across apps. appId limits the permission check, not the revocation scope. These writes span databases and are not atomic; a 500 can leave membership active while grants remain. Read membership/effective access and remaining grants before any reviewed retry or compensation.
Promote data.role echoes the requested/default role. Existing membership, stored user role and effective authorization require separate verification. Notification delivery is nonfatal and is not proven by success. An empty list_mine after promotion does not mean the user has lost organization access.
AUTH_ERROR (401) is used both for absent/invalid tokens and for authenticated callers lacking organization or app grant authority. Inspect the error and identity before assuming OAuth expired; preserve authorization denials rather than changing credential context automatically.
Worked examples
These are complete raw target-handler responses, not CLI/gateway envelopes or live authorization proof. Replace synthetic identifiers using authorized discovery. Save only the reviewed input as payload.json; inspect validation and plan before executing. --confirm assumes the intended operation is approved. Metadata read-back requires separate recipient-principal and owning-application access tests.
Manager gets first page with recipient identities and continuation
Input · payload.json:
{
"organization_id": "org_example",
"app_id": "app_topolo_people",
"resource_type": "candidate",
"resource_id": "candidate_one",
"limit": 1
}
CLI:
topolo actions validate app_topolo_auth.resource_grants.list_resource --data @payload.json --json
topolo actions plan app_topolo_auth.resource_grants.list_resource --data @payload.json --json
topolo actions call app_topolo_auth.resource_grants.list_resource --data @payload.json --json
MCP call after validation and plan review:
{
"tool": "topolo_read_action",
"arguments": {
"actionId": "app_topolo_auth.resource_grants.list_resource",
"input": {
"organization_id": "org_example",
"app_id": "app_topolo_people",
"resource_type": "candidate",
"resource_id": "candidate_one",
"limit": 1
}
}
}
Captured local handler response · HTTP 200:
{
"success": true,
"message": "Resource grants fetched",
"data": {
"grants": [
{
"id": "rg_JOD6QZYnE1JA",
"userId": "usr_recipient",
"organizationId": "org_example",
"appId": "app_topolo_people",
"resourceType": "candidate",
"resourceId": "candidate_one",
"grantType": "consumer",
"scope": "read_write",
"expiresAt": null,
"grantedBy": "usr_operator",
"metadata": {
"purpose": "interview"
},
"revokedAt": null,
"createdAt": 1790964100,
"recipient": {
"id": "usr_recipient",
"email": "recipient@example.test",
"name": "Recipient"
}
}
],
"page": {
"nextCursor": "1790964100:rg_JOD6QZYnE1JA",
"hasMore": true
}
},
"timestamp": "2026-10-02T18:01:40.145Z"
}
Next page completes exact resource discovery
Input · payload.json:
{
"organization_id": "org_example",
"app_id": "app_topolo_people",
"resource_type": "candidate",
"resource_id": "candidate_one",
"limit": 1,
"cursor": "1790964100:rg_JOD6QZYnE1JA"
}
CLI:
topolo actions validate app_topolo_auth.resource_grants.list_resource --data @payload.json --json
topolo actions plan app_topolo_auth.resource_grants.list_resource --data @payload.json --json
topolo actions call app_topolo_auth.resource_grants.list_resource --data @payload.json --json
MCP call after validation and plan review:
{
"tool": "topolo_read_action",
"arguments": {
"actionId": "app_topolo_auth.resource_grants.list_resource",
"input": {
"organization_id": "org_example",
"app_id": "app_topolo_people",
"resource_type": "candidate",
"resource_id": "candidate_one",
"limit": 1,
"cursor": "1790964100:rg_JOD6QZYnE1JA"
}
}
}
Captured local handler response · HTTP 200:
{
"success": true,
"message": "Resource grants fetched",
"data": {
"grants": [
{
"id": "rg_6nDEPU6LsT4w",
"userId": "usr_second",
"organizationId": "org_example",
"appId": "app_topolo_people",
"resourceType": "candidate",
"resourceId": "candidate_one",
"grantType": "consumer",
"scope": "read",
"expiresAt": null,
"grantedBy": "usr_manager",
"metadata": null,
"revokedAt": null,
"createdAt": 1790964100,
"recipient": {
"id": "usr_second",
"email": "second@example.test",
"name": null
}
}
],
"page": {
"nextCursor": null,
"hasMore": false
}
},
"timestamp": "2026-10-02T18:01:40.146Z"
}
Verify the result
-
Read list_resource for the exact organization/application/type/id and compare recipient, scope and expiry against the intended change; follow all pages.
-
Use the intended recipient principal with list_mine, then verify the owning application accepts only the intended resource/scope and denies an unrelated resource. Metadata read-back alone does not prove resource enforcement.
Errors and recovery
Authenticated recipient cannot enumerate other recipients
Captured local refusal · HTTP 401:
{
"input": {
"organization_id": "org_example",
"app_id": "app_topolo_people",
"resource_type": "candidate",
"resource_id": "candidate_one"
},
"response": {
"success": false,
"error": {
"message": "Not authorized to list grants for this resource",
"code": "AUTH_ERROR",
"details": null,
"timestamp": "2026-10-02T18:01:40.150Z"
}
}
}
Published recovery guidance
-
VALIDATION_ERROR(HTTP 400): Correct reported fields. Pair resource filters, preserve pagination cursors and use workspace access actions for workspace grants. -
AUTH_ERROR(HTTP 401): Inspect identity and error message: invalid token and insufficient grant authority both use 401. Refresh an expired token when necessary; obtain intended access through its authorized owner for permission denials. -
NOT_FOUND(HTTP 404): Rediscover the exact live resource grant. Revoked rows cannot be updated; creation reactivation is a separate reviewed access grant. -
SERVER_ERROR(HTTP 500): Inspect actual stored state before retrying. Create can fail after identity provisioning; promote can fail after membership creation. Correct unsupported SQL-constrained scope/type input; do not treat a failure as proof that nothing changed.
Recovery boundaries
Read-only; do not widen the organization or resource filter to recover from a denial.
Discovery and evidence
This record belongs to app_topolo_auth. Local tests prove real resource-grant SQL and handler responses with simulated identity, permissions, identity lookup/provisioning and stored-role synchronization. They do not prove recipient sign-in, invitation delivery, owning-application resource enforcement, live permissions, deployment or production completion.