Outcome
Inspect one app record and its saved P2P/resource configuration in the authorized developer console.
GET /api/developer-console/apps/{appId}
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.
This is an authorized developer-console detail projection, not a public App Store listing or the effective credential-scoped executable action catalog. Returned metadata and permission flags are not access grants or execution results.
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 and response
Action: app_topolo_developers.apps.get.
{"appId":"app_docs_fixture"}
The id is fictional. Discover the real app, bind the canonical Developers workspace separately and validate/plan against the current contract before a real call.
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.
Success is data.app: the mapped control app with actorRole/canRead/canEdit/canTransfer plus p2pCapabilities and apiKeyResources read from the integrations database. There is no actions array, mobile-artifacts array, current business data or public listing receipt in this detail projection. Input is exactly {appId:<discovered id>}; no filter, cursor, expansion, arbitrary workspaceId or mutation field is admitted.
The control query filters developer_apps.id and LEFT JOINs submissions by app_id, then uses LIMIT1 without an ordering for multiple joined submissions. The guide does not claim which submission is returned if more than one stored submission joins. Child capability/resource queries occur after the app/workspace/actor checks, include all stored statuses and are not one transaction with the control query. A failure is not converted into an empty success collection by this route.
The app projection includes id,developerId,organizationId,workspaceId,name,slug,description,category,ownerType,portfolio,audience,tenancy,surfaceModel,distributionModel,appStatus,listingStatus,pricing,version,websiteUrl,demoUrl,repoUrl,supportEmail,registeredAt,marketplaceSubcategory,tags,screenshots,notes,metadata,members,transfer,submissionId,submittedAt,reviewedAt,reviewedBy,rejectionReason,createdAt,updatedAt. pricing.monthlyPrice is omitted when the stored amount is null, not serialized as an explicit null.
The mapper preserves legal draft/configured/archived appStatus; other stored values are normalized to configured only when description is at least20 trimmed characters, category at least2 and supportEmail nonempty, otherwise draft. This read-time fallback does not prove the persisted value was repaired. listingStatus is a separate stored/submission/default not_submitted dimension, and neither field proves public availability.
Metadata is normalized with ownership/portfolio/audience/tenancy/surface/distribution defaults and members/transfer projections. Retired service-id keys are removed at the metadata root and registration object, but this is not a recursive secret/safety/privacy sanitizer. Other authored metadata, notes, member and transfer details can remain. Public documentation does not make actual app records public; preserve the legitimate reader audience and treat authored values/URLs as untrusted configuration.
actorRole and canRead/canEdit/canTransfer are calculated from the current actor and stored ownership/membership after workspace selection. They are contextual projections, not durable grants or a guarantee a later write/transfer will succeed. Each mutation has current workspace level, onboarding, profile status and concrete action permission checks. A read of a suspended profile can succeed while writes remain refused.
The appended p2pCapabilities and apiKeyResources are saved configuration projections, not executed providers or caller-specific API-key eligibility. A lastSyncedAt/publishedAt, pricing value, provider URL, default entitlement or approval flag is not proof of delivery, payment, provider health or current resource grants. Obtain key eligibility through supported Auth api_keys.options for a separately authorized key task.
This GET does not create/update the app, publish or deploy it, call P2P, fetch an artifact/provider URL, mint a key or send a message. Shared identity mirror/initial profile writes can precede the detail read or its refusal. Do not delete or modify these records as automatic recovery.
Worked responses
An app readable by its organization administrator
Complete source-derived synthetic UNRUN HTTP200. The actor is an administrator of the owning fictional organization with an authorized selected workspace; this fixture has empty saved capability/resource collections and no joined submission. Source metadata defaults are shown, not a production-ready app. Stored monthly_price is null, so pricing.monthlyPrice is absent.
{
"success": true,
"message": "Developer app fetched successfully",
"data": {
"app": {
"id": "app_docs_fixture",
"developerId": "dev_docs_fixture",
"organizationId": "org_docs_fixture",
"workspaceId": "ws_docs_fixture",
"name": "Fixture app",
"slug": "fixture-app",
"description": "",
"category": "",
"ownerType": "third_party",
"portfolio": null,
"audience": "business",
"tenancy": "organization",
"surfaceModel": "web_app",
"distributionModel": "marketplace",
"appStatus": "draft",
"listingStatus": "not_submitted",
"pricing": {
"type": "free"
},
"version": "0.0.0",
"websiteUrl": "",
"demoUrl": "",
"repoUrl": "",
"supportEmail": "",
"registeredAt": null,
"marketplaceSubcategory": null,
"tags": [],
"screenshots": [],
"notes": "",
"metadata": {
"ownerType": "third_party",
"portfolio": null,
"audience": "business",
"tenancy": "organization",
"surfaceModel": "web_app",
"distributionModel": "marketplace",
"members": [],
"transfer": null
},
"members": [],
"transfer": null,
"submissionId": null,
"submittedAt": null,
"reviewedAt": null,
"reviewedBy": null,
"rejectionReason": null,
"createdAt": "2026-10-21T08:00:00.000Z",
"updatedAt": "2026-10-21T08:00:00.000Z",
"actorRole": "owner",
"canRead": true,
"canEdit": true,
"canTransfer": true,
"p2pCapabilities": [],
"apiKeyResources": []
}
}
}
Compare actual app and workspace identities. The owner flags do not bypass a later mutation’s current authorization/onboarding checks.
An explicitly listed viewer reads the same app
Complete source-derived synthetic UNRUN HTTP200 for an ordinary non-admin actor whose verified email matches the saved viewer membership, in the same authorized organization/workspace. All other shown record fields match the first fixture. No real member record was read.
{
"success": true,
"message": "Developer app fetched successfully",
"data": {
"app": {
"id": "app_docs_fixture",
"developerId": "dev_docs_fixture",
"organizationId": "org_docs_fixture",
"workspaceId": "ws_docs_fixture",
"name": "Fixture app",
"slug": "fixture-app",
"description": "",
"category": "",
"ownerType": "third_party",
"portfolio": null,
"audience": "business",
"tenancy": "organization",
"surfaceModel": "web_app",
"distributionModel": "marketplace",
"appStatus": "draft",
"listingStatus": "not_submitted",
"pricing": {
"type": "free"
},
"version": "0.0.0",
"websiteUrl": "",
"demoUrl": "",
"repoUrl": "",
"supportEmail": "",
"registeredAt": null,
"marketplaceSubcategory": null,
"tags": [],
"screenshots": [],
"notes": "",
"metadata": {
"ownerType": "third_party",
"portfolio": null,
"audience": "business",
"tenancy": "organization",
"surfaceModel": "web_app",
"distributionModel": "marketplace",
"members": [
{
"email": "viewer@fixture.invalid",
"role": "viewer"
}
],
"transfer": null
},
"members": [
{
"email": "viewer@fixture.invalid",
"role": "viewer"
}
],
"transfer": null,
"submissionId": null,
"submittedAt": null,
"reviewedAt": null,
"reviewedBy": null,
"rejectionReason": null,
"createdAt": "2026-10-21T08:00:00.000Z",
"updatedAt": "2026-10-21T08:00:00.000Z",
"actorRole": "viewer",
"canRead": true,
"canEdit": false,
"canTransfer": false,
"p2pCapabilities": [],
"apiKeyResources": []
}
}
}
Retain canRead:true with canEdit:false/canTransfer:false. Reading member metadata or being the developer-profile actor does not override this earlier explicit viewer branch.
App missing or inaccessible in the selected context
Complete synthetic UNRUN HTTP404 for missing app or an app-workspace/actor mismatch after shared context resolution. It does not distinguish inaccessible from absent or prove no initialization happened.
{
"success": false,
"error": {
"message": "Developer app not found"
}
}
Preserve the original context and actual request id; do not guess another organization/workspace or create an app as recovery.
Verify and recover
-
Compare actual data.app.id, organizationId and workspaceId to the separately discovered app/workspace and original credential context. Use the discovered canonical app id unchanged; do not infer an alternate identity from retired metadata keys.
-
Interpret lifecycle/listing/pricing/ownership/actor flags and child declarations as current saved projections. Do not infer public publication, deployed runtime, executed actions, paid rights, provider results or key eligibility from this read.
-
If joined submission or cross-database state is inconsistent, preserve the exact response/source/context for its owner. Do not silently select a different submission or treat absent/empty child data as proof a failed query succeeded.
-
Retain the actual status/error/request id and preserve possible shared initialization. No app/profile/mirror deletion, grant expansion, publication, transfer or business execution is a justified automatic inverse.
Change log
- 2026-10-21: Added the complete app-detail projection, contextual actor flags, lifecycle/listing separation, saved child-configuration and joined-submission consistency limits, with full source-derived synthetic examples. Examples remain unrun; production runtime is not certified.