Skip to main content

Staged import with preview

CSV upload and directory-connector syncs now feed a common pipeline: matching rules, a preview you can read before anything is applied, master-priority protection so one source cannot overwrite another's attribute, and how to delete a source you no longer need.

Written for whoever runs IT9 min readUpdated

This is a different, newer import path from the one described in Bulk importing users. That article's CSV/JSON endpoints create fixed-role member accounts with no matching logic — good for a one-off batch of brand-new people. The pipeline in this article is built for repeated, ongoing imports from a real source of truth: it can create, link, update, deactivate or skip a person depending on what it finds, and it always shows you a preview before it writes anything.

Console: Identity → Import, at /iam/import. The same pipeline is what feeds the Active Directory agent's sync loop and any directory-pull connector — a CSV you upload by hand and an automated directory sync both land here.

Matching rules

Each import source declares how it matches an incoming row against an existing user:

The three matching rules.
RuleMatches on
by_emailThe row's email address against an existing user's.
by_external_idThe identifier this specific source has previously recorded for a user — its own external reference, not a Qorionix id.
by_expressionA CEL expression in the mapping scope (see The expression language behind group rules, policies and mappings), reading the incoming row as appuser and the candidate existing user as user. Useful when neither email nor a stored external id is a reliable key — matching on an employee number, for instance.

What a match becomes

The five outcomes a preview can produce for one row.
OutcomeWhen
createNo existing user matches. A new account is created.
linkA match is found, but this source has never linked that user before — a first sighting.
updateA match is found and this source already links that user — a refresh of previously imported data.
deactivateA user this source previously linked is no longer present in the source, and the source has DeprovisionOnMissing enabled. Deactivation only ever touches users this exact source owns — an unrelated account that simply has no external id recorded is never touched by this.
skipNothing to do — the row already matches what's stored.

Preview, then apply

Running a source produces a preview: every row's decision, computed purely, with no writes at all. You read the preview — how many creates, links, updates, deactivations, skips — before anything happens to a real account. Apply is a separate, explicit action that commits exactly what the preview showed. For the Active Directory agent's own sync loop, preview and apply run automatically on every scheduled pass rather than waiting for a click, so a directory sync actually converges rather than sitting as an unreviewed diff.

Every decision applied writes one row to the run's own record — this doubles as the run's audit trail; there is no separate audit table to cross-reference. GET .../runs/{id} shows you a run's outcome and its per-record decisions. While a run is being applied it sits in an applying state; it only reaches its terminal applied or failed state once every record has been written.

Master-priority protection

Every import source has a priority (lower number wins). When two sources both write to the same attribute on the same user — your AD sync says one department, a CSV upload from HR says another — the write is compared against whichever source currently owns that specific attribute. A lower-priority source's write to an attribute a higher-priority source already owns is silently skipped for that one attribute — no error, no row failure — while every other attribute on the same record still writes normally. This is the same ErrAttributeManaged protection Profile fields and custom attributes describes for the unified schema's master_source field, applied per-source rather than per-attribute-definition.

CSV, and its ceiling

A CSV source is header-mapped (columns matched by name), and short rows are skipped and counted rather than aborting the whole file. The same 1,000-row ceiling as Bulk importing users applies here.

Deleting a source

Each source in the list has a delete action (a trash icon) that opens a confirmation dialog before removing anything. Deleting a source removes the source itself, every run staged or applied under it, and every per-record decision those runs produced — the confirmation says so, because there is no separate undo once you confirm.

The delete is refused with a 409 if one of that source's runs is currently applying — you cannot delete a source out from under a run that is mid-write. Wait for the run to finish (either applied or failed) and delete again. Deleting a source that does not exist, or that belongs to another organisation, returns a plain 404.

The endpoints

Staged import surface, under /organizations/{orgID}
POST/GET  .../iam/import/sources
DELETE    .../iam/import/sources/{id}                 409 if a run is applying
POST      .../iam/import/sources/{id}/runs           JSON body
POST      .../iam/import/sources/{id}/runs/csv        raw CSV body
GET       .../iam/import/runs/{id}
POST      .../iam/import/runs/{id}/apply

Console screen: sources, a way to stage a CSV inline, run history, a per-record diff, an apply action and a delete action per source — functional, though more compact than a fully polished admin screen.

Was this article wrong?

If a procedure here does not match what you see, or a limit we described has changed, tell us and we will fix the page. Email us about this article, or see how to get help if you need an answer rather than a correction.

Everything in identity and access