Connected Workers CSV Import
The Connected Workers CSV Import API loads large CSV files — up to ~200,000 rows — into any CW table asynchronously. Where the synchronous /bulk endpoint handles a payload inline, CSV import uploads the file to S3, processes it in a background job, and reports progress you poll for.
It works the same way under every CW entity mount:
/v2/api/workspaces/{workspaceId}/cw/{locations|departments|users|assets|lists}/csv-import
- Locations, Departments and Users are record-native — one table per workspace — so you omit
tableKey. - Users upsert by Email, and landing a new address registers and invites that person — see the Users guide.
- Assets and Lists have many tables per workspace, so you must pass
tableKey(the target table slug).
When to use this vs /bulk
| CSV Import | /bulk |
|
|---|---|---|
| Best for | Large data sets — more than a few thousand rows (up to ~200k) | Small imports up to a few thousand rows |
| Transport | File uploaded to S3, processed in the background | JSON payload processed inline |
| Response | 202 with a jobId — poll for progress |
Synchronous result in one response |
Rule of thumb: reach for CSV import once a table has more than a few thousand rows. Below that, /bulk is simpler — one call, one synchronous answer.
Both are idempotent by primary key: re-running a CSV import upserts set-based, so a retry converges rather than duplicating.
Base URL & auth
Every endpoint is scoped to a workspace and served under:
https://api.treedis.com/v2/api/workspaces/{workspaceId}/cw/{entity}/csv-import
All endpoints require authentication (OAuth2 Bearer token or API key), and the caller must have access to the workspace. workspaceId is always a numeric ID. An API key is bound to a single workspace and can only address its own.
The CSV file
The file is a plain CSV with a header row (the default). Header names are slugified and matched to the target table's column keys — so a header PM_WKCTR maps to column key pm_wkctr, TPLNR to tplnr, and so on. Columns that don't match a header are left untouched.
If your file has no header, set hasHeader: false on the start call and the columns are taken in order.
The three-step flow
Presign — get an upload URL
POST …/csv-import/presign with the file name. You get back a presigned S3 uploadUrl and the s3Key to reference later.
PUT the CSV to S3
Upload the raw CSV bytes to uploadUrl with Content-Type: text/csv. This request goes straight to S3, not through the Treedis API.
Start — enqueue the job, then poll
POST …/csv-import with the s3Key (and tableKey for assets / lists) returns 202 and a jobId. Poll GET …/csv-import/{jobId} until state is succeeded or failed.
Example: locations import (no tableKey)
1. Presign
curl -X POST "https://api.treedis.com/v2/api/workspaces/7/cw/locations/csv-import/presign" \
-H "Authorization: Bearer your-access-token" \
-H "Content-Type: application/json" \
-d '{ "fileName": "locations.csv" }'
{
"success": true,
"code": 200,
"message": "Success",
"data": {
"uploadUrl": "https://s3.eu-west-1.amazonaws.com/treedis-uploads/cw-imports/7/8f3a-locations.csv?X-Amz-Signature=...",
"s3Key": "cw-imports/7/8f3a-locations.csv"
}
}
2. PUT the CSV to S3
The upload goes directly to the presigned URL — no Authorization header, Content-Type: text/csv.
curl -X PUT "<uploadUrl from step 1>" \
-H "Content-Type: text/csv" \
--data-binary @locations.csv
3. Start the job
Locations are record-native, so omit tableKey.
curl -X POST "https://api.treedis.com/v2/api/workspaces/7/cw/locations/csv-import" \
-H "Authorization: Bearer your-access-token" \
-H "Content-Type: application/json" \
-d '{ "s3Key": "cw-imports/7/8f3a-locations.csv", "hasHeader": true }'
{ "jobId": 12345 }
Example: assets import (with tableKey)
Assets have many tables per workspace, so the start call requires tableKey — the slug of the target table (here assets). Presign and the S3 upload are identical to the locations flow.
# 1. presign
curl -X POST "https://api.treedis.com/v2/api/workspaces/7/cw/assets/csv-import/presign" \
-H "Authorization: Bearer your-access-token" \
-H "Content-Type: application/json" \
-d '{ "fileName": "assets.csv" }'
# 2. PUT the file to the returned uploadUrl
curl -X PUT "<uploadUrl>" \
-H "Content-Type: text/csv" \
--data-binary @assets.csv
# 3. start — tableKey is required for assets (and lists)
curl -X POST "https://api.treedis.com/v2/api/workspaces/7/cw/assets/csv-import" \
-H "Authorization: Bearer your-access-token" \
-H "Content-Type: application/json" \
-d '{ "s3Key": "cw-imports/7/8f3a-assets.csv", "tableKey": "assets", "hasHeader": true }'
Lists work exactly the same — post to …/cw/lists/csv-import with the target list's tableKey.
Polling for progress
Poll GET …/csv-import/{jobId} until state is succeeded or failed.
curl "https://api.treedis.com/v2/api/workspaces/7/cw/locations/csv-import/12345" \
-H "Authorization: Bearer your-access-token"
{
"success": true,
"code": 200,
"message": "Success",
"data": {
"state": "active",
"progress": 42,
"result": null
}
}
Once finished, result summarises the outcome:
{
"success": true,
"code": 200,
"message": "Success",
"data": {
"state": "succeeded",
"progress": 100,
"result": {
"processed": 200000,
"inserted": 180000,
"updated": 19995,
"failed": 5,
"errorReportKey": "cw-imports/7/8f3a-locations-errors.csv"
}
}
}
Poll the status endpoint on an interval — every few seconds is plenty — and stop once data.state reads succeeded or failed. data.progress (0–100) lets you show a progress bar while state is waiting or active.
Failed rows
When result.failed is greater than 0, result.errorReportKey is the S3 key of a CSV listing the rows that couldn't be imported (with the reason). Fetch it, fix those rows, and re-run the import — because upserts are keyed by primary key, only the corrected rows change.
Errors
Errors use the same envelope with success: false and an optional details object:
{
"success": false,
"code": 400,
"message": "tableKey is required for assets imports",
"details": {
"error": "Assets have multiple tables per workspace — provide the target table slug"
}
}
| Status | When |
|---|---|
400 |
Missing s3Key, missing tableKey for assets / lists, or an unreadable / malformed file |
404 |
Job id not found (on the status endpoint) |
See the Connected Workers tags in the API Reference for the full request and response schemas.

