> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Import and Export Users

> Bulk-load Didit vendor User entities from CSV and export the full user table for external compliance tooling. Available from the Business Console.

export const AgentPromptAccordion = ({prompt, title = "AI Agent Integration Prompt"}) => {
  const [copied, setCopied] = React.useState(false);
  const handleCopy = e => {
    e.stopPropagation();
    if (!prompt) return;
    navigator.clipboard.writeText(prompt.trim()).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  const agents = ["Claude Code", "Codex", "Cursor", "Devin", "Windsurf", "GitHub Copilot"];
  return <div className="didit-agent-card">
      {}
      <div className="didit-agent-titlebar">
        <div className="didit-agent-dots" aria-hidden="true">
          <span className="didit-agent-dot didit-agent-dot-red"></span>
          <span className="didit-agent-dot didit-agent-dot-yellow"></span>
          <span className="didit-agent-dot didit-agent-dot-green"></span>
        </div>
        <span className="didit-agent-filename">{title}</span>
        <button type="button" className={`didit-agent-copy ${copied ? "didit-agent-copy-copied" : ""}`} onClick={handleCopy} title="Copy prompt to clipboard" aria-label={copied ? "Copied!" : "Copy prompt to clipboard"}>
          {copied ? <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <path d="M3 8.5l3.5 3.5L13 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
              <span>Copied</span>
            </> : <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5" />
                <path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" stroke="currentColor" strokeWidth="1.5" />
              </svg>
              <span>Copy</span>
            </>}
        </button>
      </div>

      {}
      <pre className="didit-agent-body"><code>{prompt.trim()}</code></pre>

      {}
      <div className="didit-agent-footer">
        <span className="didit-agent-footer-label">Paste into</span>
        <div className="didit-agent-chips">
          {agents.map(name => <span key={name} className="didit-agent-chip">{name}</span>)}
        </div>
      </div>
    </div>;
};

<AgentPromptAccordion
  title="Bulk Import / Export Users Prompt"
  prompt={`# Goal — bulk-load (or extract) Didit User entities programmatically

CSV import / export is a console-only UI feature. For programmatic bulk operations, loop \`POST /v3/users/create/\` for imports, optionally upload trusted profile faces with \`POST /v3/organization/{organization_id}/application/{application_id}/vendor-users/by-id/{didit_internal_id}/faces/upload/\`, and paginate \`GET /v3/users/\` for exports. There is no public CSV API endpoint.

## Bulk import — loop the create endpoint

\`\`\`bash
# users.json — array of records you want to seed into Didit
# [{"vendor_data":"user-1","display_name":"Alice"}, ...]

jq -c '.[]' users.json | while read user; do
curl -X POST https://verification.didit.me/v3/users/create/ \\
-H "x-api-key: $DIDIT_API_KEY" \\
-H "Content-Type: application/json" \\
-d "$user"
sleep 0.1   # respect rate limits — see /integration/rate-limiting
done
\`\`\`

Record shape (matches \`POST /v3/users/create/\`):

\`\`\`json
{
"vendor_data": "user-42",          // required, unique per app
"display_name": "Jane Doe",        // optional
"metadata": {"tier": "premium"},   // free-form JSON
"tags": ["promo-2026"]             // optional array of strings
}
\`\`\`

\`create\` is **not** idempotent — a duplicate \`vendor_data\` returns conflict. Branch on existence:

\`\`\`bash
# Idempotent upsert pattern
if curl -s -o /dev/null -w "%{http_code}" \\
 https://verification.didit.me/v3/users/$VD/ \\
 -H "x-api-key: $DIDIT_API_KEY" | grep -q 200; then
curl -X PATCH https://verification.didit.me/v3/users/$VD/ \\
-H "x-api-key: $DIDIT_API_KEY" -H "Content-Type: application/json" \\
-d "$BODY"
else
curl -X POST https://verification.didit.me/v3/users/create/ \\
-H "x-api-key: $DIDIT_API_KEY" -H "Content-Type: application/json" \\
-d "$BODY"
fi
\`\`\`

## Import faces from another provider

If each imported user has a trusted face image, upload it after the User exists:

\`\`\`bash
# GET /v3/users/{vendor_data}/ or POST /v3/users/create/ first.
# Use the returned didit_internal_id for the profile attachment endpoint.
curl -X POST https://verification.didit.me/v3/organization/$ORGANIZATION_ID/application/$APPLICATION_ID/vendor-users/by-id/$DIDIT_INTERNAL_ID/faces/upload/ \\
-H "x-api-key: $DIDIT_API_KEY" \\
-H "Content-Type: application/json" \\
-d '{"image": "<base64-encoded-jpg-or-png>", "comment": "Imported from previous KYC provider"}'
\`\`\`

This stores the face on the User profile and enrolls it for duplicate detection and face search. It does not blocklist the face; use /management-api/lists/upload-face for blocklists.

## Bulk export — paginate the list endpoint

\`\`\`bash
PAGE=1
while :; do
RESP=$(curl -sG https://verification.didit.me/v3/users/ \\
-H "x-api-key: $DIDIT_API_KEY" \\
--data-urlencode "page=$PAGE" \\
--data-urlencode "page_size=100")
echo "$RESP" | jq -c '.results[]' >> users.ndjson
HAS_NEXT=$(echo "$RESP" | jq -r '.next // empty')
[ -z "$HAS_NEXT" ] && break
PAGE=$((PAGE+1))
done
\`\`\`

Filter with the same query params as \`GET /v3/users/\` (\`status\`, \`search\`, date ranges, etc).

## What survives on update vs what stays

| Field | Behaviour |
|---|---|
| \`display_name\`, \`metadata\`, \`tags\` | Overwritten by PATCH |
| \`full_name\`, \`date_of_birth\`, \`portrait_image\` | Read-only after a verified session (you can override via PATCH but it's logged as a manual override) |
| Aggregate counters (\`session_count\`, \`approved_count\`, etc.) | Computed by Didit — never accept writes |
| \`features\` map | Computed — never accept writes |

## Failure modes

- \`409\` on create — \`vendor_data\` exists; switch to PATCH.
- \`429\` — rate limit; back off using \`Retry-After\`. Don't slam the API; insert 100ms+ between calls.
- \`400\` — bad JSON or invalid country/status enum. Log the row and continue.
- Partial failures: track \`vendor_data\` → status per row and retry only failures.

## Console fallback

For one-off migrations bigger than 100K rows, the Business Console CSV import/export (Users → Import / Export) is fast and skips writing your own loop. The console runs the same validations and produces an error report.

## Sources of truth

- /management-api/users/create — POST /v3/users/create/ schema
- /management-api/users/list — GET /v3/users/ pagination + filters
- /entities/users/data-model — mutable vs read-only fields
- /integration/rate-limiting — rate limits and Retry-After
- /integration/integration-prompt — canonical end-to-end prompt
`}
/>

The Business Console lets your team bulk-manage User entities without writing code. Two flows are supported: **CSV import** (create or update users at scale) and **CSV export** (extract the user table for external tooling).

<Note>
  Bulk import/export here creates or updates User profiles only. It does not recreate historical KYC sessions, document warnings, liveness evidence, or provider decisions. For a full migration from Sumsub, MetaMap, Veriff, Onfido, Persona, or another provider, see [Import Data from Another Provider](/sessions-api/import-verifications).
</Note>

## Import users from CSV

### When to use it

* **Day-one migration** from another KYC vendor — bring your existing user roster over.
* **Provider face migration** — attach trusted face images to the imported User profiles after the rows are created.
* **Bulk onboarding** of a partner's customer list.
* **Metadata backfill** — update tags, tier, or internal identifiers on many users at once.

### Template columns

Download the template from *Users → Import → Download template*. Columns:

| Column          | Required | Description                                                   |
| --------------- | -------- | ------------------------------------------------------------- |
| `vendor_data`   | Yes      | Your identifier for the user. Must be unique per application. |
| `display_name`  | No       | Human-readable name shown in the console.                     |
| `full_name`     | No       | Legal name. Will be overwritten by verified session data.     |
| `email`         | No       | Known contact email.                                          |
| `phone`         | No       | Known contact phone (E.164 format).                           |
| `country`       | No       | ISO 3166-1 alpha-3 country code.                              |
| `status`        | No       | `ACTIVE`, `FLAGGED`, or `BLOCKED`. Defaults to `ACTIVE`.      |
| `tags`          | No       | Comma-separated tags.                                         |
| `metadata_json` | No       | Stringified JSON for custom metadata.                         |

### Running the import

<Steps>
  <Step title="Upload the CSV">
    Navigate to *Users → Import → Upload CSV* and select your file.
  </Step>

  <Step title="Review the preview">
    The console validates each row. Invalid rows are highlighted with error reasons (e.g. duplicate `vendor_data`, invalid country code).
  </Step>

  <Step title="Start the import">
    Didit processes the rows asynchronously. You can close the tab — progress is tracked in *Users → Import history*.
  </Step>

  <Step title="Review results">
    When the import finishes, the history view shows counts of created, updated, and skipped rows, plus a downloadable error report.
  </Step>
</Steps>

### Update vs create

* If a `vendor_data` does not exist, a new User is created.
* If a `vendor_data` already exists, the row updates the existing User (profile fields and metadata).
* Aggregate counters and verified fields (`full_name`, `date_of_birth` from approved sessions) are **never overwritten** by imports.

### Import faces after user creation

CSV import creates or updates the User row. It does not include face images in the CSV. To bring over face biometrics from another provider, upload each face after the target User exists:

<Steps>
  <Step title="Create or import the User">
    Use the console CSV import, or call [`POST /v3/users/create/`](/management-api/users/create) in a loop.
  </Step>

  <Step title="Read didit_internal_id">
    Fetch the User with [`GET /v3/users/{vendor_data}/`](/management-api/users/get). The response includes `didit_internal_id`.
  </Step>

  <Step title="Upload the face">
    Call [`POST /v3/organization/{organization_id}/application/{application_id}/vendor-users/by-id/{didit_internal_id}/faces/upload/`](/management-api/users/upload-face) with a raw base64 face image.
  </Step>
</Steps>

```bash theme={null}
curl -X POST https://verification.didit.me/v3/organization/$ORGANIZATION_ID/application/$APPLICATION_ID/vendor-users/by-id/$DIDIT_INTERNAL_ID/faces/upload/ \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "image": "<base64-encoded-jpg-or-png>",
    "comment": "Imported from previous KYC provider"
  }'
```

The face must be a valid image, max 2 MB decoded, and contain a detectable face. Didit stores it on the profile and indexes it for duplicate detection and face search. It is not added to a face blocklist unless you separately use the [Upload face to list](/management-api/lists/upload-face) endpoint.

## Export users to CSV

### When to use it

* **Compliance audits** — deliver the full user table to auditors.
* **Data warehouse sync** — periodic dump into your BI tooling.
* **SAR / regulator requests** — extract filtered subsets of users.

### Running an export

1. Navigate to *Users → Export*.
2. Apply filters (status, country, date range, search).
3. Choose columns (the default is every field; you can drop columns you don't need).
4. Submit. Didit generates the export asynchronously and emails a signed download link to the requesting operator.

<Note>
  Exports include only data your role is allowed to read. COMPLIANCE\_OFFICER and above can export the full table; DEVELOPER and READER roles have restricted exports.
</Note>

## Performance characteristics

| Operation | Typical scale        | Notes                                                              |
| --------- | -------------------- | ------------------------------------------------------------------ |
| Import    | Up to 100,000 rows   | Larger files are chunked server-side; progress is polled by the UI |
| Export    | Up to 1,000,000 rows | Larger exports are streamed to S3 and delivered via signed URL     |

## Audit trail

Every import and export is logged in the [audit log](/console/audit-logs):

* The operator who triggered it.
* Row counts (imports) / filter snapshot (exports).
* Timestamps and success/failure state.

## Next steps

<CardGroup cols={3}>
  <Card title="Create user API" icon="plus" href="/management-api/users/create">
    Programmatic creation for scripted imports.
  </Card>

  <Card title="Upload user face" icon="face-viewfinder" href="/management-api/users/upload-face">
    Attach imported face images to User profiles.
  </Card>

  <Card title="Data retention" icon="shield-halved" href="/console/data-retention">
    How long imported data persists.
  </Card>

  <Card title="Audit logs" icon="file-lines" href="/console/audit-logs">
    Review import/export history.
  </Card>
</CardGroup>
