Skip to main content

Migration Overview

User Migration endpoints let an integration import an existing user base into the tokenization platform via API. They cover the cases where users are created in bulk during an onboarding cut-over instead of one-by-one through the standard Create User flow.

Two Wallet Flows

The platform can either use a wallet address you already have for the user, or generate a custodial wallet on your behalf. The migration endpoints split along this axis:

FlowWhen to useEndpoints
With WalletThe user already has a wallet address (self-custody or migrated from elsewhere)./migrate/with-wallet, /migrate/with-wallet/bulk, /migrate/with-wallet/csv
Without WalletThe platform should generate a wallet for the user (asynchronous)./migrate/without-wallet, /migrate/without-wallet/bulk, /migrate/without-wallet/csv

Each flow has three transports:

  • Single — JSON body with a single user object.
  • Bulk — JSON body with an array of up to 500 users; per-row results returned.
  • CSV — Multipart upload (form field file); per-row results returned. Maximum file size: 5 MB, maximum rows: 500.

Identity Rules (KVKK-Aware)

Personal information (email, name, surname) is optional in migration. At least one of the following two identifiers must be present per row:

  • email — standard identifier.
  • external_id — opaque identifier from your system; used when you must not store personal data under KVKK / GDPR.

Both can be supplied together. Behaviour by identifier combination:

emailexternal_idOutcome
Classic mode — email/name/surname stored.
KVKK mode — only external_id stored. email, name, surname are dropped.
Hybrid mode — both stored. Duplicate checks run on both.
Row rejected with reason missing_email_and_external_id.

For with-wallet endpoints, wallet_address is always required regardless of identity mode.

KYC Behaviour (kyc_sent)

Each migration row carries a kyc_sent flag indicating whether KYC has already been delivered for this user in your previous system.

kyc_sentWhat the platform does
false (default)Standard flow — the platform publishes a KYC token event for this user.
trueThe user is marked as already KYC-verified (kyc.is_verified=true, kyc.token_sent=true) and no KYC event is published. The wallet-generation event for the without-wallet flow propagates skip_kyc=true to the downstream consumer.

Duplicate Handling

Per-row duplicate checks run before user creation. A duplicate is skipped, not failed, so a single bad row does not abort a bulk import.

ReasonCondition
duplicate_emailAn existing user has the same email.
duplicate_external_idAn existing user has the same external_id (trim-normalized).
duplicate_wallet(with-wallet only) An existing user has the same wallet address (case-insensitive).
missing_email_and_external_idNeither identifier supplied.

Bulk Response Shape

All bulk and CSV endpoints return the same response shape — an aggregate header and per-row results:

{
"total": 3,
"success": 2,
"skipped": 1,
"failed": 0,
"results": [
{ "email": "a@example.com", "user_id": "6913872755654191dede37da", "status": "created" },
{ "external_id": "EXT-1", "user_id": "6913872755654191dede37db", "status": "created" },
{ "email": "dup@example.com", "status": "skipped", "reason": "duplicate_email" }
]
}
  • statuscreated | skipped | failed.
  • reason is present on skipped and failed rows; absent on created.
  • user_id is present only for created rows.
  • Either email or external_id (or both) is always present, mirroring the row's identity.

When Single Mode Fails

For the single (non-bulk) endpoints, duplicates and missing-identity errors return HTTP 400 with a message containing the same reason string, instead of a per-row report.

Authorization

All migration endpoints require a valid integration JWT (Authorization: Bearer …) and respect the same client/mTLS/IP/rate/usage middleware as the rest of the integration API. See the Integration Overview for details.