Outcome
Append one nonzero signed quantity delta against an active item and existing location in the same workspace. Movement type labels do not set the sign, pair a transfer or defer stock while review is pending.
Before you start
- Verify the authenticated identity, exact environment and pinned approved organization context before preparing an action.
- The published application permission is workspace:write. Discover a granted Inventory workspace and bind that exact resource. Supplied identifiers do not grant access.
- Authorize the documented inventory, review, import, notification, metering, privacy or archival effects before CLI --confirm or MCP confirm:true.
Discover the target
topolo whoami --json
topolo actions get app_topolo_inventory.movements.create --detail --json
topolo resources topolo-inventory --json
topolo workspaces list topolo-inventory --json
- Select the exact granted workspace id as INVENTORY_WORKSPACE_ID. Fixture ids authorize no live operation.
- Discover item ids through items.list or the reviewed items.create response in this workspace.
- Discover location ids through locations.list or the reviewed locations.create response in this workspace.
Request
POST /api/movements
Save action input as payload.json. Inspect the plan for the exact path, query and body. Set INVENTORY_WORKSPACE_ID from granted resource discovery.
Inspect and test this action in the signed action console, then perform the verification below.
Input fields
| Field | Required | Type | Constraints |
|---|---|---|---|
itemId | Required | string | Active item id returned by items.list or items.create.; {"minLength":1} |
locationId | Required | string | Existing location id from this same granted workspace.; {"minLength":1} |
movementType | Optional | string | Optional ledger label; default adjustment. The label does not change the supplied sign or create a paired transfer.; {"enum":["initial","adjustment","receipt","sale","transfer","write_off"]} |
quantityDelta | Required | number | Finite nonzero signed number; fractional and negative deltas are supported, and negative aggregate stock is allowed.; {} |
note | Optional | string | Optional trimmed explanation; blank value is stored as an empty string.; {} |
View the complete input schema in the action console. The same schema is available through CLI and MCP action discovery.
Effects
- Creates a new UUID ledger row for every successful call. The supplied signed delta is immediately included in aggregate item stock; negative stock and fractional deltas are allowed.
- Type defaults adjustment. sale, receipt, transfer and other labels do not enforce sign or create paired entries in another location.
- When absolute delta is at or above a configured positive review threshold, status is pending; otherwise not_required. Pending and rejected rows still contribute to stock.
- After storing the row, conditionally emits low-stock/out-of-stock/adjustment/review notifications and a one-unit stock_movements usage event. These asynchronous effects are not confirmed delivery or metering receipts.
Interpret the response
Use response path $.movement.id for inventory_movement. An empty list does not discover a target.
Expect HTTP 201, retain movement.id and inspect quantityDelta and reviewStatus. Read items.get for resulting aggregate quantity and movements.list for the newest row. A repeated call records another movement rather than deduplicating the original.
The success schema was checked against captured handler responses. Delegated Auth profile and canonical widget context fields can vary; exact Inventory projections and all six raw SQL export stores are source-reviewed. Error examples retain their actual refusal shapes.
View the complete success schema in the action console. The worked examples below retain complete captured responses; action discovery supplies the exact schema.
Worked examples
Captured exported Worker responses use both real local SQLite D1 databases with all six committed migrations, synthetic Auth/workspace grants and simulated Notify/Nexus/archive transport. They prove local handler shapes and scoped behavior, not live authorization, notification delivery, metering acceptance or environment deployment. Large 100-row list/capped-dashboard captures are retained in local evidence; committed tests verify those limits while worked guides use smaller complete responses. Refused examples demonstrate boundaries and must not be executed as successful workflows.
Successful request
Actual exported Worker response with both local SQLite D1 databases and all committed migrations; synthetic Auth/grants and simulated Notify/Nexus/archive transport. No live delivery, billing or environment deployment proof. Captured scenario: equal threshold requires review.
Input · payload.json:
{
"itemId": "invitem_cf12df06-7056-4441-bef4-95d5c15ef1ed",
"locationId": "invloc_cfa27344-e120-4c97-90e4-7e876b0a5e3e",
"movementType": "receipt",
"quantityDelta": 10
}
CLI:
topolo actions validate app_topolo_inventory.movements.create --data @payload.json --json
topolo actions plan app_topolo_inventory.movements.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_inventory.movements.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --confirm --json
MCP tool and arguments:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_inventory.movements.create",
"input": {
"itemId": "invitem_cf12df06-7056-4441-bef4-95d5c15ef1ed",
"locationId": "invloc_cfa27344-e120-4c97-90e4-7e876b0a5e3e",
"movementType": "receipt",
"quantityDelta": 10
},
"resourceType": "workspace",
"resourceId": "<identifier returned by granted resource discovery>",
"confirm": true
}
}
Captured local response · HTTP 201:
{
"movement": {
"id": "invmove_3847adc5-c5b0-452d-87a0-37b1272057be",
"itemId": "invitem_cf12df06-7056-4441-bef4-95d5c15ef1ed",
"locationId": "invloc_cfa27344-e120-4c97-90e4-7e876b0a5e3e",
"movementType": "receipt",
"quantityDelta": 10,
"note": "",
"itemName": "Blue widget",
"itemSku": "WIDGET-BLUE",
"locationName": "Main warehouse",
"createdByUserId": "usr_inventory_fixture",
"createdByEmail": "operator@example.test",
"createdAt": "2026-10-04T05:46:37.131Z",
"reviewStatus": "pending",
"reviewReason": "Absolute quantity delta 10 met the configured threshold 10.",
"reviewedByUserId": null,
"reviewedAt": null
}
}
HTTP 201. Expect HTTP 201, retain movement.id and inspect quantityDelta and reviewStatus. Read items.get for resulting aggregate quantity and movements.list for the newest row. A repeated call records another movement rather than deduplicating the original.
Another supported request
Actual exported Worker response with both local SQLite D1 databases and all committed migrations; synthetic Auth/grants and simulated Notify/Nexus/archive transport. No live delivery, billing or environment deployment proof. Captured scenario: fractional adjustment without review.
Input · payload.json:
{
"itemId": "invitem_cf12df06-7056-4441-bef4-95d5c15ef1ed",
"locationId": "invloc_cfa27344-e120-4c97-90e4-7e876b0a5e3e",
"quantityDelta": 2.5
}
CLI:
topolo actions validate app_topolo_inventory.movements.create --data @payload.json --json
topolo actions plan app_topolo_inventory.movements.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_inventory.movements.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --confirm --json
MCP tool and arguments:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_inventory.movements.create",
"input": {
"itemId": "invitem_cf12df06-7056-4441-bef4-95d5c15ef1ed",
"locationId": "invloc_cfa27344-e120-4c97-90e4-7e876b0a5e3e",
"quantityDelta": 2.5
},
"resourceType": "workspace",
"resourceId": "<identifier returned by granted resource discovery>",
"confirm": true
}
}
Captured local response · HTTP 201:
{
"movement": {
"id": "invmove_24b33b0b-2f8a-4bdc-a627-f69bf24413a9",
"itemId": "invitem_cf12df06-7056-4441-bef4-95d5c15ef1ed",
"locationId": "invloc_cfa27344-e120-4c97-90e4-7e876b0a5e3e",
"movementType": "adjustment",
"quantityDelta": 2.5,
"note": "",
"itemName": "Blue widget",
"itemSku": "WIDGET-BLUE",
"locationName": "Main warehouse",
"createdByUserId": "usr_inventory_fixture",
"createdByEmail": "operator@example.test",
"createdAt": "2026-10-04T05:46:37.325Z",
"reviewStatus": "not_required",
"reviewReason": null,
"reviewedByUserId": null,
"reviewedAt": null
}
}
HTTP 201. Expect HTTP 201, retain movement.id and inspect quantityDelta and reviewStatus. Read items.get for resulting aggregate quantity and movements.list for the newest row. A repeated call records another movement rather than deduplicating the original.
Another supported request
Actual exported Worker response with both local SQLite D1 databases and all committed migrations; synthetic Auth/grants and simulated Notify/Nexus/archive transport. No live delivery, billing or environment deployment proof. Captured scenario: movement type does not override supplied positive sign.
Input · payload.json:
{
"itemId": "invitem_e5b77ab2-fc66-43fa-a80c-9583d66a0ccb",
"locationId": "invloc_33f22931-4dd7-4b8d-a8a5-6a3c752fb901",
"movementType": "sale",
"quantityDelta": 2
}
CLI:
topolo actions validate app_topolo_inventory.movements.create --data @payload.json --json
topolo actions plan app_topolo_inventory.movements.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_inventory.movements.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --confirm --json
MCP tool and arguments:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_inventory.movements.create",
"input": {
"itemId": "invitem_e5b77ab2-fc66-43fa-a80c-9583d66a0ccb",
"locationId": "invloc_33f22931-4dd7-4b8d-a8a5-6a3c752fb901",
"movementType": "sale",
"quantityDelta": 2
},
"resourceType": "workspace",
"resourceId": "<identifier returned by granted resource discovery>",
"confirm": true
}
}
Captured local response · HTTP 201:
{
"movement": {
"id": "invmove_f6f06cbc-ef1d-4393-a591-3e4055e7a795",
"itemId": "invitem_e5b77ab2-fc66-43fa-a80c-9583d66a0ccb",
"locationId": "invloc_33f22931-4dd7-4b8d-a8a5-6a3c752fb901",
"movementType": "sale",
"quantityDelta": 2,
"note": "",
"itemName": "Blue widget",
"itemSku": "WIDGET-BLUE",
"locationName": "Main warehouse",
"createdByUserId": "usr_inventory_fixture",
"createdByEmail": "operator@example.test",
"createdAt": "2026-10-04T05:46:38.692Z",
"reviewStatus": "not_required",
"reviewReason": null,
"reviewedByUserId": null,
"reviewedAt": null
}
}
HTTP 201. Expect HTTP 201, retain movement.id and inspect quantityDelta and reviewStatus. Read items.get for resulting aggregate quantity and movements.list for the newest row. A repeated call records another movement rather than deduplicating the original.
Another supported request
Actual exported Worker response with both local SQLite D1 databases and all committed migrations; synthetic Auth/grants and simulated Notify/Nexus/archive transport. No live delivery, billing or environment deployment proof. Captured scenario: movements.create.
Input · payload.json:
{
"itemId": "invitem_d69d6494-b1f0-472a-8ce6-c2e8e5ddb02d",
"locationId": "invloc_13fa047c-b024-4c44-99b8-75abe7cff675",
"quantityDelta": 5
}
CLI:
topolo actions validate app_topolo_inventory.movements.create --data @payload.json --json
topolo actions plan app_topolo_inventory.movements.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_inventory.movements.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --confirm --json
MCP tool and arguments:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_inventory.movements.create",
"input": {
"itemId": "invitem_d69d6494-b1f0-472a-8ce6-c2e8e5ddb02d",
"locationId": "invloc_13fa047c-b024-4c44-99b8-75abe7cff675",
"quantityDelta": 5
},
"resourceType": "workspace",
"resourceId": "<identifier returned by granted resource discovery>",
"confirm": true
}
}
Captured local response · HTTP 201:
{
"movement": {
"id": "invmove_9515c62a-e304-4bf5-84c1-d5ec3b020789",
"itemId": "invitem_d69d6494-b1f0-472a-8ce6-c2e8e5ddb02d",
"locationId": "invloc_13fa047c-b024-4c44-99b8-75abe7cff675",
"movementType": "adjustment",
"quantityDelta": 5,
"note": "",
"itemName": "Blue widget",
"itemSku": "WIDGET-BLUE",
"locationName": "Main warehouse",
"createdByUserId": "usr_inventory_fixture",
"createdByEmail": "operator@example.test",
"createdAt": "2026-10-04T05:46:38.173Z",
"reviewStatus": "pending",
"reviewReason": "Absolute quantity delta 5 met the configured threshold 1.",
"reviewedByUserId": null,
"reviewedAt": null
}
}
HTTP 201. Expect HTTP 201, retain movement.id and inspect quantityDelta and reviewStatus. Read items.get for resulting aggregate quantity and movements.list for the newest row. A repeated call records another movement rather than deduplicating the original.
Another supported request
Actual exported Worker response with both local SQLite D1 databases and all committed migrations; synthetic Auth/grants and simulated Notify/Nexus/archive transport. No live delivery, billing or environment deployment proof. Captured scenario: new opposite movement is independently reviewed.
Input · payload.json:
{
"itemId": "invitem_cf12df06-7056-4441-bef4-95d5c15ef1ed",
"locationId": "invloc_cfa27344-e120-4c97-90e4-7e876b0a5e3e",
"quantityDelta": -10,
"note": "Compensating correction"
}
CLI:
topolo actions validate app_topolo_inventory.movements.create --data @payload.json --json
topolo actions plan app_topolo_inventory.movements.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_inventory.movements.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --confirm --json
MCP tool and arguments:
{
"tool": "topolo_call_action",
"arguments": {
"actionId": "app_topolo_inventory.movements.create",
"input": {
"itemId": "invitem_cf12df06-7056-4441-bef4-95d5c15ef1ed",
"locationId": "invloc_cfa27344-e120-4c97-90e4-7e876b0a5e3e",
"quantityDelta": -10,
"note": "Compensating correction"
},
"resourceType": "workspace",
"resourceId": "<identifier returned by granted resource discovery>",
"confirm": true
}
}
Captured local response · HTTP 201:
{
"movement": {
"id": "invmove_16afb7cb-6c3f-4051-a68e-3988135bf040",
"itemId": "invitem_cf12df06-7056-4441-bef4-95d5c15ef1ed",
"locationId": "invloc_cfa27344-e120-4c97-90e4-7e876b0a5e3e",
"movementType": "adjustment",
"quantityDelta": -10,
"note": "Compensating correction",
"itemName": "Blue widget",
"itemSku": "WIDGET-BLUE",
"locationName": "Main warehouse",
"createdByUserId": "usr_inventory_fixture",
"createdByEmail": "operator@example.test",
"createdAt": "2026-10-04T05:46:37.212Z",
"reviewStatus": "pending",
"reviewReason": "Absolute quantity delta 10 met the configured threshold 10.",
"reviewedByUserId": null,
"reviewedAt": null
}
}
HTTP 201. Expect HTTP 201, retain movement.id and inspect quantityDelta and reviewStatus. Read items.get for resulting aggregate quantity and movements.list for the newest row. A repeated call records another movement rather than deduplicating the original.
Refused request · HTTP 400
Actual exported Worker response with both local SQLite D1 databases and all committed migrations; synthetic Auth/grants and simulated Notify/Nexus/archive transport. No live delivery, billing or environment deployment proof. Captured scenario: archived item refuses movements.
Input · payload.json:
{
"itemId": "invitem_cf12df06-7056-4441-bef4-95d5c15ef1ed",
"locationId": "invloc_cfa27344-e120-4c97-90e4-7e876b0a5e3e",
"quantityDelta": 1
}
CLI:
topolo actions validate app_topolo_inventory.movements.create --data @payload.json --json
topolo actions plan app_topolo_inventory.movements.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --json
Captured local response · HTTP 400:
{
"error": "item_not_found"
}
HTTP 400. The handler refused this request. Resolve its exact cause before retrying; input validation can reject an invalid fixture before it reaches the handler.
Refused request · HTTP 400
Actual exported Worker response with both local SQLite D1 databases and all committed migrations; synthetic Auth/grants and simulated Notify/Nexus/archive transport. No live delivery, billing or environment deployment proof. Captured scenario: unknown location refused.
Input · payload.json:
{
"itemId": "invitem_cf12df06-7056-4441-bef4-95d5c15ef1ed",
"locationId": "invloc_missing",
"quantityDelta": 1
}
CLI:
topolo actions validate app_topolo_inventory.movements.create --data @payload.json --json
topolo actions plan app_topolo_inventory.movements.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --json
Captured local response · HTTP 400:
{
"error": "location_not_found"
}
HTTP 400. The handler refused this request. Resolve its exact cause before retrying; input validation can reject an invalid fixture before it reaches the handler.
Refused request · HTTP 400
Actual exported Worker response with both local SQLite D1 databases and all committed migrations; synthetic Auth/grants and simulated Notify/Nexus/archive transport. No live delivery, billing or environment deployment proof. Captured scenario: zero delta refused.
Input · payload.json:
{
"itemId": "invitem_cf12df06-7056-4441-bef4-95d5c15ef1ed",
"locationId": "invloc_cfa27344-e120-4c97-90e4-7e876b0a5e3e",
"quantityDelta": 0
}
CLI:
topolo actions validate app_topolo_inventory.movements.create --data @payload.json --json
topolo actions plan app_topolo_inventory.movements.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --json
Captured local response · HTTP 400:
{
"error": "quantity_delta_required"
}
HTTP 400. The handler refused this request. Resolve its exact cause before retrying; input validation can reject an invalid fixture before it reaches the handler.
Verify the result
- Expect HTTP 201, retain movement.id and inspect quantityDelta and reviewStatus.
- Read items.get for resulting aggregate quantity and movements.list for the newest row. A repeated call records another movement rather than deduplicating the original.
Errors and recovery
invalid_action_input(HTTP 400): Correct only the exact fields reported by the published schema validator. An invalid input can be refused before its handler.action_permission_required(HTTP 403): Stop when the verified credential lacks the published action permission. Resource identifiers and organization ownership do not grant that permission.organization_required(HTTP 403): Use the specifically authorized organization context; personal scope cannot act on organization-owned Inventory data.workspace_required(HTTP 400): Discover an authorized Inventory workspace and pass its exact separate resource binding; bootstrap does not select a workspace.workspaces_unavailable(HTTP 503): Retain the request id and report unavailable canonical workspace discovery; do not invent a default workspace.quantity_delta_required(HTTP 400): Use a finite nonzero signed number; zero is accepted by the input number schema but refused by the handler.item_not_found(HTTP 400): Select an existing active item in this exact workspace; archived items refuse movements.location_not_found(HTTP 400): Select an existing location in this exact workspace.location_workspace_mismatch(HTTP 400): The item and location must belong to the same verified workspace; do not guess ids.
Auth, action-input validation and platform resource resolution can refuse a request before its handler. Retain the exact error, request id and context; requesting another context is not a legitimate way to bypass a refusal.
Recovery boundary
No movement edit/delete action exists. If an authorized correction is required, append the opposite delta as a separate movement with an explanatory note. It may require its own review, emits its own effects and does not erase the original entry.
Related actions
- Get Inventory item: Read one item in the granted workspace with descriptive fields, archive status and quantityOnHand computed from all its movements. Archived items remain readable; missing or foreign items return not_found.
- List Inventory stock movements: Read up to 100 newest ledger entries in the granted workspace, enriched with item/location labels. Every review state is included and there is no cursor or full-ledger export on this action.
- Review Inventory stock movement: Resolve a pending movement review as approved or rejected. This changes review metadata only: rejecting does not delete the ledger entry, reverse stock or undo notifications already emitted.
Evidence
Captured exported Worker responses use both real local SQLite D1 databases with all six committed migrations, synthetic Auth/workspace grants and simulated Notify/Nexus/archive transport. They prove local handler shapes and scoped behavior, not live authorization, notification delivery, metering acceptance or environment deployment. Large 100-row list/capped-dashboard captures are retained in local evidence; committed tests verify those limits while worked guides use smaller complete responses. Capture digests and test scenarios are retained in the source fixture records.