Upload and validate a CSV file for bulk user import. Returns a row-by-row report with
a verdict per row (valid / error / warning / ambiguous), separating blocking
errors[] from non-blocking warnings[]. Validated data is stored in a temporary
session (30 min TTL) keyed by import_id, which must be passed to
/users/import/confirm to actually create or update the users.
CSV format
- Columns (in any order):
email,name,phone,company_name,roles. - Required:
email,name,company_name,roles.phoneis optional. company_nameis the visible name of the user's organization (matched against distributors/resellers/customers in the caller's hierarchy).phone, when present, must include the leading+CCcountry prefix (e.g.+39 333 1234567). Bare local numbers without+CCare rejected withinvalid_formatat validate time. Phone uniqueness against existing users is also checked (already_used).rolesis a semicolon-separated list of role names (e.g.Admin;Support); names are resolved against the in-memory role cache.company_nameis matched case-insensitively against the caller's hierarchy. If multiple organizations share that name the row is markedambiguousand the response includes the candidates — the caller picks one inresolutionsat confirm time.- The first data row is row
2(the header row is row1). - Standard CSV (RFC 4180) — fields containing commas, quotes or newlines must be
double-quoted (
"value, with comma"). Spreadsheet tools auto-quote on save.
Row outcomes
| status | What it means | Confirm action |
|---|---|---|
valid |
All checks passed | CREATE |
error |
At least one blocking error in errors[] (required, invalid_format, duplicate_in_csv, not_found, unknown, archived, already_used, …) |
always skipped |
warning |
email already exists in DB (in warnings[]) |
UPDATE if override: true, else skipped |
ambiguous |
Organization name matches multiple orgs | CREATE with chosen org if resolutions[row] is set, else skipped |
Errors and warnings can coexist on the same row; status precedence is error > ambiguous > warning > valid.
Limits — max 10 MB, max 1000 rows. Larger files return 400.
POST
/users/import/validate
curl \
--request POST 'https://collect.your-domain.com/api/users/import/validate' \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header "Content-Type: multipart/form-data" \
--form "file=@file"
Response examples (200)
{
"code": 200,
"message": "users import validated",
"data": {
"import_id": "3312b187-a8b0-45f5-b23a-98798b64eb31",
"total_rows": 6,
"valid_rows": 2,
"error_rows": 2,
"warning_rows": 1,
"ambiguous_rows": 1,
"rows": [
{
"row_number": 2,
"status": "valid",
"data": {
"email": "marco.rossi@nethesis.it",
"name": "Marco Rossi",
"phone": "+39 333 1234567",
"company_name": "Acme Corp",
"organization_id": "zm45rltjc9rr",
"roles": "Admin",
"role_ids": [
"7jmz7ryag1m254m4428n8"
]
}
},
{
"row_number": 3,
"status": "valid",
"data": {
"email": "support@beta.it",
"name": "Beta Support",
"phone": "",
"company_name": "Beta Solutions",
"organization_id": "jl6lgn3l6nsi",
"roles": "Support",
"role_ids": [
"77evvdf876ze8oykdk4y9"
]
}
},
{
"row_number": 4,
"status": "error",
"data": {
"email": "not-an-email",
"name": "Bad Email",
"phone": "+39 333 0000000",
"company_name": "Acme Corp",
"organization_id": "",
"roles": "Admin",
"role_ids": [
"7jmz7ryag1m254m4428n8"
]
},
"errors": [
{
"field": "email",
"message": "invalid_format",
"values": [
"not-an-email"
]
}
]
},
{
"row_number": 5,
"status": "error",
"data": {
"email": "test@nethesis.it",
"name": "Wrong Org",
"phone": "",
"company_name": "Organization That Does Not Exist",
"organization_id": "",
"roles": "Support",
"role_ids": [
"77evvdf876ze8oykdk4y9"
]
},
"errors": [
{
"field": "company_name",
"message": "not_found",
"values": [
"Organization That Does Not Exist"
]
}
]
},
{
"row_number": 6,
"status": "warning",
"data": {
"email": "edoardo.spadoni@nethesis.it",
"name": "Mario Rossi",
"phone": "",
"company_name": "Acme Corp",
"organization_id": "zm45rltjc9rr",
"roles": "Admin",
"role_ids": [
"7jmz7ryag1m254m4428n8"
]
},
"warnings": [
{
"field": "email",
"message": "already_exists",
"values": [
"edoardo.spadoni@nethesis.it"
]
}
]
},
{
"row_number": 7,
"status": "ambiguous",
"data": {
"email": "ambig@nethesis.it",
"name": "Ambiguous Org",
"phone": "",
"company_name": "Gamma",
"organization_id": "",
"roles": "Support",
"role_ids": [
"77evvdf876ze8oykdk4y9"
]
},
"errors": [
{
"field": "company_name",
"message": "ambiguous",
"values": [
"Gamma"
],
"candidates": [
{
"logto_id": "abc123",
"name": "Gamma Tech",
"type": "distributor"
},
{
"logto_id": "def456",
"name": "Gamma Group",
"type": "customer"
}
]
}
]
}
]
}
}
Response examples (400)
{
"code": 400,
"message": "validation failed",
"data": {
"type": "validation_error",
"errors": [
{
"key": "username",
"message": "required",
"value": "string"
}
]
}
}
Response examples (401)
{
"code": 401,
"message": "invalid token",
"data": {}
}
Response examples (403)
{
"code": 403,
"message": "insufficient permissions",
"data": {}
}