# Complete Login Flow This guide walks through the complete authentication flow for Freddy API, from initial login to making authenticated API requests. ## Overview Freddy uses a two-step authentication process: 1. **Login** - Validate credentials and receive email verification code 2. **Verify** - Confirm email code and receive JWT tokens 3. **Authenticate** - Use Bearer token for API requests 4. **Refresh** - Renew expired tokens without re-login ## Step 1: Login with Credentials Send user credentials to initiate the login process. **Endpoint:** `POST /v1/auth/login` ```python import requests response = requests.post( "https://api.aitronos.com/v1/auth/login", json={ "email_or_username": "user@example.com", "password": "your_secure_password" } ) data = response.json() email_key = data["email_key"] # Save this for step 2 ``` ```javascript const response = await fetch("https://api.aitronos.com/v1/auth/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email_or_username: "user@example.com", password: "your_secure_password" }) }); const data = await response.json(); const emailKey = data.email_key; // Save this for step 2 ``` **Response:** ```json { "success": true, "message": "Login credentials verified. Please check your email for verification code.", "email_key": "uuid-12345678-1234-1234-1234-123456789abc", "email": "user@example.com", "requires_verification": true, "next_step": "email_verification" } ``` The user receives a 4-digit verification code via email (expires in 5 minutes). ## Step 2: Verify Email Code Submit the verification code received via email. **Endpoint:** `POST /v1/auth/verify` ```python verification_code = 2290 # Code from email response = requests.post( "https://api.aitronos.com/v1/auth/verify", json={ "email_key": email_key, "verification_code": verification_code } ) data = response.json() access_token = data["token"] refresh_token = data["refreshToken"] # Store tokens securely ``` ```javascript const verificationCode = 2290; // Code from email const response = await fetch("https://api.aitronos.com/v1/auth/verify", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email_key: emailKey, verification_code: verificationCode }) }); const data = await response.json(); const accessToken = data.token; const refreshToken = data.refreshToken; // Store tokens securely ``` **Response:** ```json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "bearer", "expires_in": 86400, "user": { "id": "usr_1e60b7a014eb4f0c9f335ce11ebc0816", "email": "user@example.com", "verified": true } } ``` ## Step 3: Make Authenticated Requests Use the Bearer token in the Authorization header for all API requests. ```python headers = { "Authorization": f"Bearer {access_token}", "Content-Type": "application/json" } # Create a response response = requests.post( "https://api.aitronos.com/v1/responses", headers=headers, json={ "model": "gpt-4", "messages": [{"role": "user", "content": "Hello!"}] } ) ``` ```javascript const headers = { "Authorization": `Bearer ${accessToken}`, "Content-Type": "application/json" }; // Create a response const response = await fetch("https://api.aitronos.com/v1/responses", { method: "POST", headers: headers, body: JSON.stringify({ model: "gpt-4", messages: [{ role: "user", content: "Hello!" }] }) }); ``` ```bash curl -X POST "https://api.aitronos.com/v1/responses" \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4", "messages": [{"role": "user", "content": "Hello!"}] }' ``` ## Step 4: Refresh Expired Tokens When the access token expires (after 24 hours), use the refresh token to get a new one. **Endpoint:** `POST /v1/auth/refresh` ```python response = requests.post( "https://api.aitronos.com/v1/auth/refresh", json={"refresh_token": refresh_token} ) data = response.json() access_token = data["token"] # New access token ``` ```javascript const response = await fetch("https://api.aitronos.com/v1/auth/refresh", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refresh_token: refreshToken }) }); const data = await response.json(); const newAccessToken = data.token; ``` **Response:** ```json { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "device_id": "device-123" } ``` ## Complete Implementation Example Here's a complete Python class that handles the entire flow: ```python import requests from typing import Optional class FreddyAuth: def __init__(self): self.base_url = "https://api.aitronos.com/v1" self.access_token: Optional[str] = None self.refresh_token: Optional[str] = None def login(self, email_or_username: str, password: str) -> str: """Step 1: Login and get email_key""" response = requests.post( f"{self.base_url}/auth/login", json={ "email_or_username": email_or_username, "password": password } ) response.raise_for_status() data = response.json() return data["email_key"] def verify(self, email_key: str, verification_code: int): """Step 2: Verify email code and store tokens""" response = requests.post( f"{self.base_url}/auth/verify", json={ "email_key": email_key, "verification_code": verification_code } ) response.raise_for_status() data = response.json() self.access_token = data["token"] self.refresh_token = data["refreshToken"] return data def refresh(self): """Step 4: Refresh access token""" if not self.refresh_token: raise ValueError("No refresh token available") response = requests.post( f"{self.base_url}/auth/refresh", json={"refresh_token": self.refresh_token} ) response.raise_for_status() data = response.json() self.access_token = data["token"] return data def get_headers(self) -> dict: """Get headers for authenticated requests""" if not self.access_token: raise ValueError("Not authenticated. Call login() and verify() first.") return { "Authorization": f"Bearer {self.access_token}", "Content-Type": "application/json" } def create_response(self, model: str, messages: list): """Step 3: Make authenticated API request""" response = requests.post( f"{self.base_url}/responses", headers=self.get_headers(), json={"model": model, "messages": messages} ) # Auto-refresh on 401 if response.status_code == 401: self.refresh() response = requests.post( f"{self.base_url}/responses", headers=self.get_headers(), json={"model": model, "messages": messages} ) response.raise_for_status() return response.json() # Usage auth = FreddyAuth() # Step 1: Login email_key = auth.login("user@example.com", "password123") print(f"Check your email for verification code") # Step 2: Verify (user enters code from email) verification_code = int(input("Enter verification code: ")) auth.verify(email_key, verification_code) # Step 3: Make API calls result = auth.create_response( model="gpt-4", messages=[{"role": "user", "content": "Hello!"}] ) print(result) ``` ## JavaScript/TypeScript Implementation ```javascript class FreddyAuth { constructor() { this.baseUrl = "https://api.aitronos.com/v1"; this.accessToken = null; this.refreshToken = null; } async login(emailOrUsername, password) { const response = await fetch(`${this.baseUrl}/auth/login`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email_or_username: emailOrUsername, password: password }) }); if (!response.ok) throw new Error("Login failed"); const data = await response.json(); return data.email_key; } async verify(emailKey, verificationCode) { const response = await fetch(`${this.baseUrl}/auth/verify`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email_key: emailKey, verification_code: verificationCode }) }); if (!response.ok) throw new Error("Verification failed"); const data = await response.json(); this.accessToken = data.token; this.refreshToken = data.refreshToken; return data; } async refresh() { if (!this.refreshToken) { throw new Error("No refresh token available"); } const response = await fetch(`${this.baseUrl}/auth/refresh`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ refresh_token: this.refreshToken }) }); if (!response.ok) throw new Error("Token refresh failed"); const data = await response.json(); this.accessToken = data.token; return data; } getHeaders() { if (!this.accessToken) { throw new Error("Not authenticated"); } return { "Authorization": `Bearer ${this.accessToken}`, "Content-Type": "application/json" }; } async createResponse(model, messages) { let response = await fetch(`${this.baseUrl}/responses`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify({ model, messages }) }); // Auto-refresh on 401 if (response.status === 401) { await this.refresh(); response = await fetch(`${this.baseUrl}/responses`, { method: "POST", headers: this.getHeaders(), body: JSON.stringify({ model, messages }) }); } if (!response.ok) throw new Error("API request failed"); return response.json(); } } // Usage const auth = new FreddyAuth(); // Step 1: Login const emailKey = await auth.login("user@example.com", "password123"); console.log("Check your email for verification code"); // Step 2: Verify const verificationCode = 2290; // From email await auth.verify(emailKey, verificationCode); // Step 3: Make API calls const result = await auth.createResponse("gpt-4", [ { role: "user", content: "Hello!" } ]); console.log(result); ``` ## Token Lifecycle | Token Type | Expiration | Purpose | | --- | --- | --- | | Access Token | 24 hours | API authentication | | Refresh Token | 30 days | Renew access tokens | | Email Key | 5 minutes | Email verification session | | Verification Code | 5 minutes | One-time email code | ## Security Best Practices ### Token Storage **Web Applications:** - Store tokens in memory or secure HTTP-only cookies - Never use localStorage for production apps - Clear tokens on logout **Mobile/Desktop:** - Use secure storage (Keychain, KeyStore) - Encrypt tokens at rest - Clear on app uninstall ### Error Handling ```python def make_authenticated_request(auth, endpoint, data): try: response = requests.post( f"{auth.base_url}/{endpoint}", headers=auth.get_headers(), json=data ) if response.status_code == 401: # Token expired, refresh and retry auth.refresh() response = requests.post( f"{auth.base_url}/{endpoint}", headers=auth.get_headers(), json=data ) response.raise_for_status() return response.json() except requests.exceptions.HTTPError as e: if e.response.status_code == 401: print("Authentication failed. Please login again.") elif e.response.status_code == 403: print("Permission denied.") else: print(f"Request failed: {e}") raise ``` ## Common Issues ### Verification Code Expired If the code expires (5 minutes), request a new one: **Endpoint:** `POST /v1/auth/resend-email` ```python response = requests.post( "https://api.aitronos.com/v1/auth/resend-email", json={"email_key": email_key} ) ``` ### Invalid Credentials ```json { "success": false, "error": { "code": "INVALID_CREDENTIALS", "message": "Invalid email or password", "status": 401 } } ``` ### Too Many Login Attempts ```json { "success": false, "error": { "code": "TOO_MANY_LOGIN_ATTEMPTS", "message": "Too many login attempts. Please try again later", "status": 429, "details": { "retry_after": 300 } } } ``` ## Alternative: API Key Authentication For server-to-server or long-lived integrations, use API keys instead: ```python headers = { "api-key": "ak_your_api_key_here", "Content-Type": "application/json" } response = requests.post( "https://api.aitronos.com/v1/responses", headers=headers, json={"model": "gpt-4", "messages": [...]} ) ``` Get your API key from [Freddy Hub](https://freddy-hub.aitronos.com/freddy/api). ## Next Steps - [API Key Authentication](/docs/documentation/authentication) - Alternative authentication method - [Rate Limiting](/docs/documentation/rate-limiting) - Understand API limits - [Error Handling](/docs/documentation/error-handling) - Handle API errors gracefully - [API Reference](/docs/api-reference/authentication/login/login) - Detailed endpoint documentation