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.
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.
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
{
"email": "investor@example.com",
"password": "SecureP@ssw0rd!",
"full_name": "Jane Doe"
}Response 201 Created
{
"id": "usr_a1b2c3d4e5",
"email": "investor@example.com",
"full_name": "Jane Doe",
"created_at": "2025-01-15T10:30:00Z"
}Parameters
emailstringValid email address (required)passwordstringMinimum 8 characters with mixed case (required)full_namestringDisplay name for the account (required)curl
$ 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
# 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
{
"email": "investor@example.com",
"password": "SecureP@ssw0rd!"
}Response 200 OK
{
"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
$ 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
# 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
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 900
}curl
$ curl -X POST https://api.portfolioopt.in/auth/refresh \ -H "X-CSRF-Token: <csrf_token_from_cookie>" \ -b cookies.txt \ -c cookies.txt
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
{
"detail": "Successfully logged out"
}curl
$ curl -X POST https://api.portfolioopt.in/auth/logout \ -H "X-CSRF-Token: <csrf_token_from_cookie>" \ -b cookies.txt
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
{
"name": "My Trading Bot",
"description": "Automated portfolio rebalancing service"
}Response 201 Created
{
"id": "client_x9y8z7w6",
"name": "My Trading Bot",
"description": "Automated portfolio rebalancing service",
"created_at": "2025-01-15T12:00:00Z"
}curl
$ 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
# 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
{
"client_id": "client_x9y8z7w6",
"name": "Production Key",
"scopes": ["jobs:read", "jobs:write"]
}Available Scopes
jobs:readView job status, results, and download reportsjobs:writeSubmit new optimization jobs and manage existing onesResponse 201 Created
{
"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
$ 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
# 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)
Authorization: Bearer po_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
Option 2: X-API-Key Header
X-API-Key: po_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
curl
$ curl https://api.portfolioopt.in/jobs \ -H "Authorization: Bearer po_live_a1b2c3d4..."
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
{
"email": "investor@example.com"
}Response 200 OK
{
"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
$ curl -X POST https://api.portfolioopt.in/auth/password/reset/request \ -H "Content-Type: application/json" \ -d '{"email": "investor@example.com"}'
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
{
"token": "rst_a1b2c3d4e5f6...",
"new_password": "NewSecureP@ss!"
}Response 200 OK
{
"detail": "Password has been reset successfully"
}curl
$ 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
# 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:readonly. - 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-Tokenheader on POST, PUT, PATCH, and DELETE requests to prevent cross-site request forgery.