Authentication

Secure your API requests with JWT tokens for interactive use or API keys for programmatic access. This guide covers every authentication endpoint with copy-pasteable examples.

Overview

Two authentication methods, one consistent security model

JWT Authentication

Session-based authentication using access tokens and refresh tokens. Access tokens are short-lived (15 minutes) and refresh tokens are stored as secure HTTP-only cookies. Ideal for web applications and interactive use.

Web / Interactive

API Key Authentication

Long-lived, scoped keys for server-to-server communication. Each key has specific permissions (scopes) and can be revoked independently. Ideal for scripts, CI/CD, and backend integrations.

Programmatic / Server

JWT Authentication

Sign up, log in, refresh tokens, and log out using session-based JWT flow

POST
/auth/signup

Create a new user account. Returns the created user profile on success.

Request Body
json
{
  "email": "investor@example.com",
  "password": "SecureP@ssw0rd!",
  "full_name": "Jane Doe"
}
Response
201 Created
json
{
  "id": "usr_a1b2c3d4e5",
  "email": "investor@example.com",
  "full_name": "Jane Doe",
  "created_at": "2025-01-15T10:30:00Z"
}
Parameters
FieldTypeDescription
emailstringValid email address (required)
passwordstringMinimum 8 characters with mixed case (required)
full_namestringDisplay name for the account (required)
curl
bash
$ curl -X POST https://api.portfolioopt.in/auth/signup \
  -H "Content-Type: application/json" \
  -d '{
    "email": "investor@example.com",
    "password": "SecureP@ssw0rd!",
    "full_name": "Jane Doe"
  }'
Python
python
# Sign up a new user
import requests

response = requests.post(
    "https://api.portfolioopt.in/auth/signup",
    json={
        "email": "investor@example.com",
        "password": "SecureP@ssw0rd!",
        "full_name": "Jane Doe",
    },
)
print(response.json())

POST
/auth/login

Authenticate with email and password. Returns an access token in the response body and sets refresh_token and csrf_token as secure HTTP-only cookies.

Request Body
json
{
  "email": "investor@example.com",
  "password": "SecureP@ssw0rd!"
}
Response
200 OK
json
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "bearer",
  "expires_in": 900
}

The response also sets two cookies: refresh_token (HTTP-only, secure) and csrf_token (readable by JavaScript for CSRF protection).

curl
bash
$ curl -X POST https://api.portfolioopt.in/auth/login \
  -H "Content-Type: application/json" \
  -c cookies.txt \
  -d '{
    "email": "investor@example.com",
    "password": "SecureP@ssw0rd!"
  }'
Python
python
# Log in and persist session cookies
import requests

session = requests.Session()
response = session.post(
    "https://api.portfolioopt.in/auth/login",
    json={
        "email": "investor@example.com",
        "password": "SecureP@ssw0rd!",
    },
)
data = response.json()
access_token = data["access_token"]

# Use the token for subsequent requests
headers = {"Authorization": f"Bearer {access_token}"}
print(data)

POST
/auth/refresh

Exchange a valid refresh token (sent as a cookie) for a new access token. Requires the X-CSRF-Token header for CSRF protection.

Response
200 OK
json
{
  "access_token": "eyJhbGciOiJIUzI1NiIs...",
  "token_type": "bearer",
  "expires_in": 900
}
curl
bash
$ curl -X POST https://api.portfolioopt.in/auth/refresh \
  -H "X-CSRF-Token: <csrf_token_from_cookie>" \
  -b cookies.txt \
  -c cookies.txt
Python
python
# Refresh the access token using the session cookies
csrf_token = session.cookies.get("csrf_token")

response = session.post(
    "https://api.portfolioopt.in/auth/refresh",
    headers={"X-CSRF-Token": csrf_token},
)
new_token = response.json()["access_token"]
print(f"New access token: {new_token[:20]}...")

POST
/auth/logout

Invalidate the current session. Clears the refresh token and CSRF cookies. Requires the X-CSRF-Token header.

Response
200 OK
json
{
  "detail": "Successfully logged out"
}
curl
bash
$ curl -X POST https://api.portfolioopt.in/auth/logout \
  -H "X-CSRF-Token: <csrf_token_from_cookie>" \
  -b cookies.txt
Python
python
# Log out and clear the session
csrf_token = session.cookies.get("csrf_token")

response = session.post(
    "https://api.portfolioopt.in/auth/logout",
    headers={"X-CSRF-Token": csrf_token},
)
print(response.json())  # {"detail": "Successfully logged out"}

API Key Authentication

Create and manage long-lived API keys for programmatic access

POST
/auth/api-clients

Register a new API client application. Each client can have multiple API keys. Requires JWT authentication.

Request Body
json
{
  "name": "My Trading Bot",
  "description": "Automated portfolio rebalancing service"
}
Response
201 Created
json
{
  "id": "client_x9y8z7w6",
  "name": "My Trading Bot",
  "description": "Automated portfolio rebalancing service",
  "created_at": "2025-01-15T12:00:00Z"
}
curl
bash
$ curl -X POST https://api.portfolioopt.in/auth/api-clients \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "My Trading Bot",
    "description": "Automated portfolio rebalancing service"
  }'
Python
python
# Create an API client
response = requests.post(
    "https://api.portfolioopt.in/auth/api-clients",
    headers={"Authorization": f"Bearer {access_token}"},
    json={
        "name": "My Trading Bot",
        "description": "Automated portfolio rebalancing service",
    },
)
client = response.json()
print(f"Client ID: {client['id']}")

POST
/auth/api-keys

Generate a new API key for an existing client. You can assign specific scopes to control what the key can access.

Request Body
json
{
  "client_id": "client_x9y8z7w6",
  "name": "Production Key",
  "scopes": ["jobs:read", "jobs:write"]
}
Available Scopes
ScopeDescription
jobs:readView job status, results, and download reports
jobs:writeSubmit new optimization jobs and manage existing ones
Response
201 Created
json
{
  "id": "key_m1n2o3p4",
  "name": "Production Key",
  "key": "po_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  "scopes": ["jobs:read", "jobs:write"],
  "created_at": "2025-01-15T12:05:00Z"
}

Important: The key field is only returned once at creation time. Store it securely immediately -- you will not be able to retrieve it again.

curl
bash
$ curl -X POST https://api.portfolioopt.in/auth/api-keys \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "client_x9y8z7w6",
    "name": "Production Key",
    "scopes": ["jobs:read", "jobs:write"]
  }'
Python
python
# Generate an API key with scoped permissions
response = requests.post(
    "https://api.portfolioopt.in/auth/api-keys",
    headers={"Authorization": f"Bearer {access_token}"},
    json={
        "client_id": client["id"],
        "name": "Production Key",
        "scopes": ["jobs:read", "jobs:write"],
    },
)
api_key_data = response.json()

# IMPORTANT: Save this key securely -- it is only shown once!
api_key = api_key_data["key"]
print(f"API Key: {api_key}")

Using API Keys

Once you have an API key, include it in your requests using either of these two header formats:

Option 1: Authorization Header (Recommended)
http
Authorization: Bearer po_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
Option 2: X-API-Key Header
http
X-API-Key: po_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
curl
bash
$ curl https://api.portfolioopt.in/jobs \
  -H "Authorization: Bearer po_live_a1b2c3d4..."
Python
python
# Using an API key for all requests
import requests

API_KEY = "po_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

# Option 1: Authorization header
response = requests.get(
    "https://api.portfolioopt.in/jobs",
    headers={"Authorization": f"Bearer {API_KEY}"},
)

# Option 2: X-API-Key header
response = requests.get(
    "https://api.portfolioopt.in/jobs",
    headers={"X-API-Key": API_KEY},
)

print(response.json())

Password Reset

Self-service password recovery via email verification

POST
/auth/password/reset/request

Request a password reset email. A one-time reset token will be sent to the provided email address if it is associated with an account.

Request Body
json
{
  "email": "investor@example.com"
}
Response
200 OK
json
{
  "detail": "If the email exists, a reset link has been sent"
}

The response is intentionally identical whether or not the email exists, to prevent account enumeration.

curl
bash
$ curl -X POST https://api.portfolioopt.in/auth/password/reset/request \
  -H "Content-Type: application/json" \
  -d '{"email": "investor@example.com"}'
Python
python
# Request a password reset
response = requests.post(
    "https://api.portfolioopt.in/auth/password/reset/request",
    json={"email": "investor@example.com"},
)
print(response.json())

POST
/auth/password/reset/confirm

Confirm the password reset using the token received via email. Sets the new password and invalidates the reset token.

Request Body
json
{
  "token": "rst_a1b2c3d4e5f6...",
  "new_password": "NewSecureP@ss!"
}
Response
200 OK
json
{
  "detail": "Password has been reset successfully"
}
curl
bash
$ curl -X POST https://api.portfolioopt.in/auth/password/reset/confirm \
  -H "Content-Type: application/json" \
  -d '{
    "token": "rst_a1b2c3d4e5f6...",
    "new_password": "NewSecureP@ss!"
  }'
Python
python
# Confirm the password reset
response = requests.post(
    "https://api.portfolioopt.in/auth/password/reset/confirm",
    json={
        "token": "rst_a1b2c3d4e5f6...",
        "new_password": "NewSecureP@ss!",
    },
)
print(response.json())

Security Best Practices

Recommendations for keeping your integration secure

  • 1Never expose API keys in client-side code. API keys should only be used in server-side applications, backend services, or CI/CD pipelines. Never embed them in frontend JavaScript, mobile apps, or public repositories.
  • 2Use the principle of least privilege. When creating API keys, assign only the scopes your application actually needs. A read-only dashboard should use jobs:read only.
  • 3Rotate keys regularly. Create new API keys periodically and revoke old ones. This limits the impact of any key that may have been compromised.
  • 4Store secrets in environment variables. Use environment variables or a secrets manager (e.g., AWS Secrets Manager, HashiCorp Vault) instead of hardcoding credentials in your source code.
  • 5Always use HTTPS. All API requests must be made over HTTPS. The API rejects plaintext HTTP connections to prevent credentials from being transmitted in the clear.
  • 6Handle token refresh gracefully. Implement automatic token refresh in your application. When you receive a 401 response, attempt a silent refresh before prompting the user to re-authenticate.
  • 7Include CSRF tokens on state-changing requests. When using JWT authentication from a browser, always include the X-CSRF-Token header on POST, PUT, PATCH, and DELETE requests to prevent cross-site request forgery.