UNPKG

onairos

Version:

The Onairos Library is a collection of functions that enable Applications to connect and communicate data with Onairos Identities via User Authorization. Integration for developers is seamless, simple and effective for all applications. LLM SDK capabiliti

525 lines (406 loc) 15.3 kB
# Onairos SDK API Changes - Developer Guide **Version:** 2026-04-24 **Status:** Live **Training status URL:** Prefer **`GET https://api2.onairos.uk/training/status`** (Bearer token). Legacy alias **`/mobile-training/status`** is identical. With `environment: 'development'`, the SDK uses **`https://dev-api.onairos.uk`** as the API base; the backend’s `training.statusUrl` in the authorize response is authoritative—always poll that URL if present. --- ## Summary of Changes This document outlines the API response changes and new background training feature for developers integrating with Onairos. --- ## 1. API Response Format Changes ### Authorization Response (getAPIurl / getAPIurlMobile) **OLD Format:** ```json { "apiUrl": "https://api2.onairos.uk/combinedInference", "token": "eyJ...", "userData": { "email": "user@example.com", "username": "johndoe", "connectedPlatforms": ["linkedin", "youtube"], "isReturningUser": true } } ``` **NEW Format:** ```json { "apiUrl": "https://api2.onairos.uk/combinedInference", "token": "eyJ...", "authorizedData": { "traits": true, "personality": true, "llmData": false }, "training": { "ready": true, "statusUrl": "https://api2.onairos.uk/training/status", "pollIntervalMs": 5000 } } ``` ### Key Changes: | Old Field | New Field | Notes | |-----------|-----------|-------| | `userData.email` | **REMOVED** | Security: No PII exposed | | `userData.username` | **REMOVED** | Security: No PII exposed | | `userData.connectedPlatforms` | **REMOVED** | Available in traits response | | `userData.isReturningUser` | **REMOVED** | Use `training.ready` instead | | *(new)* | `authorizedData` | What data types user authorized | | *(new)* | `training.ready` | `true` = fetch now, `false` = poll first | | *(new)* | `training.statusUrl` | Endpoint to check training progress | | *(new)* | `training.pollIntervalMs` | Recommended polling interval | --- ## 2. Traits/Data Response Changes ### Data Response (combinedInference, traitsOnly, etc.) **NEW Format:** ```json { "InferenceResult": { "output": [[0.8, 0.2]] }, "traits": { "positive_traits": { "Curious": 92, "Analytical": 88 }, "traits_to_improve": { "Patience": 45 }, "user_summary": "You're a thoughtful technologist...", "top_traits_explanation": "Your engagement patterns show...", "archetype": "Strategic Thinker", "nudges": [ { "text": "Try journaling a decision you're mulling over" } ] }, "userProfile": { "user_summary": "You're a thoughtful technologist...", "top_traits_explanation": "Your engagement patterns show...", "archetype": "Strategic Thinker", "nudges": [...] }, "connectedPlatforms": ["linkedin", "youtube"] } ``` ### Key Additions: | Field | Description | |-------|-------------| | `traits.user_summary` | 2-3 paragraph personality summary | | `traits.top_traits_explanation` | Why these traits were identified | | `traits.archetype` | 2-3 word personality archetype | | `traits.nudges` | Personalized action suggestions | | `userProfile` | Convenient access to profile fields | --- ## 3. Background Training Mode ### Overview When a user authorizes your app, their traits may need to be generated (training). You have two options: 1. **Wait Mode** (default): User sees loading screen while training completes 2. **Background Mode**: SDK returns immediately, you poll for completion ### Web npm SDK (`backgroundLoadData`) On **`OnairosButton`**, set **`backgroundLoadData={true}`** with **`autoFetch={true}`** so that after connections/PIN/consent the SDK **skips the built-in training loading screen**, navigates to the data-request step, and completes with **`onComplete({ apiUrl, token, training, authorizedData, ... })`** while traits may still be generating. The SDK starts training in the background (socket **`start-training`**) so status polling is meaningful. Your app should: 1. Read **`training.ready`**. If `true`, **`POST apiUrl`** with `Authorization: Bearer <token>`. 2. If `false`, call **`pollTrainingStatus`** from **`onairos`** (or your own loop) on **`training.statusUrl`** until ready, then **`POST apiUrl`**. See **`docs/CROSS_SDK_PARITY.md`** and **`docs/ONAIROS_BUTTON_PROPS.md`** for the full matrix with **`autoFetch`**. ### When to Use Background Mode - User experience is time-sensitive - You want to show your app UI immediately - You'll fetch traits later (e.g., on next screen) --- ## 4. Implementation Guide ### Step 1: Check if Traits Are Ready After authorization, check `training.ready`: ```javascript const authResult = await onairosSDK.authorize(); if (authResult.training.ready) { // Traits exist - fetch immediately const userData = await fetchTraits(authResult.apiUrl, authResult.token); displayUserData(userData); } else { // Training in progress - poll for completion showLoadingState(); const userData = await pollForTraits(authResult); displayUserData(userData); } ``` ### Step 2: Fetch Traits (When Ready) ```javascript async function fetchTraits(apiUrl, token) { const response = await fetch(apiUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`Failed to fetch traits: ${response.status}`); } return await response.json(); } ``` ### Step 3: Poll for Training Completion (When Not Ready) Use each response’s **`polling.recommendedIntervalMs`** when present; otherwise fall back to **`training.pollIntervalMs`** from the authorize payload. On **HTTP 429**, honor **`Retry-After`** (header or seconds) and any **`recommendedIntervalMs`** / **`retryAfter`** in the JSON body before retrying. ```javascript async function pollForTraits(authResult) { const { apiUrl, token, training } = authResult; const { statusUrl, pollIntervalMs } = training; const maxWaitMs = 300000; // 5 minutes max const startTime = Date.now(); while (Date.now() - startTime < maxWaitMs) { const res = await fetch(statusUrl, { headers: { 'Authorization': `Bearer ${token}` } }); if (res.status === 429) { const errBody = await res.json().catch(() => ({})); const retryAfterHeader = res.headers.get('Retry-After'); const waitSec = retryAfterHeader ? parseInt(retryAfterHeader, 10) : (errBody.retryAfter ?? 5); await sleep(Math.max((waitSec || 5) * 1000, errBody.recommendedIntervalMs || 0)); continue; } const status = await res.json(); // Update UI with progress (optional) if (status.progress) { updateProgressBar(status.progress.progressPercent); updateETA(`${status.progress.remainingSeconds}s remaining`); updateMessage(status.progress.message); } // Check if training complete if (!status.trainingStatus.isCurrentlyTraining) { // Training done! Fetch traits return await fetchTraits(apiUrl, token); } const waitMs = status.polling?.recommendedIntervalMs ?? pollIntervalMs ?? 5000; await sleep(waitMs); } throw new Error('Training timeout - please try again'); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } ``` --- ## 5. Training Status Response ### Endpoint ``` GET https://api2.onairos.uk/training/status Authorization: Bearer <token> ``` Legacy path (same behavior): `GET https://api2.onairos.uk/mobile-training/status` ### Response (During Training) ```json { "success": true, "trainingStatus": { "isCurrentlyTraining": true, "canTrainAgain": false, "lastTrainingDate": "2026-03-09T05:30:00Z", "minutesSinceLastTraining": 2 }, "progress": { "progressPercent": 45, "elapsedSeconds": 54, "estimatedTotalSeconds": 120, "remainingSeconds": 66, "stage": "processing", "message": "Analyzing your data..." }, "polling": { "recommendedIntervalMs": 3000, "maxPollTimeMs": 300000 } } ``` ### Response (Training Complete) ```json { "success": true, "trainingStatus": { "isCurrentlyTraining": false, "canTrainAgain": true, "lastTrainingDate": "2026-03-09T05:32:00Z" }, "progress": null, "polling": { "recommendedIntervalMs": 10000, "maxPollTimeMs": 300000 } } ``` ### Field Reference | Field | Type | Description | |-------|------|-------------| | `trainingStatus.isCurrentlyTraining` | boolean | `true` = still training, keep polling | | `trainingStatus.canTrainAgain` | boolean | Whether user can retrain | | `progress.progressPercent` | number | 0-100, completion percentage | | `progress.remainingSeconds` | number | Estimated seconds until complete | | `progress.message` | string | Human-readable status message | | `polling.recommendedIntervalMs` | number | How often to poll (ms) | --- ## 6. Rate Limits | Endpoint | Limit | Response if Exceeded | |----------|-------|---------------------| | `/training/status` (legacy: `/mobile-training/status`) | 30 requests/minute | `429 Too Many Requests` | If you receive a `429` response: ```json { "success": false, "error": "Too many requests. Please slow down polling.", "retryAfter": 60, "recommendedIntervalMs": 5000 } ``` Check the `Retry-After` header and wait before retrying. --- ## 7. Complete Example ### React/JavaScript ```javascript import { useState, useEffect } from 'react'; function OnairosIntegration() { const [userData, setUserData] = useState(null); const [loading, setLoading] = useState(false); const [progress, setProgress] = useState(null); const [error, setError] = useState(null); async function handleAuthorization(authResult) { setLoading(true); setError(null); try { if (authResult.training.ready) { // Traits ready - fetch immediately const data = await fetchTraits(authResult.apiUrl, authResult.token); setUserData(data); } else { // Training in progress - poll const data = await pollForTraits(authResult); setUserData(data); } } catch (err) { setError(err.message); } finally { setLoading(false); setProgress(null); } } async function fetchTraits(apiUrl, token) { const response = await fetch(apiUrl, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` } }); return response.json(); } async function pollForTraits(authResult) { const { apiUrl, token, training } = authResult; const maxWait = 300000; const start = Date.now(); while (Date.now() - start < maxWait) { const status = await fetch(training.statusUrl, { headers: { 'Authorization': `Bearer ${token}` } }).then(r => r.json()); // Update progress UI if (status.progress) { setProgress({ percent: status.progress.progressPercent, message: status.progress.message, eta: status.progress.remainingSeconds }); } // Check if done if (!status.trainingStatus.isCurrentlyTraining) { return await fetchTraits(apiUrl, token); } // Wait await new Promise(r => setTimeout(r, status.polling.recommendedIntervalMs)); } throw new Error('Training timeout'); } return ( <div> {loading && progress && ( <div className="progress"> <div className="progress-bar" style={{ width: `${progress.percent}%` }} /> <p>{progress.message}</p> <p>ETA: {progress.eta} seconds</p> </div> )} {userData && ( <div className="user-profile"> <h2>{userData.userProfile.archetype}</h2> <p>{userData.userProfile.user_summary}</p> <h3>Your Traits</h3> {Object.entries(userData.traits.positive_traits).map(([trait, score]) => ( <div key={trait}> <span>{trait}</span> <span>{score}%</span> </div> ))} </div> )} {error && <p className="error">{error}</p>} </div> ); } ``` ### Python ```python import time import requests def get_user_traits(auth_result): """ Get user traits, polling if training is in progress. """ api_url = auth_result['apiUrl'] token = auth_result['token'] training = auth_result['training'] headers = {'Authorization': f'Bearer {token}'} # If traits ready, fetch immediately if training['ready']: response = requests.post(api_url, headers=headers) return response.json() # Otherwise, poll for training completion status_url = training['statusUrl'] poll_interval = training['pollIntervalMs'] / 1000 # Convert to seconds max_wait = 300 # 5 minutes start_time = time.time() while time.time() - start_time < max_wait: # Check status status = requests.get(status_url, headers=headers).json() # Show progress if status.get('progress'): print(f"Progress: {status['progress']['progressPercent']}%") print(f"ETA: {status['progress']['remainingSeconds']}s") # Check if done if not status['trainingStatus']['isCurrentlyTraining']: response = requests.post(api_url, headers=headers) return response.json() # Wait before next poll time.sleep(status['polling']['recommendedIntervalMs'] / 1000) raise TimeoutError('Training timeout') # Usage auth_result = onairos_sdk.authorize() user_data = get_user_traits(auth_result) print(f"Archetype: {user_data['userProfile']['archetype']}") print(f"Summary: {user_data['userProfile']['user_summary']}") ``` --- ## 8. Migration Checklist If you were using the old API format: - [ ] Remove references to `userData.email` (no longer available) - [ ] Remove references to `userData.username` (no longer available) - [ ] Update to use `training.ready` instead of `userData.isReturningUser` - [ ] Implement polling logic for `training.ready === false` - [ ] Update trait parsing to use new `userProfile` structure - [ ] Handle rate limiting (429 responses) --- ## 9. FAQ ### Q: Why was email/username removed? **A:** For security and privacy. The user consented to share their traits with your app, not their email address. Use the internal user ID if you need to track users. ### Q: How long does training take? **A:** Typically 60-120 seconds for first-time users. The `progress.estimatedTotalSeconds` field provides a real-time estimate. ### Q: What if training fails? **A:** Check `trainingStatus.canTrainAgain`. If true, the user can re-authorize to retry training. ### Q: Can I skip polling and just wait? **A:** Yes, if your SDK is in "wait mode" (backgroundMode: false), the SDK handles this internally and returns traits directly. ### Q: What's in the `userProfile` object? **A:** - `user_summary`: 2-3 paragraph personality description - `top_traits_explanation`: Why these traits were identified - `archetype`: 2-3 word personality label (e.g., "Strategic Thinker") - `nudges`: Personalized suggestions/tips --- ## 10. Support For questions or issues: - Email: support@onairos.uk - Documentation: https://docs.onairos.uk - Status: https://status.onairos.uk