Outcome
Grant an active same-organization app member access to one workspace.
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. Workspace-access operations require a consistent active organization context and target workspace authority; metadata discovery does not prove execution permission.
-
Discover the existing canonical workspace in the exact application/organization and intended recipient. These actions do not provision identities, organization memberships, application seats or entitlement.
-
Review the validated plan and intended effect before confirmation. Capture current owner and member grants before a write. Review new-owner eligibility and previous-owner retention separately before transferring ownership.
Discover the target
topolo whoami --json
topolo apps --json
topolo actions get app_topolo_auth.workspace_access.members.grant --detail --json
Read app_topolo_auth.workspace_access.list_mine for this organization/app; copy data.resources[].id. For management read workspace_access.members.list. Recipient candidates require current active membership and app access.
Input fields
| Field | Required | Meaning |
|---|---|---|
resourceId | Yes | Canonical workspace id copied from workspace_access.list_mine data.resources[].id, not a resourceId property. Use a current active workspace in the exact organization/application. |
organizationId | Yes | Exact organization owning this workspace. It is checked against both the authenticated context and canonical workspace record, independently of CLI selection. |
appId | Yes | Canonical owning application id. This operation does not assign application entitlement or create a seat. |
userId | Yes | Existing recipient with active membership in this organization and application access. Directory entries are candidates, not proof of eligibility; the write checks actual membership and entitlement. Current owner access cannot be revoked as a member grant. |
scope | No | Workspace access level read, read_write or admin; defaults to read_write. Repeated grant replaces this scope and preserves the stored grant id. It creates no ownership or organization membership. |
Effects
- Creates/reactivates one invited_member workspace grant for an eligible same-organization application member, replacing scope.
Interpret the response
Raw target responses use success/message/data/timestamp. Grant returns HTTP 201; the other successes return 200. CLI/MCP envelopes may wrap or project the target response. Epoch-number record times differ from the ISO envelope timestamp.
list_mine data.resources contains canonical workspace records whose identifier is id. accessMode all and canManageAll true require organization owner/super_admin or valid platform authority; ordinary organization admin remains restricted. Owner and explicit unexpired invited_member grants drive the restricted projection.
members.list returns one owner, explicit member grants and at most 500 directory candidates. This directory has no continuation and is not guaranteed exhaustive. A candidate may still fail active membership/app assignment validation. No returned user data should be exported beyond the authorized workflow.
Member data.id is the grant id after profile and grant objects are combined; data.userId is the recipient identity id. Owner data.id is its user id. Owner profile can contain only id/name/accessRole when it is absent from the directory, and member profile fields can be absent. Profile activeContext/orgId describe that user context and do not replace the workspace organization boundary.
Grant is a natural-key upsert. Repetition changes scope, clears old revocation, resets expiry/metadata through creation defaults and preserves its id/createdAt. It does not create organization membership, application assignment, a seat or ownership. Audit persistence is nonfatal; successful grant/revoke/transfer does not prove an audit entry.
Member revoke returns userId/resourceId and removes explicit access only. Repetition returns NOT_FOUND. Owner removal returns CONFLICT. Organization-wide authority and independent application permissions still require separate verification.
The workspace owner and ownership projection update in one atomic access-database batch. Removing the new owner redundant grant and retaining/removing the previous owner grants occur afterward in separate writes; the full transfer is not atomic. A 500 can leave a new owner and a redundant member grant while previous-owner retention is unfinished.
A blind retry after partial transfer is unsafe: the former owner may be denied and a transfer to the now-current owner returns CONFLICT. Read members/current ownership first. The public revoke action refuses the current owner even if a redundant owner grant remains; this family has no customer cleanup operation for that partial state. Preserve evidence and seek authorized operator repair rather than inventing an endpoint or bypassing ownership.
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.
Owner grants an eligible member default read_write access
Input · payload.json:
{
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_member"
}
CLI:
topolo actions validate app_topolo_auth.workspace_access.members.grant --data @payload.json --json
topolo actions plan app_topolo_auth.workspace_access.members.grant --data @payload.json --confirm --json
topolo actions call app_topolo_auth.workspace_access.members.grant --data @payload.json --confirm --json
MCP call after validation and plan review:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.workspace_access.members.grant",
"input": {
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_member"
},
"confirm": true
}
}
Captured local handler response · HTTP 201:
{
"success": true,
"message": "Workspace access granted",
"data": {
"grant": {
"id": "rg_Pt2LKxHpEsi-",
"userId": "usr_member",
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceType": "workspace",
"resourceId": "ws_fixture_one",
"grantType": "invited_member",
"scope": "read_write",
"expiresAt": null,
"grantedBy": "usr_owner",
"metadata": null,
"revokedAt": null,
"createdAt": 1790965443
}
},
"timestamp": "2026-10-02T18:24:03.960Z"
}
Repeated grant changes scope and preserves the grant id
Input · payload.json:
{
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_member",
"scope": "read"
}
CLI:
topolo actions validate app_topolo_auth.workspace_access.members.grant --data @payload.json --json
topolo actions plan app_topolo_auth.workspace_access.members.grant --data @payload.json --confirm --json
topolo actions call app_topolo_auth.workspace_access.members.grant --data @payload.json --confirm --json
MCP call after validation and plan review:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.workspace_access.members.grant",
"input": {
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_member",
"scope": "read"
},
"confirm": true
}
}
Captured local handler response · HTTP 201:
{
"success": true,
"message": "Workspace access granted",
"data": {
"grant": {
"id": "rg_Pt2LKxHpEsi-",
"userId": "usr_member",
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceType": "workspace",
"resourceId": "ws_fixture_one",
"grantType": "invited_member",
"scope": "read",
"expiresAt": null,
"grantedBy": "usr_owner",
"metadata": null,
"revokedAt": null,
"createdAt": 1790965443
}
},
"timestamp": "2026-10-02T18:24:03.961Z"
}
Grant remains successful when nonfatal audit persistence fails
Input · payload.json:
{
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_member",
"scope": "admin"
}
CLI:
topolo actions validate app_topolo_auth.workspace_access.members.grant --data @payload.json --json
topolo actions plan app_topolo_auth.workspace_access.members.grant --data @payload.json --confirm --json
topolo actions call app_topolo_auth.workspace_access.members.grant --data @payload.json --confirm --json
MCP call after validation and plan review:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.workspace_access.members.grant",
"input": {
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_member",
"scope": "admin"
},
"confirm": true
}
}
Captured local handler response · HTTP 201:
{
"success": true,
"message": "Workspace access granted",
"data": {
"grant": {
"id": "rg_Pt2LKxHpEsi-",
"userId": "usr_member",
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceType": "workspace",
"resourceId": "ws_fixture_one",
"grantType": "invited_member",
"scope": "admin",
"expiresAt": null,
"grantedBy": "usr_owner",
"metadata": null,
"revokedAt": null,
"createdAt": 1790965443
}
},
"timestamp": "2026-10-02T18:24:03.962Z"
}
Regrant reactivates the same stored grant id
Input · payload.json:
{
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_member"
}
CLI:
topolo actions validate app_topolo_auth.workspace_access.members.grant --data @payload.json --json
topolo actions plan app_topolo_auth.workspace_access.members.grant --data @payload.json --confirm --json
topolo actions call app_topolo_auth.workspace_access.members.grant --data @payload.json --confirm --json
MCP call after validation and plan review:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.workspace_access.members.grant",
"input": {
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_member"
},
"confirm": true
}
}
Captured local handler response · HTTP 201:
{
"success": true,
"message": "Workspace access granted",
"data": {
"grant": {
"id": "rg_Pt2LKxHpEsi-",
"userId": "usr_member",
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceType": "workspace",
"resourceId": "ws_fixture_one",
"grantType": "invited_member",
"scope": "read_write",
"expiresAt": null,
"grantedBy": "usr_owner",
"metadata": null,
"revokedAt": null,
"createdAt": 1790965443
}
},
"timestamp": "2026-10-02T18:24:03.963Z"
}
New owner candidate has an explicit grant before transfer
Input · payload.json:
{
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_new_owner"
}
CLI:
topolo actions validate app_topolo_auth.workspace_access.members.grant --data @payload.json --json
topolo actions plan app_topolo_auth.workspace_access.members.grant --data @payload.json --confirm --json
topolo actions call app_topolo_auth.workspace_access.members.grant --data @payload.json --confirm --json
MCP call after validation and plan review:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.workspace_access.members.grant",
"input": {
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_new_owner"
},
"confirm": true
}
}
Captured local handler response · HTTP 201:
{
"success": true,
"message": "Workspace access granted",
"data": {
"grant": {
"id": "rg_OjH_N222AB5D",
"userId": "usr_new_owner",
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceType": "workspace",
"resourceId": "ws_fixture_one",
"grantType": "invited_member",
"scope": "read_write",
"expiresAt": null,
"grantedBy": "usr_owner",
"metadata": null,
"revokedAt": null,
"createdAt": 1790965443
}
},
"timestamp": "2026-10-02T18:24:03.964Z"
}
Explicit new owner candidate grant before partial-transfer test
Input · payload.json:
{
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_new_owner"
}
CLI:
topolo actions validate app_topolo_auth.workspace_access.members.grant --data @payload.json --json
topolo actions plan app_topolo_auth.workspace_access.members.grant --data @payload.json --confirm --json
topolo actions call app_topolo_auth.workspace_access.members.grant --data @payload.json --confirm --json
MCP call after validation and plan review:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_auth.workspace_access.members.grant",
"input": {
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_new_owner"
},
"confirm": true
}
}
Captured local handler response · HTTP 201:
{
"success": true,
"message": "Workspace access granted",
"data": {
"grant": {
"id": "rg_OjH_N222AB5D",
"userId": "usr_new_owner",
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceType": "workspace",
"resourceId": "ws_fixture_one",
"grantType": "invited_member",
"scope": "read_write",
"expiresAt": null,
"grantedBy": "usr_owner",
"metadata": null,
"revokedAt": null,
"createdAt": 1790965443
}
},
"timestamp": "2026-10-02T18:24:03.965Z"
}
Verify the result
-
Read members.list and compare target members[].userId and scope, keeping its grant id separate from the user id.
-
Read list_mine and an owning-application request as the target principal before claiming workspace access. Verify an unrelated workspace remains denied unless independent authority applies.
Errors and recovery
Directory candidate with suspended membership is refused
Captured local refusal · HTTP 400:
{
"input": {
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_inactive"
},
"response": {
"success": false,
"error": {
"message": "User must be an active member of this organization with app access",
"code": "VALIDATION_ERROR",
"details": {
"userId": true
},
"timestamp": "2026-10-02T18:24:03.957Z"
}
}
}
Unknown recipient is not provisioned
Captured local refusal · HTTP 400:
{
"input": {
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_missing"
},
"response": {
"success": false,
"error": {
"message": "User must be an active member of this organization with app access",
"code": "VALIDATION_ERROR",
"details": {
"userId": true
},
"timestamp": "2026-10-02T18:24:03.958Z"
}
}
}
Current owner cannot receive a redundant member grant
Captured local refusal · HTTP 409:
{
"input": {
"organizationId": "org_example",
"appId": "app_topolo_campaigns",
"resourceId": "ws_fixture_one",
"userId": "usr_owner"
},
"response": {
"success": false,
"error": {
"message": "The workspace owner already has access",
"code": "CONFLICT",
"details": null,
"timestamp": "2026-10-02T18:24:03.959Z"
}
}
}
Published recovery guidance
-
state_conflict(HTTP 409): Read the current resource state, reconcile the intended change, and retry only if still valid. -
VALIDATION_ERROR(HTTP 400): Correct the reported fields and use an existing active same-organization app member. Directory candidates alone do not prove eligibility. -
AUTH_ERROR(HTTP 401): Verify the intended authenticated identity and refresh an expired token if necessary. Do not change credential context automatically. -
FORBIDDEN(HTTP 403): Check consistent active organization context and exact workspace management authority. Personal/conflicting context is refused even with stale org metadata or platform roles. -
NOT_FOUND(HTTP 404): Refresh canonical workspace/member discovery. An already revoked member grant is absent; do not fabricate a stored grant. -
CONFLICT(HTTP 409): Read current owner and grants before reviewing any new decision. Do not grant/revoke the owner as a member or blindly repeat a completed/partial transfer. -
SERVER_ERROR(HTTP 500): Read current owner and member grants before retrying. Ownership may already have changed. Preserve a partial owner/grant state for authorized repair when customer actions cannot complete cleanup.
Recovery boundaries
For a new grant review revoke for the same workspace/user. For replacement capture prior scope and review restoration with grant instead of removing previous valid access. This does not reverse membership/seat/app assignment because none was created.
Discovery and evidence
This record belongs to app_topolo_auth. Local tests prove real canonical workspace/grant SQL and handler responses with simulated identity, directory lookup, memberships and application assignment. They do not prove recipient sign-in, invitation delivery, owning-application resource enforcement, live permissions, deployment or production completion.