/users/import/confirm - Execute validated user import

POST /users/import/confirm

Confirm and execute a previously validated user import. Reuses the same pipelines as POST /users (CREATE) and PUT /users/{id} (UPDATE) for each row, so caller permissions are enforced per-row.

Inputs

  • import_id — from the /validate response. Must still be valid (sessions expire after 30 min).
  • override — global flag. When true, every row with status warning (the user already exists, matched by email) is UPDATEd with the CSV values. When omitted/false, warning rows are skipped.
  • resolutions — required for any row with status ambiguous that you want to import; keyed by row number as a string, value is { organization_id } picked from candidates[].

Behaviour per row status

status override false override true
valid CREATE CREATE
error skipped (reason: error) skipped (reason: error)
warning skipped (reason: warning_not_overridden) UPDATE — name, phone, company_name, roles overwritten from CSV (email is the lookup key, never changed)
ambiguous (no resolution) skipped (reason: ambiguous_unresolved) same
ambiguous (resolution provided) CREATE with chosen org same

RBAC for override

Updating an existing user requires the caller to have permission on the user's current organization (their hierarchy must include the target user's org). Organization changes ("moves") via override are allowed because the destination org has already been resolved against the caller's hierarchy at validate time. Per-row failures (status failed) are emitted when the caller cannot manage the existing user.

Atomicity — non-atomic: failures during creation/update (Logto rate limits, RBAC denials) are reported per-row; rows that succeeded before the failure are kept.

application/json

Body Required

  • import_id string(uuid) Required

    Import session ID from the validate response. Sessions expire after 30 min.

  • override boolean

    When true, all rows with status warning are UPDATEd using the CSV values (existing entity is looked up by email/name and overwritten field-by-field). When false or omitted, warning rows are skipped.

    Default value is false.

  • resolutions object

    Per-row organization choices for ambiguous rows, keyed by row number as a string (e.g. "7"). The chosen organization_id must be one of the candidates[].logto_id values returned by /import/validate for that row. Ambiguous rows without a resolution are skipped.

    Hide resolutions attribute Show resolutions attribute object
    • * object Additional properties
      Hide * attribute Show * attribute object
      • organization_id string Required

        The chosen organization Logto ID — must match one of the candidates[].logto_id returned for that row

Responses

  • 200 application/json

    Import results

    Hide response attributes Show response attributes object
    • code integer
    • message string
    • data object

      Per-row outcome of /import/confirm. The operation is non-atomic: a partial failure does not roll back the rows that were already created or updated. The four counters always sum to total_rows of the validate response.

      Hide data attributes Show data attributes object
      • created integer
      • updated integer
      • skipped integer
      • failed integer
      • results array[object]
        Hide results attributes Show results attributes object
        • row_number integer
        • status string

          Per-row outcome:

          • created — entity successfully created (id populated).
          • updated — entity already existed and was overwritten (only when override: true was passed and the row had status warning at validate time). id populated.
          • skipped — row was not imported. The reason field explains why.
          • failed — import was attempted but rejected (e.g. Logto sync failure, RBAC denial when moving a user to a non-managed org). error populated.

          Values are created, updated, skipped, or failed.

        • id string

          Entity ID. Populated when status is created or updated.

        • reason string

          Populated only when status is skipped:

          • error — the row had blocking errors at validate time.
          • warning_not_overridden — the row was a warning (existing entity) but override was not set.
          • ambiguous_unresolved — the row was ambiguous and no resolution was provided.

          Values are error, warning_not_overridden, or ambiguous_unresolved.

        • error string

          Failure reason. Populated only when status is failed.

  • 400 application/json

    Bad request - validation error

    Hide response attributes Show response attributes object
    • code integer

      HTTP error code

    • message string

      Error message

    • data object
      Hide data attributes Show data attributes object
      • type string

        Type of error

        Values are validation_error or external_api_error.

      • errors array[object]
        Hide errors attributes Show errors attributes object
        • key string

          Field name that failed validation

        • message string

          Error code or message

        • value string

          Value that failed validation

      • details

        Additional error details

  • 401 application/json

    Unauthorized - invalid or missing token

    Hide response attributes Show response attributes object
    • code integer
    • message string
    • data object | null
  • 403 application/json

    Forbidden - insufficient permissions

    Hide response attributes Show response attributes object
    • code integer
    • message string
    • data object | null
POST /users/import/confirm
curl \
 --request POST 'https://collect.your-domain.com/api/users/import/confirm' \
 --header "Authorization: Bearer $ACCESS_TOKEN" \
 --header "Content-Type: application/json" \
 --data '{"import_id":"3312b187-a8b0-45f5-b23a-98798b64eb31","override":true,"resolutions":{"7":{"organization_id":"abc123"}}}'
Request examples
{
  "import_id": "3312b187-a8b0-45f5-b23a-98798b64eb31",
  "override": true,
  "resolutions": {
    "7": {
      "organization_id": "abc123"
    }
  }
}
Response examples (200)
{
  "code": 200,
  "message": "users imported successfully",
  "data": {
    "created": 2,
    "updated": 1,
    "skipped": 2,
    "failed": 1,
    "results": [
      {
        "row_number": 2,
        "status": "created",
        "id": "usr_2k3lf9d8sn"
      },
      {
        "row_number": 3,
        "status": "created",
        "id": "usr_8skd0w29df"
      },
      {
        "row_number": 4,
        "status": "skipped",
        "reason": "error"
      },
      {
        "row_number": 5,
        "status": "skipped",
        "reason": "error"
      },
      {
        "row_number": 6,
        "status": "updated",
        "id": "usr_existing01"
      },
      {
        "row_number": 7,
        "status": "failed",
        "error": "logto sync failed: rate limit exceeded"
      }
    ]
  }
}
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": {}
}