Outcome
Create a CSV import job and upsert items by workspace/SKU. Successful import preserves existing item IDs and reactivates matching archived rows; parsing or batch failure returns HTTP 422 with a retained failed job.
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.imports.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.
Request
POST /api/imports
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 |
|---|---|---|---|
filename | Optional | string | Optional nonblank label; defaults inventory.csv. This is metadata, not a filesystem upload path.; {"minLength":1} |
csv | Required | string | Nonempty CSV text with sku and name headers and at least one data row. No multiline quoted values; lowStockThreshold must be finite and nonnegative.; {"minLength":1} |
View the complete input schema in the action console. The same schema is available through CLI and MCP action discovery.
Effects
- Initializes the identity mirror and a processing job before parsing. A failed parse still leaves a failed import record and can emit an import-failed notification.
- Requires sku and name headers, supports description/unit/lowStockThreshold, trims cells, strips a BOM and skips blank lines. Headers are normalized to lowercase alphanumeric names. Quoted commas and escaped quotes are handled within one line; multiline quoted CSV values are not supported.
- Duplicate SKU within the submitted CSV is refused. Every valid row upserts by workspace/SKU, replacing name/description/unit/threshold and status active while preserving the old row id, original creator fields and createdAt.
- The item upserts and completed-job update use one core database batch. rowCount is CSV rows, not insert count. Retrying creates a new job and can overwrite fields again.
Interpret the response
Use response path $.import.id for inventory_import. An empty list does not discover a target.
On HTTP 201 check import.status completed and rowCount; inspect affected SKUs via items.list, including existing ids/status. On HTTP 422 read import.id/status failed/failureReason and inspect items before any corrected retry. A failed-job response is not a successful import.
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: upsert existing SKU reactivates and preserves id.
Input · payload.json:
{
"filename": "items.csv",
"csv": "sku,name,unit,lowStockThreshold\nWIDGET-BLUE,Renamed widget,box,7\nWIDGET-GREEN,Green widget,each,2"
}
CLI:
topolo actions validate app_topolo_inventory.imports.create --data @payload.json --json
topolo actions plan app_topolo_inventory.imports.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --json
topolo actions call app_topolo_inventory.imports.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.imports.create",
"input": {
"filename": "items.csv",
"csv": "sku,name,unit,lowStockThreshold\nWIDGET-BLUE,Renamed widget,box,7\nWIDGET-GREEN,Green widget,each,2"
},
"resourceType": "workspace",
"resourceId": "<identifier returned by granted resource discovery>",
"confirm": true
}
}
Captured local response · HTTP 201:
{
"import": {
"id": "invimport_d158af2c-22ef-4712-b556-160744fa2ff5",
"filename": "items.csv",
"status": "completed",
"rowCount": 2,
"failureReason": null,
"createdAt": "2026-10-04T05:46:37.743Z",
"completedAt": "2026-10-04T05:46:37.755Z"
}
}
HTTP 201. On HTTP 201 check import.status completed and rowCount; inspect affected SKUs via items.list, including existing ids/status. On HTTP 422 read import.id/status failed/failureReason and inspect items before any corrected retry. A failed-job response is not a successful import.
Refused request · HTTP 422
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: duplicate SKU fails with retained job.
Input · payload.json:
{
"csv": "sku,name\nDUP,A\nDUP,B"
}
CLI:
topolo actions validate app_topolo_inventory.imports.create --data @payload.json --json
topolo actions plan app_topolo_inventory.imports.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --json
Captured local response · HTTP 422:
{
"error": "import_failed",
"import": {
"id": "invimport_3e648b38-70c8-47ff-b196-5b23ba41c914",
"filename": "inventory.csv",
"status": "failed",
"rowCount": 0,
"failureReason": "CSV contains duplicate sku DUP.",
"createdAt": "2026-10-04T05:46:37.779Z",
"completedAt": "2026-10-04T05:46:37.791Z"
}
}
HTTP 422. 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 422
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: missing CSV headers fail.
Input · payload.json:
{
"csv": "wrong,headers\nA,B"
}
CLI:
topolo actions validate app_topolo_inventory.imports.create --data @payload.json --json
topolo actions plan app_topolo_inventory.imports.create --resource-type workspace --resource-id "$INVENTORY_WORKSPACE_ID" --data @payload.json --json
Captured local response · HTTP 422:
{
"error": "import_failed",
"import": {
"id": "invimport_c8fdbb46-8bf6-463f-b5a5-0d4ccb36d827",
"filename": "inventory.csv",
"status": "failed",
"rowCount": 0,
"failureReason": "CSV headers must include sku and name.",
"createdAt": "2026-10-04T05:46:37.795Z",
"completedAt": "2026-10-04T05:46:37.803Z"
}
}
HTTP 422. 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
- On HTTP 201 check import.status completed and rowCount; inspect affected SKUs via items.list, including existing ids/status.
- On HTTP 422 read import.id/status failed/failureReason and inspect items before any corrected retry. A failed-job response is not a successful import.
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.import_failed(HTTP 422): Read the exact retained job failureReason, correct the CSV or reported batch issue, inspect affected items, then plan a new import. Do not claim the failed job was rolled back or deleted.
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
There is no import rollback/delete or prior-values restore action. Export or read affected items before importing; authorized items.update can compensate selected prior fields/status, while new items can be archived. A new corrected import creates another job.
Related actions
- List Inventory CSV import jobs: Read up to 100 newest CSV import jobs in the granted workspace with terminal status, row count, failure reason and timestamps. It returns job metadata rather than CSV data or imported item mappings.
- List Inventory items: List all workspace items, including archived records, newest update first. Every quantityOnHand sums all signed ledger deltas across locations and review states; there is no pagination.
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.