Action: app_topolo_developers.placements.list.
Outcome
Read a bounded stored placement collection in an accessible consumer workspace, with exact filters and snapshot limits.
GET /api/developer-console/placements
Audience and access
This public guide documents a customer-facing read of private organization placement data. Use a person session in the intended active organization with app_topolo_developers.connections:read. The current descriptor declares static authorization, allowedServices:[] and no principalAccess override; that metadata does not grant a service, API key or workspace bypass.
The handler derives organizationId from the verified user; none of these action inputs accepts an organizationId. Authentication delegates to native verifyRequest and resolves the Developers app identity. An API-key workspace header is passed to native Auth for validation, never treated locally as a grant. The placement access check calls listAccessibleWorkspaces with the original request and organization. Published recovery recommends a person session when Auth cannot answer workspace membership.
Consumer workspace access at read or higher is sufficient for these three stored reads. They do not check a current provider-workspace grant, provider resource permission, active connection, workspace link or reference declaration. Those additional checks belong to separately authorized placement creation/refresh/provider actions. A returned provider reference is not permission to fetch its target.
Selected placement handlers do not call resolveWorkspaceContext or create a developer profile, and contain no placement/store mutations. Native authentication/app identity/workspace helpers remain separate dependencies; this source packet does not certify them as globally zero-write or authorize cleanup of any profile/mirror. Do not infer rollback or delete state after an authentication failure.
Exact published input schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"consumerAppId": {
"type": "string",
"pattern": "^app_[a-z0-9]+(?:_[a-z0-9]+)+$"
},
"consumerWorkspaceId": {
"type": "string",
"pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$"
},
"consumerKind": {
"type": "string",
"maxLength": 120,
"pattern": "^[a-z][a-z0-9_]*(?:\\.[a-z][a-z0-9_]*)+$"
},
"consumerId": {
"type": "string",
"pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]{0,255}$"
},
"status": {
"type": "string",
"enum": [
"ready",
"changed",
"unavailable",
"removed",
"access_removed"
]
}
},
"required": [
"consumerAppId",
"consumerWorkspaceId"
],
"additionalProperties": false
}
Exact published output schema
{
"type": "object",
"required": [
"success",
"data"
],
"properties": {
"success": {
"type": "boolean"
},
"message": {
"type": "string"
},
"data": {
"type": "object",
"additionalProperties": true
}
},
"additionalProperties": true
}
Current complete wire and read behavior
| Field | Current wire meaning |
|---|---|
id, organizationId | Stored placement and owning organization identifiers. |
consumer | Full object: appId, workspaceId, kind, id, all strings. |
slot | String or null. |
reference | Full object: app, workspace, type, id; optional numeric version, omitted when absent. |
referenceString | Canonical formatted reference including its optional pinned version. |
label | Stored string or null; not fetched again. |
status | Stored ready, changed, unavailable, removed or access_removed. |
statusReason | Stored string or null; no closed enum is promised. |
lastCheckedAt | Stored string or null; no freshness limit is imposed by these reads. |
createdBy, createdAt, updatedAt | Stored actor id and lifecycle timestamps. |
removedBy, removedAt | Nullable stored values. Returned live rows have removedAt:null. |
Actual selected success is HTTP200 with the complete envelope {success:true,message:"OK",data:{...}}. There is no top-level timestamp. The published outputSchema stays deliberately open; the field inventory below describes the current source mapper without replacing the native schema with an invented closed contract. OK is the exact English key developers.api.messages.ok, associated by the accepted connection3 packet at the same immutable revision; do not branch on localized message text.
Each PlacementRecord has id,organizationId,consumer:{appId,workspaceId,kind,id},slot,reference:{app,workspace,type,id,version?},referenceString,label,status,statusReason,lastCheckedAt,createdBy,createdAt,updatedAt,removedBy,removedAt. slot,label,statusReason,lastCheckedAt,removedBy,removedAt are nullable. reference.version is omitted for a null/falsy stored reference_version and otherwise Number-converted; it is not emitted as version:null. referenceString is formatReference(reference). Stored timestamps and nullable fields are mapped without freshness or date normalization. Malformed stored values are not revalidated by this mapper.
Status is the stored snapshot ready|changed|unavailable|removed|access_removed. ready is not current provider health or access proof; changed can describe a pinned-version mismatch from a previous check. lastCheckedAt may be null or old and is not restamped by these reads. statusReason is an arbitrary stored string or null, not a new closed reason enum. createdAt/updatedAt are stored lifecycle timestamps, not native provider observation generations.
The stored status removed can describe a still-live placement whose provider resource was previously missing. It is distinct from soft deletion: all returned rows have removedAt:null; rows with non-null removed_at are excluded. removedBy remains a nullable mapped stored field and must not be used alone to infer deletion.
These selected service paths read the control database and call Auth workspace lookup with the caller context. They do not call caller.readAction, a provider action, catalog hydration, entitlement fetch, action partitions, Notify or any placement mutation. defaultPlacementDeps constructs those available adapters, but these reads do not invoke them. Workspace promises are cached per app only within the created caller context; database rows and Auth responses are sequential, not a single cross-system transaction or a current provider check.
Error responses are {success:false,error:{message,code?,details?}}; absent code/details are omitted rather than forced null. Native metadata error codes are recovery guidance and do not guarantee that every shared authentication/permission exception includes that code: the outer ApiError path forwards optional code/details and unexpected failures return HTTP500 with a message only. Preserve actual status/code/details/request correlation rather than inventing a complete success or a missing code.
All examples are complete source-derived SYNTHETIC UNRUN carriers with fictional identifiers, users, labels and timestamps. No private/customer/provider resource was read. Only native action metadata was fetched. Matching sourceRevision identifies source applicability; deployed serving code, runtime behavior and UI adoption were not independently tested.
Required consumerAppId uses the exact native canonical app-id pattern and consumerWorkspaceId uses the native identifier pattern. Optional consumerKind,consumerId,status filters combine with AND. consumerKind and consumerId are independently optional; neither requires the other. status is one of the five stored placement statuses. No defaults are added, and no organizationId, provider filter, cursor, limit, includeRemoved or resource-binding override is accepted in input.
data is exactly {placements:PlacementRecord[],count:number} for this handler; there is no organizationId beside the collection. Organization identity is carried within each returned row. count equals placements.length, not the organization total.
Before querying placements, requireWorkspace asks Auth for the requested consumer app and requires the exact consumer workspace with at least read access. Null/unavailable Auth produces HTTP503 workspace_access_unavailable; a confirmed list lacking that workspace produces HTTP403 workspace_access_required with details.appId and workspaceId. An empty successful placement collection is distinct from either error.
The database query fixes organization,consumer app,consumer workspace and removed_at IS NULL, then applies supplied optional filters. It orders consumer_kind,consumer_id,COALESCE(slot,''),created_at,id and applies LIMIT500. There is no continuation cursor or hasMore. At500 the result may be truncated; never claim a complete export. Narrowing filters changes the query and is not pagination; disjoint resource filters require caller-known resources, not guessed identifiers.
Errors and recovery
The following current metadata entries describe recovery. Raw shared authentication failures may omit their catalog code; preserve the actual response.
[
{
"status": 401,
"code": "authentication_required",
"recovery": "Authenticate again in the intended Topolo environment."
},
{
"status": 403,
"code": "permission_denied",
"recovery": "Use a principal holding the Developers connections permission this action declares."
},
{
"status": 403,
"code": "organization_required",
"recovery": "Select the organization whose connections you manage; connections always act for the active organization."
},
{
"status": 422,
"code": "invalid_input",
"recovery": "Use canonical application ids (app_<owner>_<name>) and only the fields in inputSchema."
},
{
"status": 403,
"code": "workspace_access_required",
"recovery": "You cannot use that workspace (error.details.appId, workspaceId). Use a workspace you belong to, or ask its admin for access."
},
{
"status": 503,
"code": "workspace_access_unavailable",
"recovery": "Topolo Auth could not confirm your workspaces. Retry later; nothing was changed. API keys cannot answer workspace membership: use a person session."
}
]
No selected placement mutation to undo. Correct only invalid inputs; reauthenticate/select the intended organization or restore genuinely authorized consumer access through supported controls as appropriate. Preserve404 concealment,503 uncertainty and used_in silent omission. No automatic grant expansion, refresh/provider call, placement create/delete, source/runtime effect or profile cleanup follows from this guide.
Verification
- Require HTTP200/success:true before using data. Confirm count===placements.length and each row’s original organization/consumer tuple.
- Treat500 as possibly capped and an old/null lastCheckedAt as an old/unknown snapshot; zero rows does not prove provider absence.
- Apply source-derived nullability and preserve absent version rather than substituting0 or null.
Complete synthetic UNRUN carriers
Complete stored changed placement collection
COMPLETE SYNTHETIC UNRUN HTTP200. The label and pinned version are stored snapshots; no provider read occurs. Every mapped placement field is present, with explicit nullable values.
Request input:
{
"consumerAppId": "app_example_consumer",
"consumerWorkspaceId": "ws_consumer_example",
"status": "changed"
}
Complete response body:
{
"success": true,
"message": "OK",
"data": {
"placements": [
{
"id": "plc_docs-example-a",
"organizationId": "org_docs_fixture",
"consumer": {
"appId": "app_example_consumer",
"workspaceId": "ws_consumer_example",
"kind": "example.page",
"id": "page_example"
},
"slot": "content",
"reference": {
"app": "app_example_provider",
"workspace": "ws_provider_example",
"type": "example.item",
"id": "item_example",
"version": 2
},
"referenceString": "topolo:app_example_provider/ws_provider_example/example.item/item_example@2",
"label": "Example item",
"status": "changed",
"statusReason": "provider version 3",
"lastCheckedAt": "2026-10-20T10:00:00.000Z",
"createdBy": "user_docs_fixture",
"createdAt": "2026-10-20T10:00:00.000Z",
"updatedAt": "2026-10-20T10:00:00.000Z",
"removedBy": null,
"removedAt": null
}
],
"count": 1
}
}
Check the original organization and consumer workspace inside the row, count=placements.length, stored lastCheckedAt and pinned version. Do not call refresh or fetch the provider merely to verify this read.
Confirmed consumer workspace is inaccessible
COMPLETE SYNTHETIC UNRUN HTTP403. Assumes authentication and organization succeeded and Auth returned a list that lacks this workspace; this is not an empty-success body.
Request input:
{
"consumerAppId": "app_example_consumer",
"consumerWorkspaceId": "ws_consumer_example",
"status": "changed"
}
Complete response body:
{
"success": false,
"error": {
"message": "You cannot use workspace ws_consumer_example of app_example_consumer",
"code": "workspace_access_required",
"details": {
"appId": "app_example_consumer",
"workspaceId": "ws_consumer_example"
}
}
}
RECOVERY UNRUN: preserve the refusal. Use a genuinely accessible workspace discovered through supported Auth controls, or have its administrator restore the person’s access. After confirmed authorized restoration to this same workspace, the exact retry input is the same object above. The first success carrier describes a possible response only when that stored row is actually visible; do not fabricate that observation or broaden a grant.
Provenance and changelog
Source applicability: TopoloDevelopers@7b89bd3b39092cdc29f87540847bf99a243f0a40. Current production descriptors for these three actions were read on2026-10-22 and still name this source revision. Handler/store/caller/native-auth boundaries were read as immutable Git objects; no mutable checkout body was used. The current checkout HEAD differs and is not treated as the serving revision. Exact English success copy OK reuses the accepted connection3 source association at the same revision.
2026-10-22: first placement3 paired human/typed source guide; added complete wire/nullability, consumer access and provider boundary, bounded query/visibility semantics, stale-state limits and synthetic success/refusal/recovery carriers. Source association does not prove served runtime or UI behavior.