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.

Authentication How to obtain and send an OAuth2 access token.

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.