Outcome
Read the app’s saved resource-type declarations and publication metadata before using Auth’s caller-specific API key options.
GET /api/developer-console/apps/{appId}/api-key-resources
Before you start
Use the original authenticated organization with the published Developers apps:read permission. Discover an accessible canonical Developers workspace and bind it separately as resource context. Input appId is an accessible developer app record identifier; it is not a workspace id or an access grant.
The concrete route allows suspended developer profiles to read but denies revoked access. It resolves a developer profile and verified workspace, then requires app read access in that workspace. Platform authority, organization administrator status, app membership and developer ownership are distinct source branches; an app outside the selected workspace or invisible to the actor returns404. Do not broaden the organization or grant after a refusal.
These are declaration reads for an app you may administer, not the credential-scoped executable action catalog or the list of actual resources an API key may bind. Saved metadata, published timestamps and displayed capability flags are not effective caller authorization.
The shared request path can insert a local identity-only workspace mirror and, for an enabled organization without a developer profile, create the initial developer profile after resolving a platform-issued workspace. This can precede app access refusal. No canonical Auth workspace is minted, and readOnly metadata is not a promise of zero initialization writes.
All worked responses are source-derived synthetic UNRUN examples. No real developer app, declaration, provider, key, person, private record, publication or other business action was read or changed. The immutable source matches the retained declaration revision; serving-runtime adoption is not independently proven.
Request
Action: app_topolo_developers.apps.api_key_resources.list.
{
"appId": "app_docs_fixture"
}
The identifiers above are fictional. Discover the real app and separately bind the canonical Developers workspace. Validate and plan with the current published contract before a real call. This documentation task has not executed it.
Response and behavior
HTTP200 uses the full envelope {success:true,message,data:{...}}. Read records inside data rather than assuming the top-level response is the collection. Exact JSON schemas below are copied from the current declaration, whose structural permissiveness does not substitute for the source-qualified field meanings.
The shared context first authenticates and finds or initializes a developer profile; a revoked profile is refused and a suspended profile is allowed for these GET handlers. For an ordinary same-organization request, Auth verifies the requested workspace at minimum read level. The existing privileged cross-organization platform context instead uses the seeded developer profile’s workspace and does not honor a requested workspace header on that branch. Both branches still reach the app-workspace match and actor.canRead checks. The app-specific handlers do not require completed onboarding for reads. Missing and inaccessible app records are deliberately indistinguishable404 responses.
App access is evaluated after workspace selection. In the source actor calculation, recognized platform access can produce owner role, an organization mismatch otherwise denies, an administrator of the owning organization is an owner, explicit matching user id/email membership can supply a role, and matching developer ownership is the fallback. A permission string alone does not establish all these context conditions.
No action is executed, no resource provider is fetched, no key is minted or bound, no declaration is published and no Auth projection is synchronized by the selected list handler. Shared mirror/profile initialization remains a separate possible effect; do not delete those records as an automatic rollback.
The exact result is data.apiKeyResources. It contains saved resource-type declarations from developer_app_api_key_resources scoped by app_id. These records describe resource providers and types; they are not actual workspace/site/mailbox instance grants. In particular resourceId is the identifier of a declaration, not automatically the resource-id to bind to a key.
Only appId is used by this GET handler. Its broad generic input contains many empty-schema optional properties, but the handler does not implement filtering, search, cursor, limit or pagination. It selects all matching declarations, ordered updated_at DESC then created_at DESC, with no status filter or id tie-breaker. Draft and archived rows may be returned; equal timestamps do not have a guaranteed stable order.
Each mapped row has resourceId,appId,developerId,organizationId,resourceType,resourcePattern,resourceConcept,canonicalResourceType,resourceLabel,resourceEmptyLabel,defaultEntitlement,exceptionPolicy,syncProvider,status,sourceRevision,lastPublishedAt,lastPublishError,createdAt,updatedAt. There is no workspaceId field in this projection. Default values workspace/enterprise/app_or_organization are mapper fallbacks, not proof of commercial entitlement or effective scope.
syncProvider is parsed JSON metadata, not an executed discovery result. This read does not resolve environment-specific provider URLs, verify reachability, fetch resources, refresh grants or redact/validate every authored metadata field. Treat the values as untrusted configuration; do not follow a supplied URL automatically or put secrets in a provider description.
For a real key operation, use the supported Auth api_keys.options action for the intended application and original organization. Its caller-specific organizationWideScopes, resources, allowedScopes and canCreate decision govern selection. Never union grants or infer key eligibility from this declaration list, an organization-owner label, a defaultEntitlement string or a published timestamp.
Worked responses
One saved resource-type declaration
Complete synthetic UNRUN HTTP200 with one draft configuration row. resourceId identifies the saved declaration; this example grants no access to an actual workspace and supplies no real provider or credential.
{
"success": true,
"message": "API key resources fetched successfully",
"data": {
"apiKeyResources": [
{
"resourceId": "res_docs_fixture_workspace",
"appId": "app_docs_fixture",
"developerId": "dev_docs_fixture",
"organizationId": "org_docs_fixture",
"resourceType": "workspace",
"resourcePattern": "workspace:{id}",
"resourceConcept": "workspace",
"canonicalResourceType": "workspace",
"resourceLabel": "Workspace",
"resourceEmptyLabel": "No workspaces",
"defaultEntitlement": "enterprise",
"exceptionPolicy": "app_or_organization",
"syncProvider": {},
"status": "draft",
"sourceRevision": "rev_docs_fixture_unrun",
"lastPublishedAt": null,
"lastPublishError": null,
"createdAt": "2026-10-21T08:00:00.000Z",
"updatedAt": "2026-10-21T08:00:00.000Z"
}
]
}
}
Interpret data.apiKeyResources[].resourceId only within the verified original app/workspace; no subsequent mutation or execution is authorized.
A successful empty result
Complete source-derived synthetic UNRUN HTTP200 after all authentication/workspace/app checks succeed and the successful query finds no stored declarations. This is not a dependency failure or an inaccessible app.
{
"success": true,
"message": "API key resources fetched successfully",
"data": {
"apiKeyResources": []
}
}
Retain the empty result in its real context; do not create or publish a declaration to make it nonempty.
App unavailable in this context
Complete source-derived synthetic UNRUN HTTP404 for a missing app or a workspace/actor visibility mismatch after shared context resolution. This is deliberately not proof of global nonexistence. Initialization may already have occurred.
{
"success": false,
"error": {
"message": "Developer app not found"
}
}
Preserve the original identity, workspace and request id. Do not try another organization or guess identifiers to bypass the access decision.
Verify and recover
-
Read data.apiKeyResources and distinguish each declaration resourceId from a real resource instance id. Compare appId and organizationId to the actual app and original context; an empty list is not an API-key authorization decision.
-
Inspect status and publication/error metadata as saved configuration only. Do not infer provider health, live Auth registration, caller scopes, paid entitlement or resource binding from this projection.
-
Use the independently discovered Auth API-key options contract for a separately authorized key task. Preserve actual refusals and source/configuration inconsistencies for the owner instead of widening scopes, republishing declarations or invoking a provider.
The list handler has no declaration mutation to undo. Its shared initialization effects are separate; automatic deletion of a developer profile or mirror is not a recovery step.
Change log
- 2026-10-21: Added source-qualified wrapped responses, original app/workspace access, declaration-versus-effective-authorization semantics, precise collection behavior and complete synthetic examples. Examples remain unrun.