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). The refresh token is a secure HTTP-only cookie that rotates on every refresh, and a session ends 90 days after sign-in however often it is refreshed. Ideal for web applications and interactive use.
API Key Authentication
Long-lived, scoped keys for server-to-server communication. Each key has scopes and can be revoked independently. Keys reach the optimization job endpoints; Monte Carlo, standalone backtests, comparisons, tags and account management need a signed-in session. 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 and send a verification email. The account can sign in at once, but most endpoints answer 403 email_not_verified until the address is confirmed with POST /auth/email/verify.
Request Body
{
"email": "investor@example.com",
"password": "SecureP@ssw0rd!",
"display_name": "Jane Doe"
}Response 200 OK
{
"ok": true,
"user_id": "3f2b8c1e-6a4d-4b7e-9c2f-1d5e8a7b6c4d"
}An email that is already registered returns 409 email already registered.
Parameters
emailstringValid email address (required)passwordstringAt least 8 characters with a lowercase letter, an uppercase letter and a digit, and not a common password (required)display_namestringDisplay name for the account (optional)curl
$ curl -X POST https://api.foliolab.ai/auth/signup \ -H "Content-Type: application/json" \ -d '{ "email": "investor@example.com", "password": "SecureP@ssw0rd!", "display_name": "Jane Doe" }'
Python
# Sign up a new user import requests response = requests.post( "https://api.foliolab.ai/auth/signup", json={ "email": "investor@example.com", "password": "SecureP@ssw0rd!", "display_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). A wrong email or password returns 401 invalid credentials. Google sign-in starts at GET /auth/google and returns to the web app with the same cookies.
curl
$ curl -X POST https://api.foliolab.ai/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.foliolab.ai/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, which must equal the csrf_token cookie. The refresh token and the CSRF token both rotate, so store the new cookies.
Response 200 OK
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 900
}curl
$ curl -X POST https://api.foliolab.ai/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.foliolab.ai/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
Revoke the current session. Requires the X-CSRF-Token header.
Response 200 OK
{
"ok": true
}curl
$ curl -X POST https://api.foliolab.ai/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.foliolab.ai/auth/logout", headers={"X-CSRF-Token": csrf_token}, ) print(response.json()) # {"ok": true}
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 up to five active API keys, and an account can have up to ten clients. Requires JWT authentication. GET /auth/api-clients lists your clients, and PATCH /auth/api-clients/{client_id} renames one or sets is_disabled. DELETE /auth/api-clients/{client_id} revokes every key of the client and deletes it.
Request Body
{
"name": "My Trading Bot"
}Response 200 OK
{
"client_id": "7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"name": "My Trading Bot"
}curl
$ curl -X POST https://api.foliolab.ai/auth/api-clients \ -H "Authorization: Bearer <access_token>" \ -H "Content-Type: application/json" \ -d '{ "name": "My Trading Bot" }'
Python
# Create an API client response = requests.post( "https://api.foliolab.ai/auth/api-clients", headers={"Authorization": f"Bearer {access_token}"}, json={"name": "My Trading Bot"}, ) client = response.json() print(f"Client ID: {client['client_id']}")
POST/auth/api-keys
Generate a new API key for an existing client. You can assign scopes to control what the key can access. Omit scopes to get jobs:read and jobs:write. GET /auth/api-keys?client_id=... lists a client's keys by prefix, PATCH /auth/api-keys/{api_key_id} revokes a key, and DELETE /auth/api-keys/{api_key_id} deletes it.
Request Body
{
"client_id": "7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f",
"scopes": ["jobs:read", "jobs:write"]
}Available Scopes
jobs:readView job status, history, results, configurations and artifactsjobs:writeSubmit, rename, cancel and delete optimization jobs and request PDF reports. Includes jobs:readjobs:* or *Every jobs scopeResponse 200 OK
{
"api_key_id": "9e8d7c6b-5a4f-4e3d-2c1b-0a9f8e7d6c5b",
"api_key": "ipo_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"scopes": ["jobs:read", "jobs:write"]
}Important: The api_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.foliolab.ai/auth/api-keys \ -H "Authorization: Bearer <access_token>" \ -H "Content-Type: application/json" \ -d '{ "client_id": "7c1d2e3f-4a5b-4c6d-8e9f-0a1b2c3d4e5f", "scopes": ["jobs:read", "jobs:write"] }'
Python
# Generate an API key with scoped permissions response = requests.post( "https://api.foliolab.ai/auth/api-keys", headers={"Authorization": f"Bearer {access_token}"}, json={ "client_id": client["client_id"], "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["api_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. A key without the scope a route needs receives 403 API key lacks required scope, and a revoked key receives 401 API key revoked.
Option 1: Authorization Header (Recommended)
Authorization: Bearer ipo_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
Option 2: X-API-Key Header
X-API-Key: ipo_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
curl
$ curl "https://api.foliolab.ai/jobs/history?limit=10" \ -H "Authorization: Bearer ipo_live_a1b2c3d4..."
Python
# Using an API key for all requests import requests API_KEY = "ipo_live_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" # Option 1: Authorization header response = requests.get( "https://api.foliolab.ai/jobs/history", headers={"Authorization": f"Bearer {API_KEY}"}, ) # Option 2: X-API-Key header response = requests.get( "https://api.foliolab.ai/jobs/history", 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, valid for two hours, is sent to the provided email address if it is associated with an account.
Request Body
{
"email": "investor@example.com"
}Response 200 OK
{
"ok": true,
"detail": "If the account exists, a reset token was issued"
}The response is intentionally identical whether or not the email exists, to prevent account enumeration.
curl
$ curl -X POST https://api.foliolab.ai/auth/password/reset/request \ -H "Content-Type: application/json" \ -d '{"email": "investor@example.com"}'
Python
# Request a password reset response = requests.post( "https://api.foliolab.ai/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, invalidates the reset token, and signs out every existing session and connected AI assistant. The new password follows the same rules as sign-up. A signed-in user changes a password with POST /auth/password/change and current_password plus new_password.
Request Body
{
"token": "rst_a1b2c3d4e5f6...",
"new_password": "NewSecureP@ss!"
}Response 200 OK
{
"ok": true
}curl
$ curl -X POST https://api.foliolab.ai/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.foliolab.ai/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. Any POST, PUT, PATCH or DELETE request that carries the session cookies must include the
X-CSRF-Tokenheader, equal to thecsrf_tokencookie, or it receives403 CSRF validation failed. A request authenticated only by a bearer token or API key, with no cookies, needs no CSRF header.
Not investment advice. Past performance is not indicative of future results.