# Sortiri — Agent Onboarding Specification # https://sortiri.ai/agent.txt # Full HTML docs: https://sortiri.ai/agents # Short discovery: https://sortiri.ai/llms.txt # Agent instructions: https://sortiri.ai/agent-instructions.md # Action manifest: https://sortiri.ai/agents.json ## What Sortiri is Sortiri is an AI fitness coach mobile app (iOS). It helps users track meals, workouts, weigh-ins, and habits with personalized daily calorie, macro, and water targets. Agents can complete fitness setup on the web after interviewing the human; the human verifies email on the claim page, then downloads the app. ## Conversation flow (recommended) When a human sends you this spec, **ask them the setup questions first** — do not guess goals or body stats. Signup happens only after you return a claim link and they verify email. 1. Read this file and https://sortiri.ai/onboarding-draft.schema.json 2. **Interview the human** — ask each intake question below (same as the web wizard at /setup) 3. Compute macro targets from their answers (see Macro calculation) 4. POST /onboarding/draft with the answers object and the human's **email** at the top level (ask for it — do not guess) 5. Send the handoff message with claimUrl — **human must open the link and complete OTP themselves** Agents never complete email verification. Collect answers in chat; the human signs up at claim time. ### Intake questions to ask the human Ask in conversation (approximate order). Map answers to the `answers` fields in the draft API. - **Goal:** lose fat, build muscle, recomp, or maintain? → `goal` - **Meals per day:** 2, 3, 4, or 5? → `mealsPerDay` - **Workouts per week:** 2, 3, 4, or 5? → `workoutsPerWeek` - **Weigh-ins per week:** 1, 2, 3, or daily (7)? → `weighInsPerWeek` - **Gender:** male, female, or other? → `gender` - **If female:** pregnant? due date if yes; if not, track menstrual cycle? last period start + cycle length (26/28/30/32 days) if yes → `pregnancyModeEnabled`, `pregnancyDueDateStr`, `cycleTrackingEnabled`, `lastPeriodStartDateStr`, `averageCycleLengthDays` - **Units:** metric or imperial? → `units` - **Date of birth** (YYYY-MM-DD) → `dob` - **Current weight** and **height** (in their chosen units) → `currentWeight`, `height` (strings) - **Activity level:** sedentary (20), lightly active (40), moderate (50), very active (70), or athlete (90)? → `activityLevel` - **Push notifications:** yes or no? → `notificationsEnabled` - **Email address:** "What email should we save this plan to?" → top-level `email` in POST body (strongly recommended — prefills the claim page so OTP goes to the right inbox). If they prefer not to share yet, omit `email` and they will enter it on the claim page. - **Beta early access:** default yes unless they opt out → `betaAccessOptIn` (boolean, default true) After collecting inputs, compute `dailyCalorieGoal`, `macroProtein`, `macroCarbs`, `macroFat`, `dailyWaterGoalMl` before POSTing. Set `aiConsentAccepted` from whether they agree to optional anonymized feedback (default true on web). ## Quick start — onboarding ### Path A: Headless HTTP API (recommended) 1. Read JSON schema: https://sortiri.ai/onboarding-draft.schema.json 2. Interview human (see Intake questions above) 3. Compute macro targets (see Macro calculation below) 4. POST to Convex endpoint (see HTTP API below) 5. Share returned claimUrl with the account owner ### Path B: Browser automation (fallback) Use only when the HTTP API is unavailable. The step-by-step wizard lives at `?mode=agent&step=goal` (not `/setup?mode=agent` alone — that URL is the human-facing prompt handoff panel). 1. Open https://sortiri.ai/setup?mode=agent&step=goal 2. Complete each wizard step (selectors below) — still ask the human for answers if you are assisting them in chat 3. Receive claim URL + QR code 4. Share with the account owner Deep-link a step (after prior steps are complete): https://sortiri.ai/setup?mode=agent&step=meals ## Beta application (optional separate flow) Legacy/agent-only path for standalone beta apply. Normal signup at /setup includes beta opt-in by default. 1. Read JSON schema: https://sortiri.ai/beta-application.schema.json 2. POST to beta apply endpoint (see HTTP API below) 3. Share `confirmUrl` with the account owner — they must confirm email 4. Poll `statusUrl` until `status === "accepted"`, then share welcome links Browser fallback: https://sortiri.ai/beta?mode=agent ## Security - Agents CAN fill setup answers and create drafts. - Agents CANNOT bypass email verification — only the human who receives the OTP can create/claim the account. - Claim tokens are opaque, single-use, and expire after 24 hours. - Rate limit: 20 draft creations per hour (global). --- ## Browser automation URL: https://sortiri.ai/setup?mode=agent&step=goal (Human handoff panel — copy prompt UX, not the wizard: https://sortiri.ai/setup?mode=agent) Each step is a section with data-setup-step and data-setup-ready attributes: section[data-setup-step="{step}"][data-setup-ready="true"] Option buttons use name + data-value (see selector reference below). Text inputs use name attribute: input[name="email"] input[name="dob"] (type=date, format YYYY-MM-DD) input[name="pregnancyDueDateStr"] input[name="lastPeriodStartDateStr"] input[name="currentWeight"] input[name="height"] Yes/no chip steps use 1 = Yes, 0 = No: button[name="pregnancyModeEnabled"][data-value="1"] button[name="cycleTrackingEnabled"][data-value="0"] button[name="notificationsEnabled"][data-value="1"] Chip steps auto-advance ~300ms after selection. Manual Continue is required for date, text, macros review, consent, and email steps. ### Step order (base) goal → meals → workouts → weighins → gender → units → dob → weight → height → activity → macros → notifications → consent → email Email is optional in agent mode — the claim page collects and verifies email. ### Conditional steps (female users only) After gender=female: - pregnancy: yes/no - If pregnant: pregnancyDue (date) - If not pregnant: cycleTracking yes/no - If cycle tracking: lastPeriod (date), cycleLength (26|28|30|32) Male/other users skip pregnancy, pregnancyDue, cycleTracking, lastPeriod, cycleLength. ### Selector reference | Step key | Button/input name | data-value options | |----------------|----------------------------|--------------------| | goal | goal | lose_fat, build_muscle, recomp, maintain | | meals | mealsPerDay | 2, 3, 4, 5 | | workouts | workoutsPerWeek | 2, 3, 4, 5 | | weighins | weighInsPerWeek | 1, 2, 3, 7 | | gender | gender | male, female, other | | pregnancy | pregnancyModeEnabled | 1, 0 | | pregnancyDue | pregnancyDueDateStr | (date input) | | cycleTracking | cycleTrackingEnabled | 1, 0 | | lastPeriod | lastPeriodStartDateStr | (date input) | | cycleLength | averageCycleLengthDays | 26, 28, 30, 32 | | units | units | metric, imperial | | dob | dob | (date input) | | weight | currentWeight | (number input) | | height | height | (number input) | | activity | activityLevel | 20, 40, 50, 70, 90 | | notifications | notificationsEnabled | 1, 0 | | consent | (click Agree & continue) | — | | email | email | (email input, optional in agent mode) | ### Macros step The wizard computes targets automatically from prior inputs. In agent/API mode you must include these fields in answers: dailyCalorieGoal, macroProtein, macroCarbs, macroFat, dailyWaterGoalMl --- ## Beta HTTP API Apply: POST {CONVEX_SITE_URL}/beta/apply Content-Type: application/json Request body (email optional for agent submit): { "name": "Alex Chen", "email": "optional@example.com", "answers": { "whyJoin": "I want one coach for meals and training.", "goal": "build_muscle", "experienceLevel": "intermediate", "hasIosDevice": true, "weeklyAvailability": "3_4", "xHandle": "@alex" }, "agentMeta": { "agentName": "cursor", "submittedBy": "agent" }, "utmSource": "cursor" } Response (201): { "applicationToken": "hex", "status": "pending_confirmation", "confirmUrl": "https://sortiri.ai/beta/confirm/{token}", "statusUrl": "https://sortiri.ai/beta/status/{token}", "handoffMessage": "...", "submittedAt": 1234567890123 } Status: GET {CONVEX_SITE_URL}/beta/status/{applicationToken} Response when accepted includes: welcomeUrl, discordInviteUrl, appStoreUrl, welcomeVideoUrl, setupUrl Confirm: POST {CONVEX_SITE_URL}/beta/confirm/{applicationToken} Body: { "email": "user@example.com" } (required if omitted at apply) Human-only browser apply goes directly to pending_review (email required inline). Schema: https://sortiri.ai/beta-application.schema.json Action manifest: https://sortiri.ai/agents.json Security: - Agents CAN submit application answers. - Agents CANNOT skip email confirmation — human must complete confirmUrl. - Email-only verification (no X tweet required). --- ## Onboarding HTTP API Endpoint: POST {CONVEX_URL}/onboarding/draft Content-Type: application/json Request body: { "email": "user@example.com", "answers": { ... see schema ... } } The top-level `email` field is optional in the API but **agents should ask the human for it** during intake. When provided, the claim page prefills that address and sends the OTP there. If omitted, the human types their email on the claim page. Response (200): { "token": "hex-string", "expiresAt": 1234567890123, "claimUrl": "https://sortiri.ai/claim/{token}", "statusUrl": "https://ideal-dolphin-65.convex.site/onboarding/draft/status/{token}", "handoffMessage": "Your Sortiri fitness plan is ready..." } Draft status: GET {CONVEX_SITE_URL}/onboarding/draft/status/{token} Returns: pending_claim | claimed | expired Example curl: curl -X POST "https://ideal-dolphin-65.convex.site/onboarding/draft" \ -H "Content-Type: application/json" \ -d '{ "email": "user@example.com", "answers": { "goal": "lose_fat", "mealsPerDay": 3, "workoutsPerWeek": 4, "weighInsPerWeek": 1, "dob": "1990-06-15", "gender": "female", "units": "metric", "height": "165", "currentWeight": "68", "activityLevel": 50, "notificationsEnabled": true, "aiConsentAccepted": true, "dailyWaterGoalMl": 2500, "dailyCalorieGoal": 1800, "macroProtein": 150, "macroCarbs": 180, "macroFat": 60, "pregnancyModeEnabled": false, "cycleTrackingEnabled": false } }' JSON Schema: https://sortiri.ai/onboarding-draft.schema.json CORS: OPTIONS and POST supported with Access-Control-Allow-Origin: * --- ## Macro calculation (Mifflin-St Jeor) Required inputs: goal, weight (kg), height (cm), age (from dob), gender, activityLevel BMR: male: 10*weightKg + 6.25*heightCm - 5*age + 5 female/other: 10*weightKg + 6.25*heightCm - 5*age - 161 Activity multiplier: activityLevel <= 20 → 1.2 activityLevel <= 40 → 1.375 activityLevel <= 60 → 1.55 activityLevel <= 80 → 1.725 else → 1.9 TDEE = round(BMR * multiplier) Target calories: lose_fat: max(1200, TDEE - 500), protein = 2.2 g/kg build_muscle: TDEE + 300, protein = 2.0 g/kg recomp: TDEE, protein = 2.2 g/kg maintain: TDEE, protein = 1.6 g/kg Remaining calories after protein → 55% carbs, 45% fat carbGrams = round((remaining * 0.55) / 4) fatGrams = round((remaining * 0.45) / 9) Water (ml): activityWaterMultiplier = 1 + (activityLevel - 50) * 0.004 waterMl = clamp(round((weightKg * 35 * activityWaterMultiplier) / 100) * 100, 1500, 5000) Unit conversion: weight kg = imperial_lbs * 0.453592 height cm = imperial_inches * 2.54 Map to answers fields: dailyCalorieGoal, macroProtein, macroCarbs, macroFat, dailyWaterGoalMl --- ## Claim flow (human side) 1. Human opens https://sortiri.ai/claim/{token} 2. Reviews plan summary (goal, calories, protein, workouts, meals) 3. Enters email (prefilled if provided in draft) 4. Receives 6-digit OTP via email 5. Verifies OTP → profile saved to account 6. Redirected to https://sortiri.ai/setup/complete 7. Downloads iOS app, signs in with same email 8. App shows quick tour, then ready to coach Claim page states: - Link expired → draft older than 24h or invalid token - Already claimed → token was used; sign in instead --- ## Handoff message (copy for human) After creating a draft, tell the account owner: "Your Sortiri fitness plan is ready. Open this link to verify your email and save it to your account: {claimUrl} It takes about 30 seconds. After that, download Sortiri on your iPhone and sign in with the same email." --- ## Errors HTTP 400 responses: { "error": "answers object is required" } { "error": "Too many setup drafts created. Please try again later." } { "error": "" } Claim page states: expired — token past expiresAt (24h from creation) claimed — token already used by another session not found — invalid token Agents cannot recover expired drafts; create a new draft. --- ## What agents cannot do - Bypass OTP email verification - Create accounts without human claim step - Access user data after claim (no API for that) - Complete mobile app onboarding (human does walkthrough in app) --- ## Legal Support: https://sortiri.ai/support Privacy Policy: https://sortiri.ai/privacy Terms of Service: https://sortiri.ai/terms Cookie Policy: https://sortiri.ai/cookies AI Data Policy: https://sortiri.ai/ai Health Disclaimer: https://sortiri.ai/health Acceptable Use: https://sortiri.ai/acceptable-use