> ## Documentation Index
> Fetch the complete documentation index at: https://api-portal.etoro.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Register users, submit KYC, and sign them in

> End-to-end guide for a partner that creates etoro users from its own product, completes KYC and verification on their behalf, and opens etoro for them already signed in.

## Overview

This guide covers the journey for a partner that owns the customer relationship in its own product and uses etoro as the brokerage and identity layer underneath. The user never has to register on etoro first.

The journey has two parts, and they run on different channels:

| Part | Channel | What it achieves |
| - | - | - |
| **A. Account and KYC** | Partner backend to etoro APIs | Create the etoro user, prepare the profile, and submit verification evidence |
| **B. Silent login** | User's browser, with the partner backend in the middle | Open etoro in the user's browser already signed in, with no password prompt |

```text theme={null}
Partner backend                                   etoro
  1. Obtain a partner token ----------------------> client credentials
  2. Check eligibility and consent documents ------> GET  /registration/users/eligibility
  3. Register the user ----------------------------> POST /registration/users
  4. Issue a non-interactive token (NIT) ----------> user credential for later calls
  5. Submit profile, KYC and verification ---------> personal details, email, phone, verification

Browser + partner backend                         etoro
  6. Browser POSTs silent-login initiate ----------> etoro sets a short-lived cookie
  7. Browser follows redirectUrl back with tid
  8. Partner backend exchanges tid for a one-time token
  9. Browser opens the returned verify URL --------> user is signed in
```

> [!NOTE]
>
> Access is provisioned per partner. Before you start, your etoro contact must have set up your application (client credentials and redirect URIs), the scopes below, your API key, and the allow-listing of your egress IP addresses. See [Before you start](#before-you-start).

All endpoints in this guide are production endpoints. The authorization server is `https://www.etoro.com`; the API is `https://public-api.etoro.com`.

## Before you start

Ask your etoro contact to confirm each of the following:

**1. Application and credentials**

A `client_id` and `client_secret` for your application, and the exact redirect URI or URIs you will use for silent login. Keep the secret on your backend only.

**2. Scopes**

The scopes this journey uses are listed below; the full scope map is in [Permitted operations and scopes](https://api-portal.etoro.com/partners/integration.md#permitted-operations-and-scopes). A scope outside your grant is rejected.

| Used for | Scope |
| - | - |
| Registration | `etoro-public:partner:registration:read`, `etoro-public:partner:registration:write` |
| Personal details and address | `etoro-public:verification.personaldetails:read` / `:write`, `etoro-public:verification.address:read` / `:write` |
| KYC questionnaire | `etoro-public:kyc:read`, `etoro-public:kyc:write`, `etoro-public:kyc:questions:read`, `etoro-public:kyc:answers:read` / `:write`, `etoro-public:kyc:regulations:read` |
| Trusted verification | `etoro-public:verification.trusted.email:write`, `etoro-public:verification.trusted.phone:write`, `etoro-public:verification:trusted:user:write` |
| Silent login | No scope on the call. The application must be enabled for cookie-bound silent login, and the callback URI must be registered |

**3. API key and IP allow-listing**

An `x-api-key` for your application, and your egress addresses registered with etoro, as described in the [integration prerequisites](https://api-portal.etoro.com/partners/integration.md#prerequisites).

**4. Trusted-partner entitlement**

Direct email and phone confirmation, and trusted user verification, are available only to partners etoro has approved as trusted, and only for users your application created.

## Part A: Create the user and submit KYC

### 1. Obtain a partner token

Authenticate your application with the OAuth 2.0 client credentials grant. No end user is involved at this stage.

```bash theme={null}
curl -X POST "https://www.etoro.com/api/sso/v1/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "client_id=<CLIENT_ID>" \
  --data-urlencode "client_secret=<CLIENT_SECRET>"
```

The response contains an `access_token` and the scopes granted to your application. Send the client secret in the form body, as above, or as HTTP Basic — not both. Request a new token when it expires.

### 2. Check eligibility and get the consent documents

Which legal documents a user must accept depends on their country. Always fetch them for the country you are onboarding; never hard-code document identifiers.

```bash theme={null}
curl -X GET "https://public-api.etoro.com/api/v1/registration/users/eligibility?countryIso=AU" \
  -H "x-request-id: <UUID>" \
  -H "Authorization: Bearer <PARTNER_TOKEN>"
```

```json Response 200 theme={null}
{
  "countryIso": "AU",
  "eligibility": { "blocked": false, "restricted": false },
  "documents": [
    { "documentId": 279, "type": "PrivacyPolicy" },
    { "documentId": 280, "type": "CookiePolicy" }
  ]
}
```

If `blocked` or `restricted` is `true`, do not continue to registration for that country. Present every returned document to the user, and record the time they accepted it and their IP address.

### 3. Register the user

Send one `consents` entry for **every** document returned in step 2.

```bash theme={null}
curl -X POST "https://public-api.etoro.com/api/v1/registration/users" \
  -H "x-request-id: <UUID>" \
  -H "Authorization: Bearer <PARTNER_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
        "username": "<UNIQUE_USERNAME>",
        "email": "<USER_EMAIL>",
        "countryIso": "AU",
        "languageIso": "en",
        "partnerCorrelationId": "<YOUR_REFERENCE>",
        "consents": [
          { "documentId": 279, "signedAt": "<UTC_ISO8601>", "clientIp": "<END_USER_IP>" },
          { "documentId": 280, "signedAt": "<UTC_ISO8601>", "clientIp": "<END_USER_IP>" }
        ]
      }'
```

| Field | Required | Notes |
| - | - | - |
| `username` | Yes | 4 to 20 characters, unique |
| `email` | Yes | Unique |
| `countryIso` | Yes | ISO 3166 country code |
| `consents` | Yes | One entry per document from step 2. `signedAt` is UTC ISO 8601, within the last 24 hours and not more than 10 minutes in the future. `clientIp` is the end user's IPv4 address, not your server's |
| `partnerCorrelationId` | No | Your own reference for the user, up to 100 characters. Keep it; it is useful for support and audit |
| `languageIso`, `regionIsoCode` | No | Preferred language, and sub-national region where it applies (for example `US-TX`) |

No password is supplied; etoro generates one. A successful call returns `201` and a token bundle for the new user:

```json Response 201 theme={null}
{
  "success": true,
  "data": {
    "username": "<UNIQUE_USERNAME>",
    "partnerCorrelationId": "<YOUR_REFERENCE>",
    "loginInfo": {
      "id_token": "<ID_TOKEN>",
      "access_token": "<USER_ACCESS_TOKEN>",
      "token_type": "bearer",
      "expires_in": 600,
      "scope": "<GRANTED_SCOPES>"
    },
    "loginSucceeded": true
  }
}
```

Persist two values from this response:

* The `sub` claim of the `id_token`. On a partner application this is the user's **`externalUserId`**, and you need it for [silent login](#part-b-sign-the-user-in-silently). It is not an etoro customer id.
* The `access_token`. It is short-lived; honor `expires_in`. Use it as a bearer token for the next user-scoped calls, then replace it with a non-interactive token.

Common errors: `409` for a username, email, or `partnerCorrelationId` that is already in use (use new values); `422` for an invalid or missing consent, or a stale `signedAt`. See [Errors and rate limits](#errors-and-rate-limits) for the rest.

### 4. Issue a non-interactive token

The registration `access_token` expires. To keep calling user-scoped endpoints from your backend after that, issue a **non-interactive token (NIT)** for the user.

The NIT published in this portal is the sub-account user token in [Issue a non-interactive token](https://api-portal.etoro.com/partners/integration.md#issue-a-non-interactive-token). That call needs the registration `access_token` and an `x-sub-account-id`, and it returns the secret only once. Store it in a secret store, and set `expiresAt` and `ipsWhitelist`.

> [!NOTE]
>
> That path applies when the user is an etoro trading sub-account of your application. If your application registers a user directly and you do not have a sub-account id, confirm the issuance path with your etoro contact before you build it. Do not call an internal host.

Until a NIT exists, user-scoped calls use `Authorization: Bearer` and the registration `access_token`. After it exists, they use your `x-api-key` and the user's `x-user-key`. The two are mutually exclusive; see [Issue a non-interactive token](https://api-portal.etoro.com/partners/integration.md#issue-a-non-interactive-token). The examples below show the key pair.

```bash theme={null}
curl -X GET "https://public-api.etoro.com/api/v1/users/personaldetails" \
  -H "x-request-id: <UUID>" \
  -H "x-api-key: <YOUR_API_KEY>" \
  -H "x-user-key: <NIT>"
```

### 5. Prepare the profile and submit KYC

Call the operations below for the new user. Each lists the scopes it needs in the [Partners API reference](https://api-portal.etoro.com/partners-api-reference).

| Step | Operation | Purpose |
| - | - | - |
| Read the profile | `GET /api/v1/users/personaldetails`, `GET /api/v1/users/address/{addressType}` | A newly registered user usually holds only the country. Read before you write |
| Update personal details | `PUT /api/v1/users/personaldetails` | Partial update: send only the fields you have, such as name, date of birth, citizenship, and tax identifications |
| Update the address | `PUT /api/v1/users/address/{addressType}` | The user's residential address |
| Read the questionnaire | `GET /api/v1/kyc-data/questionnaire` | A new user returns no answers yet |
| Submit answers | `POST /api/v1/kyc-data/questionnaire`, or `POST /api/v1/kyc-data/questionnaire/{questionId}` | All answers at once, or one question at a time |
| Check what is missing | `GET /api/v1/kyc/gaps` | Outstanding KYC requirements for the user |

Where your own provider has already verified the user, you can supply that evidence instead of asking the user to repeat it.

#### Confirm email and phone with your own evidence

Direct confirmation records that **you** verified the email or phone, so etoro does not send a one-time code. Both calls take a `verificationEvidence` object:

| Field | Required | Notes |
| - | - | - |
| `partnerReferenceId` | Yes | Your reference for the verification. Reusing the registration `partnerCorrelationId` keeps one reference per user |
| `verificationMethod` | Yes | One of `sms`, `voiceCall`, `inPerson`, `videoCall`, `documentCheck`, `documentVerification`, `bankVerification`, `governmentDb`, `other` |
| `verifiedAt` | Yes | When you verified it, in UTC |
| `externalProviderId` | When the method is `documentVerification` | Your verification provider |
| `externalReferenceId`, `additionalDetails` | No | The provider's reference and free-text context |

```bash Email theme={null}
curl -X POST "https://public-api.etoro.com/api/v1/users/email/verifications/direct-confirmations" \
  -H "x-request-id: <UUID>" \
  -H "x-api-key: <YOUR_API_KEY>" \
  -H "x-user-key: <NIT>" \
  -H "Content-Type: application/json" \
  -d '{
        "email": "<USER_EMAIL>",
        "verificationEvidence": {
          "partnerReferenceId": "<YOUR_REFERENCE>",
          "verificationMethod": "documentVerification",
          "verifiedAt": "<UTC_ISO8601>",
          "externalProviderId": "<YOUR_KYC_PROVIDER>"
        }
      }'
```

```bash Phone theme={null}
curl -X POST "https://public-api.etoro.com/api/v1/users/phone/verifications/direct-confirmations" \
  -H "x-request-id: <UUID>" \
  -H "x-api-key: <YOUR_API_KEY>" \
  -H "x-user-key: <NIT>" \
  -H "Content-Type: application/json" \
  -d '{
        "phoneCountryCode": "+61",
        "phoneNumber": "<NATIONAL_NUMBER_DIGITS>",
        "verificationEvidence": {
          "partnerReferenceId": "<YOUR_REFERENCE>",
          "verificationMethod": "sms",
          "verifiedAt": "<UTC_ISO8601>"
        },
        "enable2Fa": false
      }'
```

`phoneNumber` is digits only, without the country code, and must be a valid national number for that country. A `409` means the email or phone is already verified, or is in use by another user; where the verification was intentional, treat an "already verified" response as done.

#### Submit trusted user verification

When the user has reached the required verification level, submit your verification evidence to complete trusted verification:

```bash theme={null}
curl -X POST "https://public-api.etoro.com/api/v1/users/verification" \
  -H "x-request-id: <UUID>" \
  -H "x-api-key: <YOUR_API_KEY>" \
  -H "x-user-key: <NIT>" \
  -H "Content-Type: application/json" \
  -d '{
        "verificationEvidence": {
          "partnerReferenceId": "<YOUR_REFERENCE>",
          "verificationMethod": "document_verification",
          "verifiedAt": "<UTC_ISO8601>",
          "externalProviderId": "<YOUR_KYC_PROVIDER>"
        }
      }'
```

This operation requires the user to be at verification level 2 and then upgrades them to level 3. It is idempotent for a user already at level 3. If the user has not reached level 2, the call returns `400`; complete the earlier profile and verification steps and check `GET /api/v1/kyc/gaps` rather than retrying. Use the `verificationMethod` value `document_verification` on this operation; the enumeration in the table above applies to the email and phone confirmations.

> [!WARNING]
>
> Trusted verification asserts that **you** verified the user. It is only accepted for users your application created, and you are accountable for the evidence you submit. Never send evidence for a verification that did not take place.

## Part B: Sign the user in silently

Silent login opens etoro in the user's browser already signed in. It is a handshake between the browser, your backend, and etoro, and the order matters.

```text theme={null}
Your app (user clicks "Open etoro")
   |
   v
The user's browser POSTs initiate               <- etoro sets the login cookie here
   |
   v
The same browser follows redirectUrl            <- your callback receives tid and state
   |
   v
Your backend calls partner-login-token          -> returns { ott, redirectUrl }
   |
   v
Browser opens that redirectUrl                  <- cookie + ott establish the session
   |
   v
User is signed in on etoro
```

> [!WARNING]
>
> **Only etoro's own response may set the login cookie.** The cookie is created when the user's browser calls initiate. Your application must never create, copy, forge, or proxy it. A call from your server plants the cookie on the server, the browser never receives it, and verify fails. That is expected behavior, not a defect.

### 1. The user's browser calls initiate

There is no initiate page to open with query parameters. The user's browser sends:

```bash theme={null}
curl -X POST "https://www.etoro.com/api/sso/v2/silent-login/initiate" \
  -H "Content-Type: application/json" \
  -H "X-STS-DeviceId: <DEVICE_GUID>" \
  -d '{
        "fromClientId": "<CLIENT_ID>",
        "redirectUri": "<REGISTERED_REDIRECT_URI>",
        "state": "<OPAQUE_VALUE>"
      }'
```

The `curl` shows the contract. Run it in the user's browser (so the `Set-Cookie` is stored there), not on your server. `X-STS-DeviceId` is a GUID you generate for this handshake. `fromClientId` is your application's client id and must be a GUID. `redirectUri` must match a registered redirect URI character for character, including scheme and path. `state` is optional.

A successful call returns `201` and sets `etoro-silent-login-tid` (`HttpOnly`, `Secure`, domain `.etoro.com`, about five minutes):

```json Response 201 theme={null}
{
  "redirectUrl": "<REGISTERED_REDIRECT_URI>?tid=<TID>&state=<OPAQUE_VALUE>"
}
```

Navigate that same browser to `redirectUrl`. `tid` is a GUID with no dashes. When you sent `state`, it is appended and URL-encoded; when you omitted it, the callback has `tid` only. An unregistered `redirectUri` is rejected with `403`.

### 2. Exchange the tid for a one-time token

On your backend, read `tid` from the callback and identify the user. Authenticate with a partner token (see [step 1 of Part A](#1-obtain-a-partner-token)) and send the user's `externalUserId`, which is the `sub` claim saved at registration:

```bash theme={null}
curl -X POST "https://www.etoro.com/api/sso/v2/silent-login/partner-login-token" \
  -H "Authorization: Bearer <PARTNER_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{
        "tid": "<TID>",
        "externalUserId": "<ID_TOKEN_SUB>"
      }'
```

```json Response 201 theme={null}
{
  "ott": "<ONE_TIME_TOKEN>",
  "redirectUrl": "https://www.etoro.com/accounts/partners/silent-login/verify?ott=<ONE_TIME_TOKEN>"
}
```

`tid` must be the GUID from the callback. `externalUserId` must be the value saved from registration and must belong to your application; a value minted for another application is rejected.

### 3. Open the verify URL in the same browser

Redirect the browser to the `redirectUrl` **exactly as returned**. Do not change its host, path, or query string. The etoro page combines the cookie from step 1 with the one-time token, establishes the session, and lands the user on etoro signed in. You do not call the verify endpoint yourself.

### Timing and single use

* The cookie lives for about five minutes. Complete steps 1 to 3 promptly.
* The one-time token (`ott`) is single use. If a sign-in fails, start again from step 1; do not reuse an `ott` or a `tid`.
* The cookie must come from the **same browser** that opens the verify URL.

### Silent login errors

| Symptom | Likely cause | What to do |
| - | - | - |
| Verify fails with `SilentLoginCookieMissing` | The cookie is absent: the initiate call was not made by this browser, or the cookie expired | Run initiate in the user's browser again |
| Verify fails with `403` | The cookie does not match the one-time token, or was altered | Start a new handshake |
| Initiate returns `403` | `redirectUri` is not registered for `fromClientId` | Use a registered URI, or ask etoro to register it |
| Initiate returns `400` | `X-STS-DeviceId` or `fromClientId` is missing or not a GUID, or the body is empty | Send a GUID device id and a GUID client id |
| `partner-login-token` returns `400` | `tid` or `externalUserId` is missing, or `tid` is not a GUID | Send the `tid` from the callback and the saved `externalUserId` |
| Verify fails after a retry | The `ott` was already used or has expired | Start a new handshake |

## Errors and rate limits

Each operation's rate limit is stated in the [Partners API reference](https://api-portal.etoro.com/partners-api-reference); most share a default of 60 requests per 60 seconds, and exceeding it returns `429`. See [Rate limits](https://api-portal.etoro.com/core/getting-started/rate-limits.md). Common causes of failure across the guide:

| Symptom | Cause | Fix |
| - | - | - |
| `401` | Missing or invalid token, or key pair | Check the token has not expired and that you are not mixing bearer and key-pair authentication |
| `403` with `InsufficientPermissions` | The token lacks a required scope | Ask your etoro contact to add the scope to your application |
| `403` with `IpNotAllowed` | The caller's address is not allow-listed | Register your egress addresses with etoro |
| `403` with `UserNotOwnedByPartner` | The user was not created by your application | Only act on users you registered |
| `409` | Duplicate username, email, phone, or reference | Use unique values |
| `422` | Invalid or missing field, or consent | Inspect the error body for the offending field |

## Go-live checklist

* Consent documents fetched per country at registration time, never hard-coded, with an accurate `signedAt` and the end user's own `clientIp`.
* `externalUserId` (the `sub` claim) and `partnerCorrelationId` stored against your own user record.
* Every item in the [partner security checklist](https://api-portal.etoro.com/partners/integration.md#security-checklist): NIT storage, expiry and IP restriction, egress allow-listing, and no personal data in logs or events.
* Silent login initiated only from the user's browser, with `partner-login-token` called only from your backend.
* A full dry run of Part A and Part B in a non-production environment, agreed with your etoro contact, before any customer sees it.

## Reference

> **[Partners API reference](https://api-portal.etoro.com/partners-api-reference)**
>
> Every Partners API endpoint, with the scopes each one requires.

> **[Partner integration flow](https://api-portal.etoro.com/partners/integration.md)**
>
> Non-interactive tokens, account association, scopes, and event reporting.

> **[SSO with OAuth 2.0](https://api-portal.etoro.com/core/getting-started/sso-oauth.md)**
>
> The standard sign-in flow for users who already hold an etoro account.

> **[API keys and headers](https://api-portal.etoro.com/core/getting-started/authentication.md)**
>
> The `x-api-key` / `x-user-key` pair and request header format.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.