โ† Research

API Documentation

eToro 2FA API

OpenAPI 3.0 interactive documentation โ€” all endpoints with examples and cURL commands

๐Ÿ“‹ OpenAPI 3.0.3๐Ÿ”’ Bearer JWT๐ŸŒ https://api.etoro.comv1.0.0

๐Ÿ“‘ Endpoints

POST /2fa/totp/setupPOST /2fa/totp/verifyPOST /2fa/totp/validateDELETE /2fa/totp POST /2fa/passkey/register/optionsPOST /2fa/passkey/register/verifyPOST /2fa/passkey/authenticate/optionsPOST /2fa/passkey/authenticate/verify GET /2fa/passkey/credentialsDELETE /2fa/passkey/credentials/:idPOST /2fa/passkey/rename POST /2fa/backup-codes/generatePOST /2fa/backup-codes/verifyGET /2fa/statusPOST /2fa/recovery

๐Ÿ” Authentication

Setup/management endpoints use Authorization: Bearer {accessToken}. Login validation endpoints use X-Temp-Token: {tempToken} issued after password verification.

๐Ÿ“ก TOTP

POST/api/v1/2fa/totp/setupGenerate TOTP secret + QRโ–ธ

Auth: Bearer token | Rate: 3/hour

No request body. Returns TOTP secret and otpauth:// URI for QR code.

200 Response

{
  "secret": "JBSWY3DPEHPK3PXP",
  "qrUri": "otpauth://totp/eToro:user@email.com?secret=JBSWY3DPEHPK3PXP&issuer=eToro&algorithm=SHA1&digits=6&period=30",
  "algorithm": "SHA1",
  "digits": 6,
  "period": 30
}

409 TOTP_ALREADY_ENABLED

{ "error": "TOTP_ALREADY_ENABLED", "message": "TOTP is already configured" }

cURL

curl -X POST https://api.etoro.com/api/v1/2fa/totp/setup \
  -H "Authorization: Bearer eyJhbGci..."
POST/api/v1/2fa/totp/verifyVerify code & enable TOTPโ–ธ

Auth: Bearer token | Rate: 5/5min

Request

{ "code": "738204" }
FieldType
codestringREQUIRED โ€” 6-digit code from authenticator

200 Response

{
  "enabled": true,
  "method": "totp",
  "backupCodes": ["A1B2-C3D4","E5F6-G7H8","I9J0-K1L2","M3N4-O5P6","Q7R8-S9T0","U1V2-W3X4","Y5Z6-A7B8","C9D0-E1F2","G3H4-I5J6","K7L8-M9N0"]
}

cURL

curl -X POST https://api.etoro.com/api/v1/2fa/totp/verify \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{"code":"738204"}'
POST/api/v1/2fa/totp/validateValidate code during loginโ–ธ

Auth: X-Temp-Token | Rate: 5/5min

Request

{ "code": "738204" }

200 Response

{ "accessToken": "eyJhbGci...", "refreshToken": "eyJhbGci...", "expiresIn": 3600 }

Error Responses

401 { "error": "INVALID_TOTP_CODE", "message": "Invalid verification code", "attemptsRemaining": 4 }
401 { "error": "CODE_ALREADY_USED", "message": "This code has already been used" }
423 { "error": "ACCOUNT_LOCKED", "lockoutUntil": "2026-02-22T14:27:00Z" }
429 { "error": "RATE_LIMIT_EXCEEDED", "retryAfter": 300 }

cURL

curl -X POST https://api.etoro.com/api/v1/2fa/totp/validate \
  -H "X-Temp-Token: temp_2fa_abc123" \
  -H "Content-Type: application/json" \
  -d '{"code":"738204"}'
DELETE/api/v1/2fa/totpDisable TOTPโ–ธ

Auth: Bearer + 2FA | Rate: 2/hour

// Request
{ "code": "738204" }

// Response 200
{ "disabled": true, "method": null }

๐Ÿ”‘ Passkeys (WebAuthn/FIDO2)

POST/api/v1/2fa/passkey/register/optionsGet passkey registration options (discoverable credential)โ–ธ

Auth: Bearer | Rate: 3/hour | No body

Returns PublicKeyCredentialCreationOptions with residentKey: "required" for discoverable credentials (passwordless-ready). Excludes already-registered credentials via excludeCredentials.

200 Response

{
  "challenge": "dGVzdC1jaGFsbGVuZ2U...",
  "rp": { "name": "eToro", "id": "etoro.com" },
  "user": { "id": "MTIzNDU...", "name": "user@email.com", "displayName": "Yoni" },
  "pubKeyCredParams": [
    { "type": "public-key", "alg": -7 },
    { "type": "public-key", "alg": -257 }
  ],
  "timeout": 120000,
  "attestation": "none",
  "authenticatorSelection": {
    "residentKey": "required",
    "requireResidentKey": true,
    "userVerification": "preferred",
    "authenticatorAttachment": "platform"
  },
  "excludeCredentials": [
    { "id": "existing-cred-base64url", "type": "public-key", "transports": ["internal"] }
  ]
}

409 MAX_CREDENTIALS_REACHED

{ "error": "MAX_CREDENTIALS_REACHED", "message": "Maximum 10 passkeys registered" }

cURL

curl -X POST https://api.etoro.com/api/v1/2fa/passkey/register/options \
  -H "Authorization: Bearer eyJhbGci..."
POST/api/v1/2fa/passkey/register/verifyVerify & store passkey credentialโ–ธ

Request

{
  "attestation": {
    "id": "credential-id-base64url",
    "rawId": "Y3JlZGVudGlhbC1pZA...",
    "response": {
      "clientDataJSON": "eyJ0eXBl...",
      "attestationObject": "o2NmbXR...",
      "transports": ["internal", "hybrid"]
    },
    "type": "public-key",
    "clientExtensionResults": { "credProps": { "rk": true } },
    "authenticatorAttachment": "platform"
  },
  "deviceName": "iPhone 15 Pro"
}

200

{
  "registered": true,
  "credentialId": "credential-id-base64url",
  "deviceName": "iPhone 15 Pro",
  "deviceType": "multiDevice",
  "backupEligible": true,
  "backupState": true,
  "transports": ["internal", "hybrid"]
}

cURL

curl -X POST https://api.etoro.com/api/v1/2fa/passkey/register/verify \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{"attestation":{...},"deviceName":"iPhone 15 Pro"}'
POST/api/v1/2fa/passkey/authenticate/optionsGet passkey auth options (2FA or passwordless)โ–ธ

Auth: X-Temp-Token (2FA mode) or None (passwordless) | No body

2FA mode: Pass X-Temp-Token header โ†’ returns allowCredentials with user's registered passkeys.
Passwordless mode: No auth โ†’ returns empty allowCredentials for discoverable credential flow.

200 Response (2FA mode)

{
  "challenge": "YXV0aC1jaGFsbGVuZ2U...",
  "allowCredentials": [
    { "id": "cred-1-base64url", "type": "public-key", "transports": ["internal", "hybrid"] },
    { "id": "cred-2-base64url", "type": "public-key", "transports": ["usb"] }
  ],
  "timeout": 120000,
  "userVerification": "preferred",
  "rpId": "etoro.com"
}

200 Response (Passwordless mode)

{
  "challenge": "cGFzc3dvcmRsZXNz...",
  "allowCredentials": [],
  "timeout": 120000,
  "userVerification": "required",
  "rpId": "etoro.com"
}

cURL

curl -X POST https://api.etoro.com/api/v1/2fa/passkey/authenticate/options \
  -H "X-Temp-Token: temp_2fa_abc123"
POST/api/v1/2fa/passkey/authenticate/verifyVerify passkey assertionโ–ธ

Request

{
  "assertion": {
    "id": "credential-id-base64url",
    "rawId": "Y3JlZGVudGlhbC1pZA...",
    "response": {
      "clientDataJSON": "eyJ0eXBl...",
      "authenticatorData": "SZYN5YgO...",
      "signature": "MEUCIQC...",
      "userHandle": "MTIzNDU..."
    },
    "type": "public-key",
    "clientExtensionResults": {},
    "authenticatorAttachment": "platform"
  }
}

200

{
  "accessToken": "eyJhbGci...",
  "refreshToken": "eyJhbGci...",
  "expiresIn": 3600,
  "credentialUsed": {
    "id": "credential-id-base64url",
    "deviceName": "iPhone 15 Pro"
  }
}

Error Responses

401 { "error": "WEBAUTHN_VERIFICATION_FAILED", "message": "Passkey verification failed" }
401 { "error": "CREDENTIAL_NOT_FOUND", "message": "No matching credential" }
401 { "error": "COUNTER_MISMATCH", "message": "Possible cloned authenticator detected" }

cURL

curl -X POST https://api.etoro.com/api/v1/2fa/passkey/authenticate/verify \
  -H "Content-Type: application/json" \
  -d '{"assertion":{...}}'
GET/api/v1/2fa/passkey/credentialsList registered passkeysโ–ธ

Auth: Bearer | Rate: 30/min

200 Response

{
  "credentials": [
    {
      "id": "cred-id-1",
      "deviceName": "iPhone 15 Pro",
      "deviceType": "multiDevice",
      "backupEligible": true,
      "backupState": true,
      "transports": ["internal", "hybrid"],
      "lastUsed": "2026-02-22T10:30:00Z",
      "createdAt": "2026-01-15T09:00:00Z"
    },
    {
      "id": "cred-id-2",
      "deviceName": "YubiKey 5",
      "deviceType": "singleDevice",
      "backupEligible": false,
      "backupState": false,
      "transports": ["usb", "nfc"],
      "lastUsed": "2026-02-20T14:00:00Z",
      "createdAt": "2026-02-01T11:00:00Z"
    }
  ],
  "maxCredentials": 10
}

cURL

curl https://api.etoro.com/api/v1/2fa/passkey/credentials \
  -H "Authorization: Bearer eyJhbGci..."
DELETE/api/v1/2fa/passkey/credentials/:idRemove a passkeyโ–ธ

Auth: Bearer + 2FA | Rate: 5/hour

200

{ "deleted": true, "credentialId": "cred-id-2", "remainingCredentials": 1 }

Error Responses

400 { "error": "CANNOT_DELETE_LAST_METHOD", "message": "Cannot delete last 2FA method" }
404 { "error": "CREDENTIAL_NOT_FOUND", "message": "Credential not found" }

cURL

curl -X DELETE https://api.etoro.com/api/v1/2fa/passkey/credentials/cred-id-2 \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "X-2FA-Code: 738204"
POST/api/v1/2fa/passkey/renameRename a passkeyโ–ธ

Auth: Bearer | Rate: 10/hour

Request

{ "credentialId": "cred-id-1", "deviceName": "Work Phone" }

200

{ "credentialId": "cred-id-1", "deviceName": "Work Phone", "updated": true }

cURL

curl -X POST https://api.etoro.com/api/v1/2fa/passkey/rename \
  -H "Authorization: Bearer eyJhbGci..." \
  -H "Content-Type: application/json" \
  -d '{"credentialId":"cred-id-1","deviceName":"Work Phone"}'

๐Ÿ›Ÿ Backup Codes

POST/api/v1/2fa/backup-codes/generateGenerate 10 backup codesโ–ธ

Auth: Bearer + 2FA | Rate: 1/day

// Request โ€” requires current 2FA verification
{ "currentCode": "738204" }

// Response 200
{
  "codes": ["A1B2-C3D4","E5F6-G7H8","I9J0-K1L2","M3N4-O5P6","Q7R8-S9T0","U1V2-W3X4","Y5Z6-A7B8","C9D0-E1F2","G3H4-I5J6","K7L8-M9N0"],
  "warning": "Store these codes securely. Previous codes are now invalid."
}
POST/api/v1/2fa/backup-codes/verifyUse a backup codeโ–ธ

Auth: X-Temp-Token | Rate: 3/15min

// Request
{ "code": "A1B2-C3D4" }

// Response 200
{ "accessToken": "eyJhbGci...", "refreshToken": "eyJhbGci...", "codesRemaining": 9 }

๐Ÿ“Š Status & Recovery

GET/api/v1/2fa/statusGet user's 2FA configurationโ–ธ

Auth: Bearer | Rate: 30/min

{
  "enabled": true,
  "primaryMethod": "totp",
  "totp": { "enabled": true, "configuredAt": "2026-02-22T14:00:00Z" },
  "webauthn": {
    "enabled": true,
    "credentials": [{
      "id": "cred-id-1", "deviceName": "iPhone 15 Pro",
      "lastUsed": "2026-02-22T10:00:00Z", "createdAt": "2026-01-15T09:00:00Z", "backedUp": true
    }]
  },
  "backupCodes": { "remaining": 8, "generatedAt": "2026-02-22T14:00:00Z" },
  "recovery": { "email": "y***@etoro.com", "phone": "+972***1010" }
}
POST/api/v1/2fa/recoveryInitiate account recoveryโ–ธ

Auth: Email token | Rate: 1/24hr

// Request
{ "email": "user@email.com", "identityProof": "base64-id-photo", "reason": "lost_device" }

// Response 202
{
  "recoveryId": "rec-uuid-123", "status": "pending_review",
  "estimatedTime": "24-48 hours",
  "message": "Recovery request submitted. Check email for next steps."
}

โš ๏ธ Error Response Schema

{
  "error": "ERROR_CODE",        // Machine-readable error code
  "message": "Human readable",  // User-friendly message
  "attemptsRemaining": 4,       // Optional: remaining attempts
  "retryAfter": 300,            // Optional: seconds until retry
  "lockoutUntil": "ISO-8601"    // Optional: lockout expiry
}
CodeHTTPDescription
INVALID_TOTP_CODE401TOTP code doesn't match
CODE_ALREADY_USED401Replay attack detected
INVALID_BACKUP_CODE401Backup code not found/used
WEBAUTHN_VERIFICATION_FAILED401WebAuthn assertion invalid
TOTP_ALREADY_ENABLED409TOTP already configured
MAX_CREDENTIALS_REACHED409Max 5 passkeys
ACCOUNT_LOCKED423Too many failures
RATE_LIMIT_EXCEEDED429Throttled
ENCRYPTION_ERROR500Vault/HSM failure
โ† Research ยท Architecture ยท Server SpecKit ยท Client SpecKit