Skip to content

Importing multiple users

In addition to single user import, Privy also allows you to import your users in batches via REST API to simplify the migration process. You may import up to 20 users in a single request.

Using the REST API

Make a POST request to:

sh
https://auth.privy.io/api/v1/users/import

In the body of the request, include a users field with an array of up to 20 ImportUser objects. You can see the types for this object, and the nested LinkedAccount objects below.

 See the types for `ImportUser` objects

ImportUserObject

FieldTypeDescription
linked_accountsLinkedAccount[]An array including all of the user's linked accounts.
create_embedded_walletboolean(Optional) Whether to pregenerate an embedded wallet for the imported user. Defaults to false.

TIP

Looking for the types of LinkedAccounts?

 See a breakdown of different account types and the data they include

Note:

  • Each account should be a JSON object including all the necessary fields for that account type. Valid account types are 'custom_jwt', 'discord_oauth', 'farcaster', 'github_oauth', 'google_oauth', 'instagram_oauth', 'linkedin_oauth', 'spotify_oauth', 'twitter_oauth', 'email', 'phone' and 'wallet'. See below for what additional information for each account type. Please exclude the verifiedAt field.
  • If importing a user with a custom_jwt account, the custom_jwt account must be the only element of the linked_accounts array. It is not permitted to import a user with a custom_jwt account and other linked_accounts.

CustomJwtAccount extends LinkedAccount

FieldTypeDescription
type'custom_jwt'N/A
custom_idstringID of user from Twitter user API response.

DiscordAccount extends LinkedAccount

FieldTypeDescription
type'discord_oauth'N/A
subjectstringID of user from Discord user API response.
emailstringEmail of user from Discord user API response
usernamestringUsername of user from Discord user API response. Include the 4-digit discriminator prefixed by '#'.

(See Discord docs)

EmailAccount extends LinkedAccount

FieldTypeDescription
type'email'N/A
addressstringEmail address of user account.

FarcasterAccount extends LinkedAccount

FieldTypeDescription
type'farcaster'N/A
fidstringFID of the user from Farcaster user API response.
owner_address stringWallet address of the user from Farcaster user API response. Note that this is the Farcaster wallet address, and not the Privy embedded wallet address.
usernamestring(Optional) Username of user from Farcaster user API response. Do not include the '@'.
display_namestring(Optional) Display name of user from Farcaster user API response.
biostring(Optional) Bio of user from Farcaster user API response.
profile_picture_urlstring(Optional) Profile picture URL of the user from Farcaster user API response. Must be a valid image URL.
homepage_urlstring(Optional) Profile URL of the user from Farcaster user API response.

(See Farcaster docs. Note that the Privy import interface differs slightly from the Farcaster public interface in order to maintain consistency with other Privy LinkedAccount types.)

GithubAccount extends LinkedAccount

FieldTypeDescription
type'github_oauth'N/A
subjectstringID of user from GitHub user API response.
emailstringEmail of user from GitHub user API response
namestringName of user from GitHub user API response
usernamestringUsername of user from GitHub user API response

(See GitHub docs)

GoogleAccount extends LinkedAccount

FieldTypeDescription
type'google_oauth'N/A
subjectstringsub pulled from Google-provided JWT with "openid" scope.
emailstringemail from Google-provided JWT with "email" scope.
namestringname from Google-provided JWT with "profile" scope.

InstagramAccount extends LinkedAccount

FieldTypeDescription
type'instagram_oauth'N/A
subjectstringID of user from Instagram user API response.
username stringThe name displayed on a user's profile from Instagram's /me API response.

(See Instagram docs)

LinkedinAccount extends LinkedAccount

FieldTypeDescription
type'linkedin_oauth'N/A
subjectstringID of user from LinkedIn user API response.
email stringEmail of user from LinkedIn user API response
namestringName of user from LinkedIn user API response. Do not include the '@'.

(See Linkedin docs)

PhoneAccount extends LinkedAccount

FieldTypeDescription
type'email'N/A
numbertruePhone number of user account (non-international numbers default to US).

SpotifyAccount extends LinkedAccount

FieldTypeDescription
type'spotify_oauth'N/A
subjectstringID of user from Spotify user API response.
emailstringEmail of user from Spotify user API.
name stringThe name displayed on a user's profile from Spotify display_name API response.

(See Spotify docs)

TwitterAccount extends LinkedAccount

FieldTypeDescription
type'twitter_oauth'N/A
subjectstringID of user from Twitter user API response.
name stringName of user from Twitter user API response
usernamestringUsername of user from Twitter user API response. Do not include the '@'.
profile_picture_urlstring(Optional) Profile picture URL of the user from Twitter user API response. Must be a valid image URL.

(See Twitter docs)

WalletAccount extends LinkedAccount

FieldTypeDescription
type'wallet'N/A
chain_type'ethereum'Type of chain for the wallet. Only EVM chains ('ethereum') are currently supported.
addressstringChecksummed wallet address.

INFO

User import endpoints have heavier rate limit of 240 users per minute. If you are being rate limited, responses will have status code 429. We suggest you setup exponential back-offs starting at 1 second to seamlessly recover.

Below is a sample cURL command for importing multiple new users into Privy:

bash
$ curl --request POST https://auth.privy.io/api/v1/users/import \
-u "<your-privy-app-id>:<your-privy-app-secret>" \
-H "privy-app-id: <your-privy-app-id>" \
-H "Content-Type: application/json" \
-d '{
   "users": [
       {
           "linked_accounts": [
               {
                   "type": "github_oauth",
                   "subject": "837163725915354975",
                   "username": "Smiles",
                   "name": "The Joker",
                   "email": "[email protected]",
                   "profile_picture_url": "https://i.imgur.com/joker.jpg"
               }
           ]
       },
       {
           "linked_accounts": [
               {
                   "type": "wallet",
                   "chain_type": "ethereum",
                   "address": "0xd8da6bf26964af9d7eed9e03e53415d37aa96045"
               }
           ]
       },
       {
           "linked_accounts": [
               {
                   "type": "phone",
                   "number": "18888675309"
               }
           ]
       },
       {
           "linked_accounts": [
               {
                   "type": "email",
                   "address": "[email protected]"
               }
           ]
       }
   ]
}'

A successful response will include a list of results along with a few aggregates displaying the number of created, number of failed, and total number of users.

A result can either be successful or unsuccessful. This will be a CreateResultSuccess or CreateResultFailure object respectively; you can see the types for these objects below. A "success" status code from the API (e.g. 200) does not imply that all imports necessarily succeeded.

 See the types for CreateResult objects

CreateResult Object

FieldTypeDescription
action"create"The type of action performed on this object's input index.
indexnumber Index of input in request body that this action is being performed on.
successbooleanWhether the action was successful or not.

CreateResultSuccess extends CreateResult

FieldTypeDescription
successtrueIndicates the import action was successful.
idstringPrivy DID of the newly created user account.

CreateResultFailure extends CreateResult

FieldTypeDescription
successfalseIndicates the import action failed.
codenumberKnown error code representing failure reason.
errorstringHuman readable error message.
causestring Machine readable error cause (i.e. DID of offending user).

Error Codes and Causes

CodeErrorCause Description
100Unknown error.N/A
101Account conflict caused by an existing user. Multiple users cannot share the same account.DID of first existing user that conflicts with input. If the import contains multiple linked accounts, there may be more than one conflict.

Once a user has been imported into Privy, if they log in, all of their imported accounts (wallet, email, etc.) will be included in their user object.

If the imported user has an embedded wallet, that wallet will be available to the user upon sign in.

Below is a sample successful response for importing multiple new users into Privy:

json
{
  "results": [
    {
      "action": "create",
      "index": 0,
      "success": true,
      "id": "did:privy:clfn2wysq01ijykc8gyq2j2t1"
    },
    {
      "action": "create",
      "index": 1,
      "success": false,
      "code": 101,
      "error": "Account conflict caused by an existing user. Multiple users cannot share the same account.",
      "cause": "did:privy:clfmxole300rmykc89nojp3v2"
    }
  ]
}

INFO

When a user logs in to your app with Google or Apple OAuth, if there is an existing email account associated with the email address used for Google/Apple OAuth, Privy will automatically authenticate the user into the existing email account. This helps prevent account duplication issues where a single user may have multiple accounts unnecessarily.


When importing users to Privy, Privy will not automatically merge Apple, Google, and email users with the same email address if imported separately. This is to support migrations from authentication systems that do not automatically merge Apple, Google, and email accounts on the basis of email address.


If you'd like to import a Google account and email account to have the same Privy DID, you must import both accounts in the same linked_accounts array. If you import each account separately, they will have different Privy DIDs.