# Authentication & API Keys Deep Dive

Master eToro API authentication — API key management, secure storage, token refresh patterns, and troubleshooting common auth errors.

---


## Overview

Every request to the eToro API must be authenticated. This guide covers the authentication model in depth — how keys work, how to store them safely, how to handle token expiration, and what to do when things go wrong.

> If you haven't set up API access yet, start with the [Getting Started](/learn/getting-started-with-etoro-api-v2) guide first.

## Authentication Model

The eToro API uses a two-key system:

| Key | Header | Purpose |
|-----|--------|---------|
| **Public API Key** | `x-api-key` | Identifies your application |
| **User Key** | `x-user-key` | Identifies the acting user |

For OAuth-based authentication (used in partner and enterprise integrations), please contact the eToro partnerships team.

Both headers are required on every request. Unlike OAuth token flows where tokens expire frequently, eToro API keys are long-lived credentials tied to your account.

```javascript skip-test
const headers = {
  "x-api-key": process.env.ETORO_API_KEY,
  "x-user-key": process.env.ETORO_USER_KEY,
  "Content-Type": "application/json",
};

const response = await fetch("https://public-api.etoro.com/api/v1/market-data/instruments", {
  headers,
});
```

## Generating API Keys

1. Log in to [api-portal.etoro.com](https://api-portal.etoro.com)
2. Navigate to **Settings → Trading → API Key Management**
3. Click **Create New Key**
4. Copy both the Public API Key and User Key immediately — the User Key is only shown once

> **Warning:** Treat your User Key like a password. Anyone with both keys can execute trades on your behalf.

## Secure Key Storage

Never hardcode API keys in your source code. Use environment variables or a secrets manager.

### Environment Variables

```bash skip-test
# .env file (add to .gitignore!)
ETORO_API_KEY=your_public_api_key_here
ETORO_USER_KEY=your_user_key_here
ETORO_ENVIRONMENT=demo
```

```javascript skip-test
import "dotenv/config";

const config = {
  apiKey: process.env.ETORO_API_KEY,
  userKey: process.env.ETORO_USER_KEY,
  baseUrl: "https://public-api.etoro.com/api/v1",
  executionPrefix:
    process.env.ETORO_ENVIRONMENT === "demo"
      ? "trading/execution/demo"
      : "trading/execution",
};

if (!config.apiKey || !config.userKey) {
  throw new Error("Missing API credentials. Check your .env file.");
}
```

### Using a Secrets Manager (Production)

For production deployments, use a secrets manager instead of `.env` files:

```javascript skip-test
// AWS Secrets Manager example
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";

async function getCredentials() {
  const client = new SecretsManagerClient({ region: "us-east-1" });
  const response = await client.send(
    new GetSecretValueCommand({ SecretId: "etoro-api-keys" })
  );
  return JSON.parse(response.SecretString);
}
```

## Building a Reusable API Client

Wrap authentication logic in a client class to avoid repeating headers:

```javascript skip-test
class EtoroClient {
  constructor({ apiKey, userKey, environment = "demo" }) {
    this.baseUrl = "https://public-api.etoro.com/api/v1";
    this.headers = {
      "x-api-key": apiKey,
      "x-user-key": userKey,
      "Content-Type": "application/json",
    };
  }

  async request(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    const response = await fetch(url, {
      ...options,
      headers: { ...this.headers, ...options.headers },
    });

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new ApiError(response.status, error.message || response.statusText);
    }

    return response.json();
  }

  async getInstruments(params = {}) {
    const query = new URLSearchParams(params).toString();
    return this.request(`/market-data/instruments${query ? `?${query}` : ""}`);
  }

  async getPortfolio() {
    return this.request("/trading/info/real/portfolio");
  }
}

class ApiError extends Error {
  constructor(status, message) {
    super(`API Error ${status}: ${message}`);
    this.status = status;
  }
}
```

Usage:

```javascript skip-test
const client = new EtoroClient({
  apiKey: process.env.ETORO_API_KEY,
  userKey: process.env.ETORO_USER_KEY,
});

const instruments = await client.getInstruments({ type: "stock" });
console.log(`Found ${instruments.length} stocks`);
```

## Rate Limiting and Retry Logic

The eToro API enforces rate limits. When exceeded, the API returns `429 Too Many Requests` with a `Retry-After` header. See the [Rate Limits documentation](https://api-portal.etoro.com/getting-started/rate-limits) for current limits and the [Rate Limits & 429 Handling Playbook](/learn/rate-limits-and-429-handling) for implementation patterns.

Implement exponential backoff to handle rate limits gracefully:

```javascript skip-test
async function requestWithRetry(fn, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (error.status === 429 && attempt < maxRetries) {
        const delay = Math.pow(2, attempt) * 1000 + Math.random() * 500;
        console.log(`Rate limited. Retrying in ${Math.round(delay)}ms...`);
        await new Promise((r) => setTimeout(r, delay));
        continue;
      }
      throw error;
    }
  }
}

// Usage
const data = await requestWithRetry(() => client.getInstruments());
```

## Common Authentication Errors

| Status | Error | Cause | Fix |
|--------|-------|-------|-----|
| `401` | Unauthorized | Missing or invalid API key | Verify `x-api-key` header is set correctly |
| `401` | Invalid user key | Wrong or expired user key | Regenerate user key at api-portal.etoro.com |
| `403` | Forbidden | Key lacks permission for this endpoint | Check your API key scopes |
| `403` | KYC required | Real trading requires identity verification | Complete KYC on etoro.com |
| `429` | Too Many Requests | Rate limit exceeded | Implement backoff (see above) |

### Debugging 401 Errors

```javascript skip-test
async function debugAuth(apiKey, userKey) {
  const response = await fetch(
    "https://public-api.etoro.com/api/v1/market-data/instruments?limit=1",
    {
      headers: {
        "x-api-key": apiKey,
        "x-user-key": userKey,
      },
    }
  );

  console.log("Status:", response.status);
  console.log("Headers:", Object.fromEntries(response.headers));

  if (!response.ok) {
    const body = await response.text();
    console.log("Error body:", body);
  } else {
    console.log("Authentication successful!");
  }
}
```

## Key Rotation Best Practices

1. **Rotate keys regularly** — Generate new keys every 90 days
2. **Use separate keys per environment** — Demo and production should use different key pairs
3. **Revoke old keys immediately** — After rotation, delete the previous key from api-portal.etoro.com
4. **Monitor for unauthorized usage** — Log all API calls and alert on unexpected patterns
5. **Never share keys across teams** — Each developer or service should have its own key pair

## Next Steps

- [Building an Algo Trading Bot](/learn/building-an-algo-trading-bot) — Put your authenticated client to work
- [Real-Time Market Data via WebSocket](/learn/real-time-market-data-websocket) — Stream live data with authenticated connections
- [API Reference](https://api-portal.etoro.com/api-reference) — Full endpoint documentation
