Update a recorded placement
Updates local details or planned/placed/recovered/lost status. Moving to placed can fill placedAt, and moving to recovered/lost can fill endedAt; these timestamps do not establish actual asset location.
Outcome and boundaries
Updates local details or planned/placed/recovered/lost status. Moving to placed can fill placedAt, and moving to recovered/lost can fill endedAt; these timestamps do not establish actual asset location.
A request with only identifiers still updates stored updatedAt. It does not delete a placement or reverse an external operation.
Before you start
- Confirm the intended environment and current credential organization using whoami; discover the exact application/action before any call.
- Required application permission: app_topolo_campaigns.campaigns:write. Catalog discoverability is not permission to execute.
- Discover an authorized canonical workspace and bind resourceType workspace/resourceId separately from action input.
- All worked cases in this guide are hypothetical source-reviewed illustrations; no business action, live fixture, customer data or provider request was executed.
- Canonical workspace minimum access is read_write in addition to the application permission. After grant verification, the first request may create an app-local identity/recipient mirror even before the later app-permission check; this is not campaign-domain completion.
Discover the intended identifiers
- Read placements.list for this campaign; snapshot all fields before changing metadata or timestamps.
Action: app_topolo_campaigns.placements.update · PATCH /api/campaigns/{id}/placements/{placementId}.
Input fields
| Field | Required | Meaning and validation |
|---|---|---|
id | Yes | Campaign identifier from campaigns[].id; scoped to the selected organization/workspace. It is not a batch/code/placement id. string; minLength=1 |
placementId | Yes | Placement id from placements.list for this same campaign. string; minLength=1 |
label | No | Human-readable stored label; no external delivery or printing occurs. string |
venueRef | No | Optional stored physical-location reference; no venue lookup or actual asset movement is performed. string or null |
status | No | Only the enum values declared below are valid at the action-schema layer; read current state before a separately authorized transition. string; enum=["planned", "placed", "recovered", "lost"] |
placedAt | No | Omit to retain or auto-fill when first marking placed; explicit null clears. Stored strings are not validated as a timestamp by this source. string or null |
endedAt | No | Omit to retain or auto-fill when first marking recovered/lost; explicit null clears. Stored strings are not physical-event proof. string or null |
metadata | No | Stored JSON object. It is not an instruction, access grant or evidence of a provider outcome; avoid credentials or sensitive customer data. object |
Safe call sequence
Write the reviewed input to payload.json; fixture identifiers below are unusable examples. Validate before planning, bind the discovered resource separately, and execute only within actual authority.
topolo actions validate app_topolo_campaigns.placements.update --data @payload.json --json
topolo actions plan app_topolo_campaigns.placements.update --resource-type workspace --resource-id "$CAMPAIGNS_WORKSPACE_ID" --data @payload.json --json
The published action requires confirmation. Only after the exact mutation is authorized, use that same payload/context with --confirm on plan/call; no operation in this guide has been executed.
Worked cases
Every response below is synthetic and source-reviewed, not captured. HTTP status and body are explained independently; a structurally valid body alone never proves successful execution. Middleware may add a different error envelope.
Record placed state · HTTP 200
Input:
{
"id": "campaign_example",
"placementId": "placement_example",
"status": "placed"
}
Illustrative response:
{
"placement": {
"id": "placement_example",
"codeId": "code_example",
"label": "Illustrative reception stand",
"venueRef": null,
"status": "placed",
"placedAt": "2026-10-20T00:00:00.000Z",
"endedAt": null,
"metadata": {},
"createdAt": "2026-10-20T00:00:00.000Z",
"updatedAt": "2026-10-20T00:00:00.000Z"
}
}
Clear stored venue/timestamps · HTTP 200
Input:
{
"id": "campaign_example",
"placementId": "placement_example",
"venueRef": null,
"placedAt": null,
"endedAt": null
}
Illustrative response:
{
"placement": {
"id": "placement_example",
"codeId": "code_example",
"label": "Illustrative reception stand",
"venueRef": null,
"status": "planned",
"placedAt": null,
"endedAt": null,
"metadata": {},
"createdAt": "2026-10-20T00:00:00.000Z",
"updatedAt": "2026-10-20T00:00:00.000Z"
}
}
Unknown placement · HTTP 404
Input:
{
"id": "campaign_example",
"placementId": "placement_absent",
"status": "lost"
}
Illustrative response:
{
"error": "placement_not_found"
}
Verify the outcome
- Re-read placements.list and compare the intended id, status, dates and metadata. Verify the physical event separately.
Refusals and recovery
- HTTP 404
placement_not_found: Correct the named input or retain the authoritative refusal in the same context. - HTTP 400
invalid_status: Correct the named input or retain the authoritative refusal in the same context. - HTTP 403
forbidden: Raw handler permission refusal. SDKs can normalize it to permission_denied. Keep the same intended resource; do not widen access. - HTTP 403
workspace_forbidden: The canonical workspace grant refused the selected resource. Resolve it with the access owner, not a guessed workspace.
A separately reviewed update can restore snapped fields, including explicit null timestamps. It does not undo movement of a physical asset or erase an event history.
Exact schemas and provenance
Input schema:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"placementId": {
"type": "string",
"minLength": 1
},
"label": {
"type": "string"
},
"venueRef": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"status": {
"type": "string",
"enum": [
"planned",
"placed",
"recovered",
"lost"
]
},
"placedAt": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"endedAt": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
]
},
"metadata": {
"type": "object",
"properties": {},
"additionalProperties": {}
}
},
"required": [
"id",
"placementId"
],
"additionalProperties": false
}
Output schema:
{
"type": "object",
"title": "placements update response",
"description": "Operation result returned by placements.update.",
"properties": {
"success": {
"type": "boolean"
},
"id": {
"type": "string"
},
"data": {
"anyOf": [
{
"type": "object",
"title": "placement record",
"properties": {
"id": {
"type": "string",
"description": "Stable identifier when the placement exposes one."
}
},
"additionalProperties": true
},
{
"type": "object",
"additionalProperties": true
}
]
},
"result": {
"type": "object",
"additionalProperties": true
},
"message": {
"type": "string"
}
},
"additionalProperties": true,
"x-topolo-contract-source": "derived-action-metadata",
"x-topolo-contract-precision": "structural"
}
Reviewed immutable source: TopoloCampaigns@cacbb8b12b4e19a662cf82d03a927aef3c3a35e8, apps/api/src/campaign-orchestration-store.ts. Fresh production catalog was read on 2026-10-20. Its publication metadata is not proof that this source is loaded. Output examples are not widened or rewritten to force schema success; the accompanying validation record classifies exact success, refusal, no-body and structural-schema limits.
Related actions
- List campaign placements — Campaigns: Lists stored physical-code placement metadata. Recorded status and timestamps do not establish the current physical location of an asset. An absent campaign can return an empty list.
Local input rejection example
This deliberately invalid input belongs to a pure schema check; it is never a planned or executed request. The closed action input schema must reject the extra field. It has no HTTP status or server response.
{
"id": "campaign_example",
"placementId": "placement_example",
"status": "placed",
"__unsupported_field": true
}
The hidden companion labels this as ILLUSTRATIVE_LOCAL_SCHEMA_CHECK; its validation representation is not an action output.
Published output validation limits
Current published output qualification: the output schema accepts an empty object, a partial success marker, a partial data object and a refusal body. It therefore establishes only broad structure. Require the documented HTTP/domain result and source-shaped fields; missing fields or a schema-valid error are not success.
These observations come from pure checks of the exact published schema; they are not server responses or production captures.