What is Overlord?
Overlord is a hardcore AI accountability partner. Imagine ChatGPT, but with a lot more data on what you're up to and that can take actions that motivate you to stick to your goals/habits.
Overview
I want to simulate somebody following me around 24/7. They know my goals, my good habits, my bad habits, and what I'm up to at any point in the day. With this, I won't need self-control. Overlord is my attempt at this. At the current state, it's almost there - with some setup and a bit of a learning curve, you can get it there.
I like to think of Overlord as setting up invisible guardrails for my life: I currently have Overlord doing the following:
- Controlling my screen time (Only allowed on timewasting apps at the gym)
- Morning routine, triggered by when I first open iMessage (through Apple Shortcuts)
- Most evenings, I lock myself into work sessions on my mac
How to Interact
You can send Overlord:
- Messages
- Calling
- Photos
- Videos
- Timelapses
- Integration data
- Pomodoros (ie locking your phone)
- Mac pomodoros (to show that you're working)
Integrations
Overlord connects with:
- Mac Monitoring
- iOS Screen Time
- Apple Health
- Apple Shortcuts
- iMessage
- Telegram
- Android Screen Blocking
- Calendar
- Google Fit
- IFTTT
Availability
Overlord is available on all platforms:
- Web: chat.overlord.app - Access from any browser, no installation required
- Mobile: iOS and Android apps through the Forfeit app (rebranding to Overlord soon)
- Desktop: Mac app (iPad app optimized for Apple Silicon)
- iMessage: Text +1 (646) 327-3977
- WhatsApp: Message +1 (646) 327-3977 on WhatsApp
- Telegram: DM @OverlordTelegramBot
Your account works seamlessly across all platforms with real-time sync.
One-pager
For the majority of people, self-control is their #1 issue. Most people are drastically messing up in one element of their life due to not being able to control themselves. Thought experiment: If you had a friend following you around 24/7, would you be able to kick all your bad habits? Probably.
Let's take the issue of obesity (40% of Americans right now). If an obese person had a friend following them around each day, they'd likely be able to eat 230 calories less each day (enough to lose 2lbs/month). This would be extremely easy - your friend will just say "do you need large fries, or are medium OK?", and everyone would be thin.
This can, of course, apply to everything. The following issues (think about these yourself), would be immediately fixed if you had a friend following you around encouraging you: porn addiction, exercising, doomscrolling, drinking too much...
Now, imagine instead of a friend, it's an AI. Of course, you feel no shame towards an AI, as you would a friend, yet. This means you'd need to jerryrig pain into it: It can charge you money (LessWrong - Losing money or completing habits), call you to persuade you, text your friends, call your mum. But if done intelligently - striking a balance between being too nice and too mean - it should be pretty close. And pretty close to a fix for self-control is the Holy Grail for most people.
How far off are these AIs? These will come when the AI personal assistants come, and, in my opinion, would be more valuable to a lot of people. 24/7 accountability partners aren't a job that we can replicate (as paying a human 24/7 is too expensive), so they're not discussed as much as personal assistants, but they will be incredibly valuable. Would you rather have a chatbot who can book hotels and flights for you, or quit smoking?
Now let's talk about how easy it is to overcome most bad habits (of course - I'm not talking about genuine addictions). I have a bad habit of smoking cigarettes - I don't care if I'm drunk, but I can't say no if a friend offers me one when I'm sober. They're incredibly easy to resist, like a 1/10 in the moment, but for some reason this doesn't happen. Let's compare that with some deterrance mechanisms:
Texting my mum telling her I smoked: 5/10 (I wouldn't smoke)
Losing $5: 3/10 (I wouldn't smoke)
Having to spend 5 minutes sat in silence: 4/10 (I wouldn't smoke).
As long as the deterrance mechanism score higher than the negative action, I wouldn't do it. And it takes a shockingly simple deterrance mechanism to counteract a negative action, and therefore break a lifelong bad habit.
Now the deterrance mechanism is fixed, how can we monitor people? We ingest all the personal data (ie, an AI Overlord), and piece it together from there. We spend about 10 hours a day on screens, so that leaves ~6hrs where we don't know what a user is doing. The obvious ones work: Location, credit card transactions, simply asking the person what they're doing.
Crucially, from this, just as a human would, an intelligent AI can piece things together. If I'm trying to quit drinking, and the AI sees that it's 9pm on a Friday, I'm at a bar, and I spend $9 on my card, it can easily suss out that I'm probably drinking. It can recognise patterns and weak points very easily. It doesn't need to prevent you from doing your bad habits every minute of the day, just when you're weak - each minute will have a different "Chance of relapse score" depending on the time of day, how much sleep you had last night, and in general how suspicious this AI is that you're close to relapsing.
AI will create the perfect conditions to create addicts
Here's what's likely to happen, and why we will need this more than ever.
- Loss of purpose: People will no longer have purpose through their jobs, as AI does it better than them. Purpose fends off addiction/bad habits very well.
- Economic displacement: Until we figure out UBI, people will lose their jobs and have no money. Poorer people are more likely to be addicts.
- Abundance of time: These jobless, purposeless, broke people will now have 16 hours a day to fill. Abundance of time with nothing to do is a recipe for addiction.
- AI Superstimuli: AIs will very soon be the most charismatic, caring, intelligent people you've ever spoke to. Most people's friends would be a 5/10 in the following: Attractiveness, Intelligent, Interestingness, Empathy. If you could facetime a person who's a 10/10 in all these qualities, would you still find it fun doing anything else?
- Social withdrawal: People's friends will start to drop off the map due to this. With less and less friends, people will socialise less.
In short: We will very soon be purposeless, broke, bored, lonely, and a superintelligent, superattractive AI will step in - most of society won't be able to resist. I argue that we simply won't be able to resist - we will need an "AI Iron Dome" to protect from this new superstimuli.
We already have guardrails imposed on us by society: I wouldn't stand up and shout profanities in a coffee shop, as it's socially embarrasing. I wouldn't ignore my boss as it's financially painful. There are thousands of things that you could do at any moment in time, but due to these invisible guardrails we have around us, we can only do two or three. Most people wouldn't even leave the line at the coffee shop as it's a little bit weird. These defence AIs would do the same thing: It would let you set your own guardrails on your life, so you never do something future you would regret.
So, we will soon have two very powerful competing AIs: The AI "Superfriends", and the AI "Iron Domes". They won't be able taking away your agency, just aligning your actions with what you in 24 hours would want you to do. Right now we have 100% control over our actions moment-to-moment, which is disastrous. We should have 90-95% control of what we are doing, and the other 5-10% should be controlled by an AI, aligned with our future, rational self. This seems dystopian now, but we will likely have no choice in the matter.
Practical examples
These are essentially the same as what you may tell a friend to do if they had a certain element of control over you. Here are some examples:
- "In my own voice, call me at 7am and give me a pep talk. Every minute I'm not awake past 7am, charge me $0.10"
- "For each rep of a posture exercise I do, give me one minute on Instagram"
- "I tend to smoke weed when I go to Jake's, and want to stop. Make me send a photo of my eyes every time I leave."
- "Only allow me on my phone when I have no events on my calendar"
- "Make sure I take max three Zyns a day (must send photo of canister each morning)"
- "When I go out, gently push me to get home. When I wake up hungover, intelligently motivate me with financial penalties to leave the house and get to the gym right as I wake up."
- "I'm getting home now - make sure I lock in on my Mac with 45 min pomodoros with 15 min spacing (must send video of myself just lying down, decompressing) until 6pm"
Example Goals
Explore real examples of how Overlord helps users stay accountable across different areas of life. All examples show actual chat conversations demonstrating Overlord's enforcement capabilities.
"Block Instagram. Allow short unblocks anytime, but extended access only during meals (12-1pm, 6-7pm) with meal photo. Charge me $25 if I disconnect Screen Time."
"Call me at 6:30am to wake up. I need to confirm I'm awake by 6:35am or pay $5, be outside by 6:45am, and send photos of my toothbrush and clean desk."
"Start blocking entertainment apps at 9:30pm. Full bedtime lockdown at 10pm until 7am."
"Alternate between cardio (170bpm heart rate) and strength training daily. Skip if hungover or away from home based on location/sleep data."
"Walk 3km to unlock Instagram until 9am"
"Sleep goal is 10pm. If I sleep past 10pm, adjust my bedtime routine earlier the next night based on how late I slept. Routine: toothbrush photo, then dark room selfie 15 mins later, then full screen lockdown. Miss any step and screens get blocked immediately."
"Weigh myself before 9am. Goal weight reduces by 1lb each week. If below goal weight, fasting ends at 12pm. If above goal weight, fasting ends at 2pm. Call me every hour to verify I haven't eaten. Eating window always closes at 8pm."
"Need minimum 4 hours of Cursor time before unlocking YouTube. After that, for every 45 minutes of Cursor time logged, earn 5 minutes on YouTube"
"If I enter a bar, call me and text my accountability buddy"
"When I press my bedside button, start my morning routine with timed photo tasks"
"Visit the gym daily (verified by geofence). After each visit, show detailed stats on gym attendance, length of stay, consistency, times this week, etc."
"Take a breathalyzer test by 11pm or lose $20"
"Must do some form of physical activity every day. Can be verified through Apple Health, GPS at gym, photo of yoga mat, selfie sweating, video doing push ups, etc. Only exception: text from PT saying I can skip today."
"Unblock Instagram for 10 minutes at a time, but only if I say I'm only using it to reply to DMs"
"Be outside by 8am or charge me $10"
"Once I get home on weekdays, call me and bug me until I turn the dishwasher on"
"Code for 4 hours daily. Message me every hour with my total coding time. If I don't hit 4 hours by 8pm, text my co-founder that I failed."
"If I change Screen Time permissions, text my partner immediately"
"I must write in Google Docs for 3 hours each day before TikTok is unblocked"
"Lock my phone when I'm home, unlock when I leave"
"Only allow Reddit when I have free time on my calendar"
"Make me wait 5 minutes before accessing Twitter"
"I want to eat 3500 calories each day. I'll send you photos/tell you what I ate and you encourage me to eat more"
Installation
Get started with Overlord in seconds using our web app, or download the mobile app for advanced integrations.
Web App (Recommended)
Access Overlord instantly from any browser at chat.overlord.app. No installation required.
Sign in with your email to start chatting with Overlord immediately. You can create goals, track progress, and receive accountability all from your browser.
Mobile App (Optional)
Overlord is built into the Forfeit app and will be rebranded soon. Download from the App Store (iOS) or Google Play (Android) for additional features like:
- Apple Health / Google Fit integration for automatic workout tracking
- Screen Time blocking for iOS or Digital Wellbeing for Android
- Mac monitoring for productivity tracking
- Apple Shortcuts automation
When you first open the mobile app, select Overlord during onboarding. Don't worry if you select Forfeit instead—you can always navigate to Overlord later from within the app.
Setup Payment
Visit account.forfeit.app/login and sign in with your email to add your card details. This enables Overlord to charge you if you fail your goals.
Connect Integrations (Optional)
Connect any integrations you want to use for automatic evidence submission and verification. Most integrations require the mobile app (Apple Health, Google Fit, Screen Time, etc.). IFTTT and Calendar integrations work from the web.
Enable Phone Calling (Optional)
Add your phone number in settings to enable Overlord to call you for critical deadlines. This works from both web and mobile.
Add Accountability Partners (Optional)
Add your friends' names and phone numbers to let Overlord text them when you need accountability. Configure this from settings on web or mobile.
iMessage Integration (Optional)
You must set up an account first. Once set up, you can text Overlord at +1 (646) 327-3977 to use it through iMessage instead of the web or app.
WhatsApp Integration (Optional)
You can message Overlord on WhatsApp too. Add your phone number in settings, then message Overlord's WhatsApp number. It works exactly like iMessage - send text, photos, and videos.
Telegram Integration (Optional)
You can also DM Overlord on Telegram at @OverlordTelegramBot. Link your account during onboarding or from app settings, then message the bot just like you would in the app. Responses stream in real-time.
Customize Overlord
Set your preferences for how strict Overlord should be, how it talks to you, and how often it sends notifications. All customization is available from both web and mobile.
Create Your First Goal
Just tell Overlord what you want to do to create your first goal. It will ask follow-up questions to configure everything. Works the same on web and mobile.
Your First Goal
There are two ways to create goals with Overlord:
1. Custom Flow (Step-by-Step)
Use the structured form to configure your goal settings:
2. Conversational (Recommended)
Just tell Overlord what you want to accomplish and it will configure everything through chat:
Submitting Evidence
When it's time to complete your goal, Overlord will prompt you for evidence:
Using Integrations
You can also use integrations for automatic verification:
Understanding the Interface
Overlord is available as a web app (chat.overlord.app), iOS app, Android app, and Mac app. The interface is designed to be clean and focused on your conversation with Overlord.
Web Interface
The web app at chat.overlord.app provides a streamlined chat-first experience optimized for desktop and mobile browsers.
Web Layout
The web interface features:
- Chat Area: Main conversation with Overlord in the center
- Sidebar: Quick access to your active goals and settings (collapsible on mobile)
- Message Input: Type messages, attach photos/files, or use voice input
- Settings: Accessible from the top-right menu to customize behavior and manage integrations
Keyboard Shortcuts (Web)
- ⌘K or Ctrl+K: Quick search documentation
- ESC: Close modals/dialogs
- Enter: Send message
- Shift+Enter: New line in message
Desktop Notifications (Web)
Enable browser notifications to receive real-time alerts when Overlord needs your attention, when deadlines approach, or when goals require evidence submission.
Mobile App Interface
The iOS, Android, and Mac apps provide additional features beyond the web interface, including native integrations and offline support. The Mac version is an iPad app optimized for Apple Silicon that adapts beautifully to the Mac environment.
The Three Main Screens (Mobile)
Key Interface Elements
1. Goal Cards
Each goal is represented by a card showing your current status and progress. Goal cards display the name, deadline, progress indicators, streak counters, and the next action you need to take. Status indicators use color coding: green for on track, yellow for warning, and red for failing. Available on both web and mobile.
2. Individual Goal View
Click or tap any goal card to see its detailed view with full conversation history, evidence log, analytics, and performance over time. This is where you can review past submissions, track your streaks, and see all the context around a specific goal. Available on both web and mobile.
3. Evidence Submission
Submit proof of goal completion through various attachment types including photos, videos, voice messages, or integration data. On web, drag and drop files or click to upload. On mobile, the interface provides quick access to your camera, photo library, and connected services for seamless evidence submission.
4. Settings Panel
Customize your Overlord experience through the settings page. Configure AI personality, manage integrations, adjust notification preferences, update billing information, and control privacy settings all from this centralized hub. Settings sync across web and mobile.
Web vs Mobile
Most features work identically on web and mobile. The main differences:
- Web: Best for chatting, reviewing goals, and desktop workflows. Requires mobile app for native integrations (Apple Health, Screen Time, etc.)
- Mobile: Full access to native integrations, camera for real-time evidence, push notifications, and offline support
Your account syncs seamlessly between platforms - start a conversation on web and continue on mobile, or vice versa.
O-Agent
Overlord's AI has been completely revamped from the ground up. The O-Agent is the new brain behind everything — a custom-built accountability agent that's significantly smarter, faster, and more capable than what came before. The biggest difference is intelligence: it no longer forgets things, approves things it shouldn't, or fails to approve things it should. Here's everything the O-Agent can do:
- Smarter Conversations: The agent is dramatically more intelligent. It understands context, nuance, and intent far better than before — conversations feel natural, not robotic.
- 3-Layer Memory System: Overlord now remembers everything across sessions through a three-layer memory architecture — conversation memory, searchable daily notes, and permanent personality files that evolve over time. It no longer forgets what you told it yesterday.
- Chat History Search: The agent can search through your previous days' conversations to recall what was discussed, what you committed to, and what happened — no more "I don't remember that."
- Evidence Re-Analysis: If Overlord analyses your evidence incorrectly — misreads a photo, misjudges a video, or makes the wrong call — it can pull up the original submission and re-analyse it on the spot. No need to resubmit.
- Adaptive Thinking Speed: The agent decides how long to think based on the complexity of your message. Simple check-ins get instant replies; complex appeals or multi-step evaluations get deeper reasoning.
- Parallel Tool Execution: Can run multiple actions at the same time — checking your health data, reviewing your schedule, and looking up past conversations all at once instead of one at a time. This makes responses noticeably faster.
- Settings Management via Chat: Edit all your settings directly in conversation — appeal rules, notification preferences, coaching style, strictness levels, and more. No need to dig through settings menus.
- Credential Vault: Securely store passwords, API keys, and tokens through an encrypted input that never touches chat messages. Give Overlord your Instagram password and set conditions for when you can get it back.
- Third-Party Integrations via HTTP: Overlord can now integrate with almost any app or service in seconds. Just ask it to connect to something — Notion, Todoist, Linear, GitHub, or any service with an API — and it sets up the connection on the spot. Your API keys are stored securely and injected server-side, so they're never exposed to the AI model. New domains require your explicit approval before Overlord can access them, and services you've already stored credentials for are auto-approved.
- Web Search: Can search the internet in real-time using Google Search to answer questions about nutrition, fitness, health, current events, or anything you'd normally Google.
- Historical Data Search: Overlord can now search back through your historical data — ask it what you were doing at 7pm last Tuesday, how many steps you took last week, or what apps you used on your Mac yesterday. This works across Mac app monitoring (screen time, browser history, coding activity) and Apple Health (workouts, steps, sleep, heart rate). You can also sync as many Apple Health data sources as you want — no longer limited to 5.
- Reads Photo Metadata: When you submit a photo, Overlord reads metadata like capture time, GPS coordinates, and device information to verify when and where the evidence was taken.
- Scheduling & Proactive Outreach: The entire notification and scheduling system has been rebuilt. Overlord can now create and manage its own scheduled tasks — reminders, recurring check-ins, one-off nudges, and phone calls — using natural language like "remind me to submit my gym photo after work" or "check in every morning at 8am." It also reaches out proactively when it matters: approaching deadlines, auto-verifying health goals, pattern-based nudges, and morning briefings. Notifications are smarter, timing is more accurate, and it knows when to stay quiet.
- Support Escalation: When Overlord can't resolve something on its own — whether it's a bug, a billing issue, or something that needs human judgment — it can escalate directly to the support team on your behalf. It writes a detailed message to your support chat with full context about what happened and what it already tried, and immediately notifies the team so they can pick it up. You don't need to explain the situation again from scratch.
- Documentation Search: Can search Overlord's own documentation to answer questions about features, integrations, and how things work.
- Guided Onboarding: Conversational setup for new users that collects your name, coaching style, check-in preferences, strictness level, and integrations naturally — not a rigid interview, just a normal conversation.
- Personality & Identity Files: Maintains evolving files about who you are (preferences, schedule, injuries, context), coaching observations (what works, what doesn't), and its own identity — all of which persist and improve over time.
- Forfeit History & Streaks: Can look up your long-term goal performance, charge history, success rates, and streak data to inform coaching decisions and call out patterns.
Goals
Goals are the structure of everything in Overlord. A goal can be a year-long commitment that you have to do daily, or a one-off task that you have to complete in ten minutes.
Creating Goals
Goals can be set either through the chat or through the structured goal creation flow. For more detailed information, see the Creating Goals section.
Goal Parameters
Every goal has the following parameters:
- Name - Just a name for the goal. The AI generates this automatically based on your description, so you don't have to choose it (e.g., "Morning Workout")
- Description - This is what has all the data in it, essentially (e.g., "Go to the gym by 8am and send a photo")
- Start Date - When the goal begins (e.g., "January 1st, 2025")
- End Date - When the goal ends (e.g., "December 31st, 2025")
- Frequency - How often it repeats (e.g., every day, weekdays, M/W/F, 3x/week, every Sunday, etc.)
In the description, you can add the following (all optional):
- If there's a deadline for completion (e.g., "by 8pm")
- If you want a specific type of evidence to be used to verify it (e.g., "send a photo of my meal" or "check my Apple Health steps")
- If you want it to only be active based on a certain condition (e.g., "only if I arrive at the gym" or "only if I enter the office")
- If there's a monetary penalty for failing (e.g., "lose $10" or "pay $5")
- If you want Overlord to call you to remind you (e.g., "call me at 7:30am to wake me up")
- If you want it to text a friend if you don't complete it (e.g., "text Sarah that I'm lazy if I skip")
- Notification and appeal instructions - If you want it to notify you or be stricter/less strict than defined by the default notification and appeal instructions set in the Customising Overlord section (e.g., "be very strict with me" or "only notify me 10 mins before deadline")
Goal Types
Overlord automatically understands what type of goal you're creating based on how you describe it. You don't need to manually specify the goal type - just write naturally and Overlord will handle it. Goals can be:
- Active - You must do something (e.g., "I must work out today")
- Passive - You must not do something (e.g., "I must not smoke cigarettes today")
- Conditional - Only triggered under certain conditions (e.g., "If I enter the gym, I must do at least 5 minutes of cardio")
Community Goals
You can see a list of other users' goals on the Community Goals website. You can also browse these in-app and copy other users' goals to use as templates. To contribute your own goals to the community, click the share button on your goals in-app.
Goal Suggestions and Improvements
Habits wax and wane - you may prioritize waking up early one week, then the next week have a lot of social events, so those take priority. Overlord knows when you're slipping on your goals and can suggest small adjustments to make sure you don't fall off the wagon completely (e.g., pausing for a few days, reducing the commitment, etc.). Of course, you can tell Overlord if you don't want this feature.
Goal Complexity
Overlord can handle goals ranging from simple one-liners to extremely complex multi-rule systems.
Simple Goals
"I must send a photo in the gym by 6pm or lose $5"
"Text mum that I'm lazy if I don't work out today"
Medium Complexity Goal
"If I either enter Trader Joe's, or a transaction from Trader Joe's is on my Monzo (linked via IFTTT), then I must send you a photo of the receipt, and if it has any unhealthy foods (chocolate, sweets, pizzas etc), then text my fiance what unhealthy foods I bought. If I don't send a photo of the receipt within 30 mins of leaving, also text her."
Complex Goals
The complex goals below do work, but they're more prone to the AI making mistakes compared to simpler goals. That said, it's surprising how complex you can get with them - Overlord can handle multi-conditional logic, time-based rules, exception handling, and integration coordination across multiple systems.
Example: Screen Blocking with Time-Based Rules
A sophisticated screen blocking goal with time-based rules, unblock quotas, and vacation exceptions:
## What
1. Block device during blocked periods.
2. User can request unblocks per rules.
## Rules
1. Blocked periods:
"Sunday to Thursday, from 10:00 PM to 11:59 PM."
"Monday to Friday, from 12:00 AM to 6:00 AM."
2. Unblocking rules:
"During blocked periods:"
"Max 5 unblocks."
"Each unblock up to 5 minutes."
"No justification required."
"Outside blocked periods:"
"Max 10 unblocks."
"Each unblock up to 10 minutes."
"No justification required."
3. If I disable screen time permissions, charge me 20€.
## Exceptions
1. During an emergency:
"Unlimited unblocks."
"Each unblock up to 5 hours."
"30-word justification required."
2. Vacation (sick, travel, or vacation):
"Proof required:"
"For today: same-day proof."
"For tomorrow: next-day proof."
"Vacation can also be proven by a Berlin public holiday."
"If vacation is today only:"
"Unblock until 10:00 PM."
"If vacation is tomorrow only **and** time now is after 5:00 PM:"
"Unblock until end of day today (11:59 PM)."
"If vacation is both today and tomorrow:"
"Unblock until 10:00 PM tomorrow."
3. Friday after 5:00 PM:
"Unblock until end of day today (11:59 PM)."
4. Saturday:
"Max 15 unblocks."
"Each unblock up to 15 minutes."
"No justification required."
5. Sunday:
"Max 15 unblocks."
"Each unblock up to 15 minutes."
"Unblock until 10:00 PM (when the protected period starts)."
4. No justification required.
Example: Dynamic Bedtime Based on Sleep
This goal adjusts your bedtime each night based on your actual sleep time from the previous night (tracked via Apple Health):
## What
Automatically adjust my bedtime based on how late I stayed up last night to ensure I maintain healthy sleep habits.
## Rules
1. Check Apple Health for last night's "time fell asleep"
2. Calculate tonight's bedtime:
- If fell asleep before 10:30 PM last night: Bedtime tonight is 11:00 PM
- If fell asleep between 10:30-11:00 PM: Bedtime tonight is 10:30 PM
- If fell asleep between 11:00-11:30 PM: Bedtime tonight is 10:00 PM
- If fell asleep between 11:30 PM-12:00 AM: Bedtime tonight is 9:30 PM
- If fell asleep after 12:00 AM: Bedtime tonight is 9:00 PM
3. At calculated bedtime:
- Block all entertainment apps on iPhone (YouTube, Netflix, Instagram, TikTok, Twitter/X, Reddit)
- Block Mac (all apps blocked except Messages and Calendar)
- Allow only on iPhone: Messages, Phone, Calendar, Health apps, Spotify (sleep playlists only)
- Send notification: "Bedtime in 10 minutes" at bedtime - 10 mins
4. Nighttime routine (must complete within 30 minutes of bedtime):
- Brush teeth (send photo of toothbrush or bathroom sink)
- Skincare routine (send photo)
- Set out tomorrow's clothes (send photo)
- Plug in phone in another room (send photo of phone charging away from bedroom)
- If routine not completed within 30 mins of bedtime: Charge $10
5. Weekend adjustment:
- Friday and Saturday: Add 1 hour to calculated bedtime
- Sunday: Use normal calculation (to prepare for Monday)
## Consequences
1. If I disable Screen Time permissions on iPhone or Mac: Charge $25
2. If I request unblock after bedtime:
- iPhone unblock:
- First request: Denied with reminder of why bedtime is early tonight
- Second request: Grant 15 minutes, but add $5 penalty
- Third+ request: Denied, add $10 penalty, text my partner "I'm staying up too late again"
- Mac unblock:
- Only granted for genuine emergencies (work crisis, family emergency)
- Requires 50-word written explanation
- Grants 30 minutes, charges $15
3. If nighttime routine skipped entirely: Charge $20 + earlier bedtime tomorrow (-30 mins)
## Exceptions
1. If I'm traveling (check Calendar for "travel" or flight confirmations):
- Pause goal for travel days
2. If I'm sick (must tell Overlord + provide context):
- Use default 10:30 PM bedtime regardless of previous night
3. If I got less than 5 hours sleep last night (emergency/unusual situation):
- Use 9:00 PM bedtime tonight to catch up
Example: Daily Points System with Tiered Consequences
This goal tracks your productivity throughout the day with a points system that determines consequences at 11:59 PM:
## What
Track daily productivity through a points system. Earn points for good habits, lose points for bad ones. Points determine consequences at end of day.
## Earning Points
1. **Email Management:**
- You will be notified by IFTTT whenever I receive an email
- Reply to work email within 1 hour of receiving: +2 points (max 10 points/day)
- Clear inbox to zero by 5 PM: +5 points
2. **Exercise:**
- Complete workout with gym check-in or photo: +5 points
- 10,000+ steps (check Apple Health): +3 points
- Both workout AND 10k steps: Bonus +2 points (total +10)
3. **Nutrition:**
- Healthy meal with photo verification: +3 points (max 9 points/day for 3 meals)
- No fast food all day (check Monzo transactions via IFTTT): +4 points
4. **Productivity:**
- Focused work session >2 hours (check Screen Time for work apps): +4 points (max 8 points/day)
- Complete all calendar tasks: +5 points
5. **Personal Development:**
- Read book 30+ minutes (submit timelapse video): +2 points
- Meditation session (check Apple Health or submit timelapse video): +2 points
## Losing Points
1. Screen time on entertainment apps >2 hours OR junk food ordered: Submit screenshot of Screen Time and DoorDash at end of day
- Entertainment apps >2 hours: -3 points
- Junk food/takeout ordered: -4 points
2. Skip planned calendar event without rescheduling: -5 points
3. Stay up past midnight: -6 points
## End of Day Consequences (Calculated at 11:59 PM)
**Score Ranges:**
1. **Under 10 points:**
- Charge $20
- Text accountability partner: "Only scored [X] points today. Rough day."
- Tomorrow starts with -5 points (must dig out of hole)
2. **10-19 points:**
- Charge $10
- Text accountability partner your final score
3. **20-29 points:**
- No financial consequence
4. **30-39 points:**
- No consequence
- Earn one "skip token" (can skip one goal tomorrow with no penalty)
5. **40+ points:**
- No consequence
- "Exceptional day!" message
- Unlock "cheat day" for tomorrow (can skip any goals, no points tracked)
- Earn $10 reward (credited back to your account)
## Special Rules
1. Points reset to zero at 12:00 AM each day
2. Skip tokens can accumulate (max 3 at a time)
3. Cheat days cannot be used on Mondays (start week strong)
4. Can check current score anytime by asking Overlord
5. If score is negative at end of day, consequences from "Under 10" tier apply
Appeals
Life happens — sometimes you need to skip a goal, adjust a commitment, reverse a mistake, or remove a goal entirely. Overlord has a built-in appeal system that handles all of these requests while respecting the rules you've set for yourself.
Types of Appeals
Skip / Excuse
If you can't complete a goal on a particular day — whether you're sick, traveling, dealing with an emergency, or just need a rest day — you can ask Overlord to skip it. For frequency-based goals (e.g., "3 times per week"), Overlord can grant advance approval for specific dates without changing your overall frequency.
Edit Goal
Want to change your goal? You can ask Overlord to modify the description, frequency, deadline, or other details. Edits that make a goal harder are always allowed. Edits that make a goal easier are evaluated against your appeal instructions — so if you've told Overlord to be strict, it won't let you water down your commitments.
Reverse Failure / Refund
If Overlord marked you as failed incorrectly — maybe you actually did complete the goal, or the evidence didn't process properly — you can appeal to reverse the failure. Reversing a failure automatically refunds any charges for that goal on that date. Refunds can be processed for charges up to 14 days old.
Delete Goal
You can ask Overlord to permanently delete a goal. Deletion is irreversible — the goal is archived and removed from your active goals. Overlord will always confirm before proceeding. If you just want a temporary break, editing the end date is usually a better option.
How Decisions Are Made
When you submit an appeal, Overlord evaluates it against your appeal instructions — custom rules you set in your profile that define how strict or lenient Overlord should be. These rules override Overlord's default judgment, so you're always in control of the standard you're held to.
Example appeal instructions you can set:
- "No excuses" — all skip requests are denied, no exceptions
- "Only emergencies" — skips granted only for genuine emergencies
- "Require 24h advance notice" — planned skips only, no day-of excuses
- "Send a video explaining why" — must submit video evidence with your appeal
- "No edits allowed" — can't make goals easier once set
- "No refunds for any reason" — charges are final
- "Accept all appeals" — maximum leniency
If your appeal instructions don't cover a specific situation, Overlord uses reasonable judgment in your favour. When an appeal is denied, Overlord will tell you exactly which rule prevented it.
Chatting
There are many ways to interact with Overlord. Here's how you can communicate with it:
Messages
Send text messages to Overlord to communicate, ask questions, submit evidence descriptions, or appeal decisions. You can also record voice messages in-app, which are automatically transcribed to text. Messages are the primary way to interact with Overlord throughout your day.
Photos
Send photos as evidence for your goals. Photo analysis is very effective, and you can submit very complex evidence with this—for example, screen time screenshots for every day from the past week, and have Overlord add up a certain category. If the analysis is off (this is rare), you can hold down on the image and click "Re-analyse media".
Videos
Videos are analyzed using Google Gemini. They're great for verification, but they can be slow to upload if long (e.g., 30 minutes). The main downfall of video evidence is counting reps. Videos are useful for:
- Counting reps (e.g., 1 pull-up = 1 min screen time)
- Testimonials (e.g., "I won't drink alcohol today")
- Getting in an ice bath
- Doing multiple things at once (e.g., a full morning routine)
Timelapses
Timelapses are similar to videos, but faster to upload. They're good for activities like studying, working out, or reading where you want to prove sustained effort over time.
Pomodoros (Locking Your Phone)
When you start a pomodoro, you can't access other apps on your phone. This is good for recording focused work time. Overlord verifies the lock duration and awards credit when the session completes. You can set goals like "30 min pomodoros by 10am whilst in the library".
Mac Pomodoros
Overlord knows what you've been on all day on Mac (as long as you enable "start tracking on launch"), but you can also send in pomodoros. At the end of the pomodoro, a message is sent to Overlord like the one below, detailing what you've been up to the whole time:
More information about Mac monitoring is available in the Mac Monitoring integration section.
Memory
Overlord remembers everything about your goals, preferences, and patterns to provide increasingly personalized accountability over time.
What Overlord Remembers
- Goal History: All your past goals, successes, failures, and patterns
- Conversation Context: Full chat history for each goal and every interaction you've had
- Preferences: How you like to be communicated with, what works for you, what doesn't
- Weaknesses: Times of day or situations where you struggle most
- Evidence Patterns: What type of proof you typically submit and when
- Appeal History: What excuses you've used and which were legitimate
- Custom Instructions: Any specific rules or guidelines you've given Overlord
Memory Notes
You can view and manage what Overlord remembers about you through the notes interface. Add custom notes, review stored information, and edit or delete memories at any time. Everything Overlord learns about your patterns and preferences is accessible here.
How Memory Improves Accountability
Overlord uses your history to hold you accountable more effectively. It learns your patterns, predicts when you're likely to struggle, and can detect when you're making excuses. Memory enables personalized communication tailored to what actually motivates you.
Calling Out Past Patterns
Overlord remembers what you've said before and will call you out when you contradict yourself.
Learning Your Patterns
Over time, Overlord identifies when you're most likely to struggle and can predict challenges before they happen.
Personalized Motivation
Overlord learns what consequences actually motivate you and adjusts its approach accordingly.
Suggesting Realistic Adjustments
When Overlord sees you consistently failing a goal, it uses your history to suggest more realistic targets instead of letting you beat yourself up.
Creating Goals
You can create goals through the chat or through the structured goal creation flow. The flow offers three types of goals:
Regular Goals
Just write out in plain English what you want to do. Overlord will understand your intent and set up the goal with all necessary parameters.
Example:
"Wake up by 7am. I should send a photo of the fridge by 7:05am or lose $5. Call me at 6:55 to wake me up. Let me skip this if I was up after midnight the night before (check Apple Health)"
LLM Analysis
Overlord's AI analyzes your natural language goal description and extracts all the key parameters, rules, and conditions. It understands context, interprets intent, and structures your goal automatically. It also points out issues with your goal and tells you things you can add in to improve it.
LLM analyzes your goal text and structures it into deadline, verification method, consequences, conditions, and notification preferences
Routine Goals
Create multi-step morning or evening routines with timed tasks. This is essentially Routinery but with an AI assistant helping you along each step of the way.
Overlord will call you, text you reminders, and guide you through each task in your routine. Perfect for morning wake-up sequences or bedtime routines.
iOS Screen Blocking Goals
Set up intelligent app blocking that responds to your schedule and behavior. See the iOS Screen Time integration section for full details on how screen blocking works.
Chat-Based Creation
You can also create goals instantly through chat:
Editing Goals
There are two ways to edit your goals:
Chat-Based Editing
Just tell Overlord what you want to change and it will modify the goal for you.
Direct Editing
You can also click on any goal to open the goal details page, where you can directly edit parameters like deadlines, penalties, evidence requirements, and other settings.
Appeal Instructions
Before accepting any edits, Overlord checks your appeal instructions (which you configure in the Customising Overlord section) to ensure it's not being too soft or too strict with you. These instructions define how lenient Overlord should be when you request changes.
Overlord also reviews any memories it has about your past behavior and patterns. For example, if you previously said "don't let me use 'I'm tired' as an excuse," Overlord will remember this and deny edit requests that match that pattern.
Example: Lenient Appeal Instructions
Appeal Instructions: "Be understanding and accept most deadline changes if I have a reasonable explanation."
Example: Strict Appeal Instructions
Appeal Instructions: "Be very strict. Only accept deadline changes if there's a genuine emergency or I provide strong evidence."
Example: Conditional Appeal Instructions
Appeal Instructions: "Accept deadline changes only on weekdays, and only if I haven't already pushed it back this week."
Example: Memory-Based Denial
Overlord remembers past patterns and conversations.
Charging Money
To set up payments, log in to add your card details. Then, put in the goal description that you want to lose money if you fail.
Example Goals with Money Stakes
"Wake up by 7am. I should send a photo of the fridge by 7:05am or lose $5. Call me at 6:55 to wake me up."
"Go to the gym 3 times this week or lose $20. Send me a reminder every morning at 8am."
"No junk food after 8pm. Call me at 8pm and if I said that I failed (I won't lie), charge me $10 immediately."
"Read for 30 minutes every day or lose $3. I'll send timelapses of me reading to you."
How Refunds Work
When you fail a goal, Overlord charges you immediately. However, there are two ways to get a refund:
1. Ask Overlord for a Refund
You can ask Overlord to refund the charge. It will check your appeal instructions and the context of the situation to decide whether to approve the refund. If your appeal instructions allow it and the situation justifies it, Overlord can refund you automatically.
2. Refund Appeal to Support
If Overlord denies your refund request, or if you want a human to review your case, you can hit the "Refund Appeal" button. This sends your case to the support team, who handle these appeals twice daily.
Blocking/Unblocking Apps
See the iOS Screen Time integration section for full details on how app blocking and unblocking works.
Calling User
Overlord uses Twilio and the OpenAI GPT Realtime API to call you. Overlord chooses when to call you, and is great for really getting a hold of you, alarms, and talking you through doing things (like getting out of bed).
After each call, Overlord knows what you spoke about in the chat history too.
Note: For now calls are limited to 3 per day per user, but we will be making this essentially unlimited soon.
How to Set Up
Step 1: Head to Settings Screen
Open the Overlord app and navigate to Settings.
Step 2: Add Your Phone Number
Scroll down to the calling/texting part and add your phone number.
Step 3: Add Overlord to Contacts (Optional)
Click "Add Overlord to Contacts" - this just saves it as a contact with our icon as the contact photo.
Texting Accountability Partners
Overlord can choose to text contacts that you add in Overlord. For example, if you fail to do things, or if you want an evening summary sent to a friend, it can do that.
How to Set Up
Step 1: Click on Settings
Click on the settings icon in the top left.
Step 2: Scroll Down to Calling/Texting
Find the calling/texting section in settings.
Step 3: Add Your Accountability Partner's Number
Enter the phone number of your accountability partner.
Step 4: Your Friend Receives SMS
Your friend will receive an SMS (green text) from the Overlord number when you trigger notifications.
Web Search
Overlord can search the internet in real-time to answer your questions. This is powered by Google Search, so you get up-to-date results.
What You Can Ask
- Nutrition — "How many calories are in a chicken breast?" or "What's a good post-workout meal?"
- Health & fitness — "Is it okay to run every day?" or "What stretches help with lower back pain?"
- Current events — "What's the weather today?" or "What time does the gym close?"
- General knowledge — anything you'd normally Google
How It Works
Just ask naturally in conversation. The agent decides when a web search would help and runs it automatically — you don't need to use a special command.
Credential Vault
You can give Overlord your passwords, API keys, or tokens to hold securely. This is useful for two things:
Password Accountability
Give Overlord a password (like your Instagram or Netflix login) and set conditions for when you can get it back. For example:
- "Hold my Instagram password. Only give it back after I've been to the gym."
- "Keep my Netflix password until the weekend."
- "Store my gaming account password. Only reveal it if I've finished all my goals for the day."
When you ask for a password back and the conditions are met, the agent delivers it securely — your plaintext credentials are never displayed in the chat itself.
API Keys & Integrations
Store API keys for services like Notion, Todoist, or Linear so the agent can interact with them on your behalf. Once stored, just ask the agent to do things with that service — it'll make API calls using your key.
Security
All credentials are encrypted at rest and handled entirely server-side. The AI model never sees your plaintext passwords or keys — they're injected into requests securely without the agent having direct access. Retrieval is time-limited and single-use to prevent exposure.
HTTP Requests
Overlord can make HTTP requests to third-party APIs and websites on your behalf. This is what allows it to interact with external services — checking your Notion workspace, creating tasks in Todoist, updating a Linear ticket, or pulling data from any API you give it access to.
What It's Useful For
- Productivity tools — "Add a task to my Todoist inbox" or "What's on my Notion board?"
- Developer tools — "Create a GitHub issue for this bug" or "Check my Linear sprint"
- Custom integrations — Any service with an API that you want the agent to interact with
- Data retrieval — Pull information from external sources to inform goal decisions
How It Works
When a conversation leads to needing external data or performing an action on a third-party service, the agent makes an HTTP request. It supports GET, POST, PUT, DELETE, and PATCH methods, and can send JSON payloads.
If you've stored an API key in the Credential Vault for a supported service (e.g., Notion, Todoist, Linear, GitHub), the agent automatically injects your credentials into the request. The API key is handled server-side — it's never exposed to the AI model itself.
Domain Allowlist
Overlord never makes requests to a website without your explicit approval. Here's how the safety system works:
- Known services are auto-approved — If you've stored a Notion API key, requests to
api.notion.comare automatically allowed. Same for Todoist, Linear, GitHub, and other supported services. - New domains require approval — If the agent needs to reach a domain you haven't approved before, it pauses and asks you first. You'll see the domain and a reason for the request, and you can approve or deny it.
- Approvals are remembered — Once you approve a domain, it's saved to your allowlist. Future requests to the same domain go through automatically.
Proactive Outreach
Overlord doesn't just wait for you to message it — it reaches out on its own when it matters.
When It Reaches Out
- Approaching deadlines — a nudge before your goal's deadline passes
- Health goal auto-approval — checks your Apple Health data and approves fitness goals automatically
- Morning check-ins — a brief "here's what's on your plate today"
- Pattern detection — if it notices you tend to skip goals on Fridays, it might check in earlier
Scheduled Check-Ins
During onboarding, Overlord asks when you'd like check-ins (e.g., 9am, 1pm, 5pm). You can change these anytime by asking: "Change my check-in times to 8am and 6pm."
Self-Scheduling
The agent can also schedule its own one-off reminders. If you say "remind me to submit my gym photo after work," it'll set a reminder and message you at the right time.
Mac Monitoring
How It Works
Overlord monitors your Mac activity by taking snapshots of what applications you're using and their window titles. Snapshots trigger on every application change for accurate tracking.
Each snapshot captures data like:
- Google Chrome - YouTube - Joe Rogan Experience #500
- Cursor - overlord_app_website
- Messages - John Smith
Overlord passes this information through an LLM that compares it against your active goals. The LLM evaluates questions like:
- Should we block this application?
- Should we notify the user?
- Should we call them?
This intelligent analysis allows Overlord to enforce context-aware rules - like allowing YouTube for coding tutorials but blocking gaming videos, or tracking time spent in specific applications.
Browser URL Tracking
The Mac Monitor now captures actual browser URLs (not just window titles) for supported browsers including Safari, Chrome, Brave, and Edge. This enables more precise blocking decisions based on exact URLs rather than page titles.
What this means:
- The AI receives the actual URL being visited (e.g., "https://reddit.com/r/programming")
- Time spent on each URL is tracked separately
- URL breakdown is shown as sub-items under browser apps in your Mac usage data
- Blocking rules can be more granular - for example, blocking reddit.com but allowing reddit.com/r/programming specifically
Types of Mac Blocking Goals
Mac blocking goals use natural language AI interpretation rather than app selection. You describe what you want blocked in plain English, and the AI enforces those rules.
Important: Always use absolute clock times (like "from 9am to 5pm"), never relative durations (like "for 1 hour").
1. Time-based Blocking (Most Common)
Block apps or websites during specific hours:
- "Block Reddit from 9am to 5pm"
- "No YouTube from 2pm to 6pm"
- "Block news sites from 5pm to 6pm"
- "Block all social media until 6pm"
2. Usage-based Blocking
Allow limited time per day, then block:
- "Allow 30 minutes of Twitter then block"
- "Allow 1 hour of gaming then block"
3. Productivity-based Blocking
Block until productive work is completed:
- "No social media until 5 hours on Xcode"
- "Block YouTube until 2 hours in VS Code"
- "No Reddit until 3 hours of coding"
4. Context-based Blocking
Allow based on content type:
- "Allow YouTube if it is for coding, otherwise block"
- "Block Reddit unless browsing programming subreddits"
5. Advanced Blocking
Combine multiple conditions:
- "Block all social media until 4 hours in Xcode, then allow 30 minutes"
- "No YouTube before 2pm unless it is coding tutorials"
Creating Mac Blocking Goals
Unlike iOS/Android screen blocking where you select specific apps, Mac blocking goals work through natural language descriptions. The Mac Monitor AI reads your goal description and interprets the blocking rules automatically.
You can create Mac blocking goals:
- Through chat by asking Overlord to create a blocking goal
- Using the goal creation flow (+ button in the app)
To change blocking rules: Simply edit the goal description through chat or in the goal settings. The Mac Monitor re-reads the description immediately and updates the blocking rules.
Requesting Temporary Access
You can ask Overlord for temporary access to blocked apps or websites via chat. For example: "I need YouTube for 10 minutes for homework research"
How it works:
- Overlord evaluates your request against your strictness settings
- If approved, Overlord adds a temporary time-window exception to your goal description
- The Mac Monitor sees the exception and grants access during that exact time window
- After the time window ends, blocking automatically resumes
Example: If you ask for 15 minutes of YouTube access at 2:30pm, Overlord will add "Allow YouTube between 2:30pm and 2:45pm" to your goal description. The Mac Monitor will allow access during that exact window, regardless of how much time you've already used today.
Note: You should manually remove the break exception from your goal description after it ends, or it will persist for future days.
Pomodoro Sessions
You can start pomodoro sessions directly from the Mac Monitor app. When a pomodoro ends, you'll receive a detailed summary showing:
- Total session duration
- Inactivity time during the session
- Apps and websites visited during the session
Proactive Messages
Overlord analyzes your Mac activity patterns and proactively suggests goals or improvements based on what it observes:
Example Goals
"Block YouTube from 9am-5pm"
"Block YouTube until I've gone to the gym"
"Block YouTube all day except for 1 minute unblocks when I ask"
"Block YouTube every weekday unless it's productive (coding, biology, or history related)"
"When I enter the library, ask if I want to lock in for 2 hours"
"Block YouTube when in the library"
"If I go on social media during work hours, bug me until I get off"
"If I go on Reddit, charge me $1 per 10 minutes, and tell me every time I get charged"
"Block all entertainment sites if Google Docs is open"
"If my focus score drops below 60% (calculate this based on proportion of time on Cursor vs everything else), block everything"
"Block Twitter from 9am–5pm, but allow 3 minutes per hour"
"Unblock Youtube only once I've sent a timelapse of me meditating for 10 mins"
"Let me watch Netflix only once all my goals are completed for the day"
"Block YouTube, but let me override by typing out random words"
"When I enter the office, block all social media for the next 3hrs"
Apple Health
Your Apple Health data for today is automatically loaded into Overlord's context. This means Overlord passively knows your steps, workouts, sleep, heart rate, and other metrics without you doing anything. You can also manually send updated data whenever you want - like right after finishing a run.
This lets you create goals that depend on health data: block apps until you hit 10k steps, charge money if you don't sleep 7 hours, or require a workout before unlocking social media.
Example Goals
- "Complete 5000 steps by 10am or lose $5"
- "Only unblock TikTok once I hit 170BPM"
- "Sleep at least 7 hours or pay $10"
- "Close all three activity rings daily"
- "Complete 30 minutes of exercise by 6pm"
How to Connect
- Click "Connect Health Data"
- Enable permissions in the Apple Health prompt
- Click "Manage Data Types" and select up to 5 data types you want to track
Note: You can currently only select up to 5 data types to send to Overlord. This is to keep the prompt from getting too large.
Manual data updates: You can also manually send Apple Health data to Overlord at any time by choosing from your recent activities. Useful for immediately updating Overlord after completing a workout or activity.
What You Can Track
Available data types (select up to 5):
- Steps
- Heart rate
- Active energy burned
- Sleep details
- Nutrition summary
- Mindfulness
- Water intake
- Weight
- Body fat percentage
- Distance walking/running
- Flights climbed
- Workouts
Note: Overlord only sees today's data, not historical data. You can manually send updated data at any time.
Apple Shortcuts
Apple Shortcuts allows you to create powerful automations that notify Overlord when specific events happen on your iPhone. These automations can trigger messages to Overlord, allowing it to track behaviors and verify goal completion automatically.
Most Useful Shortcut Triggers
The most useful shortcut triggers for Overlord integration include:
- Entering/leaving a GPS location - Overlord doesn't have dedicated GPS yet, but you can use this to notify when you arrive at the gym, leave home, etc.
Example: "If I enter the gym make sure I tell you I went to failure or text my mum I'm a wimp" - Alarm events - When alarms go off, are snoozed, or are stopped (useful for tracking when you've woken up or if you're snoozing alarms)
Example: "Charge me $5 if I snooze my alarm" - Emails from certain people - Trigger actions when you receive emails from specific contacts
Example: "If I get an email from my boss, call me every hour until it's answered" - Messages from certain people - Respond to messages from specific contacts
Example: "If I get a message from my ex, make sure I send a screenshot the next day with no response or lose $50" - Joining a certain WiFi - Know when you've arrived at work, home, or other locations
Example: "If I connect to work WiFi, ensure I send a photo of my desk clutter-free within 5 mins" - NFC tag taps - Tap an NFC tag to verify physical location or task completion
Example: "Make sure I'm in my car by 7am (I'll tap my car NFC tag)" - App opens - Track when certain apps are opened
Example: "When I open iMessage between 5am-11am, it means I've woken up. Then start my morning routine..." - Apple Pay transactions - Monitor purchases (you can filter by specific merchants and categories)
Example: "I must not spend any money in fast food places"
How It Works
Create a shortcut in the Apple Shortcuts app that sends a message to Overlord when triggered. Overlord can then use this information to verify goal completion, track habits, or apply consequences.
How to Set Up
Step 1: Create Shortcut and Search for Forfeit
Click create shortcut, then search for Forfeit.
Step 2: Select Enter/Depart or Custom Message
Select either enter/depart or custom message. For this example, it's to tell Overlord when I open iMessage.
Step 3: Create an Automation
Create a new automation and select your desired trigger. For this, I'll do when iMessage is opened.
Step 4: Configure Automation Settings
Select when app is opened, then select run immediately, without confirmation (optional to disable notify when run).
Step 5: Select Your Shortcut
This is where you choose what shortcut to be triggered when the automation happens. Click on the shortcut you made on step 1-2.
Step 6: Test Your Shortcut
Click on the shortcut to test it. This should send a message to Overlord that you should see.
Google Fit
Connect Google Fit to Overlord for automatic health and fitness data synchronization on Android. Overlord monitors your steps, workouts, active minutes, sleep, heart rate, and other fitness metrics throughout the day. Perfect for Android users who want automated verification of exercise goals, activity targets, and healthy habits without manual tracking.
Unlike manual fitness logging or passive tracking apps, Overlord actively verifies your Google Fit data against your commitments and applies real consequences when targets are missed. Your fitness data automatically flows from Google Fit to Overlord, eliminating the need to submit evidence photos or manual updates.
Example Goals
- "Hit 10,000 steps daily or lose $5"
- "Complete 3 workouts per week"
- "Track 8 hours of sleep minimum"
- "Burn 500 calories through activity daily"
- "Maintain active minutes goal of 60 per day"
How to Connect
- Click Settings
- Click Integrations
- Click "Connect Google Fit"
- Sign in with your Google account
- Grant permissions for the data types you want to track
What You Can Track
Available data types:
- Steps
- Heart rate
- Active energy burned
- Sleep details
- Nutrition summary
- Mindfulness
- Water intake
- Weight
- Body fat percentage
- Distance walking/running
- Flights climbed
- Workouts
IFTTT
IFTTT (If This Then That) is a service that connects over 600 apps and smart devices together. With Overlord's IFTTT integration, you can trigger accountability based on real-world events - like pressing a physical button, checking GitHub commits, or weighing yourself on a smart scale.
Visit ifttt.com/overlord_app to get started.
Example Goals
- If I don't do a GitHub commit each day then charge me $50
- If I press my Govee smart button then I must send a photo outside within 30 mins
- When I weigh myself on my Withings scale I must be on average 1lbs below my previous weight last week
How to Connect
- Visit ifttt.com and create a free account
- Search for "Overlord AI" service or head to ifttt.com/overlord_app
- Create an applet using our "Log Custom Event" action
- Connect your favorite triggers (location, time, buttons)
- When IFTTT triggers are triggered, it sends information to Overlord. These can be used to start routines, verify goals, and many other things
iMessage
Text Overlord at +1 (646) 327-3977. This is actual iMessage (blue messages, not green text).
Overlord via iMessage performs exactly the same as regular Overlord in the app - you can send photos, videos, create goals, and everything else. The only difference is that when you ask it to create goals, it creates them instantly without needing to click create then confirm.
How to Set Up
Step 1: Head to Settings Screen
Open the Overlord app and navigate to Settings.
Step 2: Add Your Phone Number
Scroll down to the calling/texting part and add your phone number. This is required for iMessage integration to work.
Step 3: Add Overlord to Contacts (Optional)
You can download the contact card or click "Add Overlord to Contacts" in settings - this saves Overlord as a contact with our icon as the contact photo.
Important: Setup Required
You must set up an account and add your phone number in the app settings before using iMessage integration. If you haven't added your phone number yet, you'll see this error:
Message Overlord at +1 (646) 327-3977 on WhatsApp. Same number as iMessage - it works on both.
WhatsApp uses the same system as iMessage, so replies automatically route to whichever platform you messaged from. If you text on WhatsApp, you get a reply on WhatsApp. If you text on iMessage, you get a reply on iMessage.
How to Set Up
- Add your phone number in the Overlord app settings
- Save +1 (646) 327-3977 as a contact, or click here to open in WhatsApp
- Send a message - you're connected
Notes
- You can use both iMessage and WhatsApp - they sync to the same chat history
- Photos and videos work just like on iMessage
- All the same features are available: goal creation, evidence submission, check-ins, etc.
Telegram
DM Overlord at @OverlordTelegramBot on Telegram. It works exactly the same as chatting in the app - you can create goals, submit evidence, get check-ins, and everything else.
Responses stream in real-time so you see the reply as it's being generated, just like in the web app.
How to Set Up
Option 1: During Onboarding
When Overlord asks about messaging preferences during setup, say you want Telegram. It will generate a link for you to open - click it and your Telegram account is connected automatically.
Option 2: Link an Existing Account
- Ask Overlord in the app or web chat: "I want to connect Telegram"
- Overlord will generate a link like
t.me/OverlordTelegramBot?start=CODE - Click the link to open Telegram and press Start
- Your account is now linked - you'll get a confirmation message
What You Can Do
- Chat with Overlord the same as in the app
- Receive proactive check-ins and reminders
- Submit text evidence for goals
- Create and manage goals
Notes
- Only private DMs are supported (not group chats)
- You can use both iMessage and Telegram at the same time
- Telegram notifications can be toggled on/off in app settings
Calendar (Google, Apple)
Connect your calendar to Overlord to turn scheduled events into accountability checkpoints. Overlord reads your calendar in real-time and can verify meeting attendance, enforce focus time blocks, ensure meeting preparation, and prevent schedule violations. Perfect for professionals who want to stop missing important meetings or need to protect their deep work time from distractions.
Unlike passive calendar reminders that you can ignore, Overlord actively monitors your calendar and applies real consequences when you miss commitments. It can automatically block distracting apps during focus blocks, charge you money for missed meetings, and track patterns of attendance over time.
Example Goals
- "Block all social media when events are on my calendar"
- "Make sure I get to class on time"
- "Send photos in Zoom rooms with 'Annie' within 2 mins of them starting"
- "GPS location should show at work before the event starts in the calendar or lose $5"
- "Lose $1 for every minute late to class"
How to Connect
- Click Settings
- Click Integrations
- Click "Connect Calendar" (this connects to your device calendar - Apple Calendar or Google Calendar)
- Select which calendars you want to include (Work, Personal, etc.)
Note: Overlord connects to your device's native calendar app, which syncs with Google Calendar, Apple Calendar, Outlook, or other calendar services you've configured on your phone.
What Overlord Can Access
Overlord has read-only access to your calendar and can see:
- Event names (e.g., "Team Standup", "Client Call with Acme Corp")
- Participants (who's invited to the meeting)
- Start and end times
Overlord cannot: Edit your calendar, create events, access event descriptions/notes, see email contents, access event locations, or see event status.
iOS Screen Time
Overlord is by far the best screen blocker out there. You can either define the goal yourself, or you can go through the setup flow. Overlord blocks your apps, and only unblocks under conditions that you define.
Simple Examples
- Unblock when I send a photo in the gym
- Unblock for however many pushups I do
- Unblock for 5 mins whenever I send a 50 min timelapse of me studying
Complex Blocking Rules
Overlord blocking goals can be extremely complex. Here's an example:
When Apps Will Be Blocked
You have several options for when your apps will be blocked:
- 24/7 - Apps are always blocked except during granted exceptions
- Certain hours - Block apps during specific times (e.g., 9am-5pm workdays). You can also set time limits within these hours.
- Number of opens - Allow a certain number of opens instead of minutes (e.g., allow 30 opens of Instagram instead of 30 minutes)
Setup Guide
Step 1: Enable Screen Time Permissions
Click on "Create Goal", click on "iOS Block", then click "Continue" to enable Screen Time permissions.
Step 2: Select Time Limit Goals
Pick the times when you're allowed to use the apps. You can pick to have no "allowed hours" and only be allowed exceptions.
You can also choose to allow a certain number of unblocks instead of a certain number of minutes to unblock.
Step 3: Define Exceptions
Set up exceptions that will allow you to get access if completed. For example, require proof like a time-lapse video before granting exceptions. This prevents you from just asking for exceptions without accountability.
Step 4: Set Exception Duration
You can also just set a duration like 10 minutes for how long exceptions last when granted.
Step 5: Add Permission Penalty
When you disable Screen Time permissions, it tells Overlord. Overlord can then either take money off you, text accountability partners, or perform other actions when you disable Screen Time permissions. No other apps have this that I'm aware of.
Android Screen Blocking
Android screen blocking works the same as iOS Screen Time, with one key difference: apps are blocked 24/7 by default. There's no option to set certain hours - it's always-on blocking that only unblocks when you meet your conditions or request exceptions.
GPS & Location
Overlord can use your phone's GPS to automatically verify location-based goals, detect patterns, and trigger actions when you arrive at or leave specific places.
Saved Locations
Tell the agent about places that matter — your gym, office, home, a coffee shop — and it saves them with a geofence radius. You can create locations by chatting naturally:
Geofence Events
When you enter or leave a saved location, the app records it. The agent uses these events to:
- Auto-verify goals — "You were at PureGym from 6:02 PM to 7:15 PM. Goal approved."
- Detect patterns — "You haven't been to the gym since Tuesday."
- Send nudges — "You just got home. Don't forget your evening goal."
Distance Tracking
The agent can calculate distances between your current position and any saved location. Useful for goals like "be within 1km of the gym by 6pm."
Setup
Enable location tracking from the Integrations page in the app. The agent only receives data from locations you've explicitly saved — it doesn't track your position continuously. You can disable it at any time.
Appeals & Payments
Support & Refunds
You can always message our support team and we respond twice a day, 7 days a week. Overlord can make mistakes, so we make sure to have a human in the loop through our support chat.
Getting Refunds:
- Use the "Refund Appeal" button in chat to send your case to support for review
- You can also ask Overlord directly to refund charges if you believe it made a mistake
- Our support team reviews all refund appeals and processes legitimate ones quickly
Flexible Charging
Overlord can charge you whenever you want based on your specific rules. This allows for highly customized consequences that match your goals.
Example charging rules:
- "Charge me $1 for every 10 minutes I'm late out of bed"
- "Charge me $1 for every km under 5km I run each day, but let me earn it back by running extra on other days"
- "Charge me $5 per day if I don't hit my step goal, but refund $10 if I maintain a 7-day streak"
- "Charge me $2 for every social media app I unblock during work hours"
- "Charge me $10 if I miss bedtime, but only on weekdays"
- "Start with $1 charges, but double the amount each time I fail until I succeed"
Pricing
Overlord costs $12.99/month USD.
Smart Messages: Your subscription includes Smart Messages, which are messages powered by Claude Sonnet 4.5. These are expensive to send—we make no money if you use all of these messages. Most users never use all of them though. If you do run out, you can purchase another 300 messages for $10 USD.
Payment Security
- All payments processed securely through Stripe
- Support for credit cards and debit cards
- You can lock your payment method to prevent yourself from removing it mid-goal
- Removing your payment method disables monetary consequences - you can still use all the other features
Customising Overlord
Overlord adapts to your needs through powerful customization options. Configure how Overlord communicates with you, how strict it should be, and prevent yourself from weaseling out of your commitments.
Customize Overlord
Personalize how Overlord interacts with you. Edit your display name, give Overlord instructions on how it should talk to you (formal, casual, encouraging, strict), and adjust response length preferences to get concise updates or detailed explanations.
Notifications & Appeals
Control how often Overlord notifies you and adjust how strict it should be with appeals. You can customize appeal rules to match your weaknesses—for example, "Be generally lenient in the morning but require photo evidence since I'm lazy in the mornings" or "No excuses on weekdays but accept appeals on weekends."
Example configurations:
- "Always require evidence for morning goals since I'm groggy"
- "Be strict on weekdays but lenient on weekends"
- "Accept appeals only with video proof for gym goals"
- "Automatically approve if I submit within 15 minutes of deadline"
Smart Approvals & Lock
Designed for users who struggle with making excuses or backing out of commitments. Smart Approvals makes Overlord significantly more rigorous when evaluating appeals and granting exceptions—it applies stricter scrutiny to ensure you're not taking the easy way out. The Lock feature prevents you from weakening your settings when motivation fades, protecting you from sabotaging your own accountability in moments of weakness.
Ideal for when you know your future self will try to negotiate out of commitments.
Miscellaneous
AI Model Selection
You can choose between different AI models in settings. We currently support Claude Sonnet 4.5, GPT-5, and o3. Once Gemini 3.0 drops, this will be supported immediately - Gemini 3.0 is expected to make Overlord a lot smarter.
Screen Time Vault
The Screen Time Vault helps you enforce iOS Screen Time restrictions by making your passcode extremely difficult to access. Here's how it works:
Setup
- Set your unlock rules (e.g., "Only on Sundays 7-9pm" or "After 3 hours of work with screenshot proof")
- Overlord generates a random 4-digit passcode for your Screen Time
- The password is immediately hidden and stored securely
- You'll be shown a backup Apple ID (overlordpwlocker@gmail.com) to add as your Screen Time recovery email
- You then lock your iOS Screen Time with the generated passcode
How It Keeps You Accountable
- Random password: You can't predict or remember the 4-digit code
- Shared recovery email: You can't reset the password using your own email
- AI gatekeeper: To retrieve the password, you must submit an appeal with evidence
- Optional memory game: A distraction game helps you forget the passcode after seeing it
Retrieving Your Password
When you need access to your Screen Time settings:
- Tap "Request Password Access" in the app
- Submit an appeal explaining why you need access
- Provide photo evidence (screenshots, completed work, etc.)
- Overlord evaluates your appeal against your unlock rules
- If approved, the password is revealed
This creates friction that helps you stick to your screen time goals. Even when tempted to bypass your restrictions, the effort required to retrieve the password gives you time to reconsider.
Coming soon: We'll be including the ability to save and give back passwords in Overlord chat, making it even easier to lock away any password you want to keep yourself from accessing.
Discord Community
We have a very active Discord community where users share goals, tips, and accountability strategies. You will get the link in the app once you subscribe.
Model Context Protocol (MCP)
Connect an MCP-compatible AI app to selected Forfeit data and actions. You choose the permissions during authorization.
Open Settings, then MCP & API.
Use the remote MCP URL below.
Sign in and approve only the scopes you need.
Connect an AI app
- Open Settings in the Forfeit mobile app or Overlord webapp.
- Open MCP & API, then choose the AI app you want to connect.
- Add the following remote MCP server URL to that app:
https://mcp.forfeit.app/connect
- Sign in to Forfeit when the authorization page opens.
- Review the requested permissions and approve the connection.
The host must support remote Streamable HTTP MCP servers and OAuth. A client that only supports local stdio servers cannot connect directly.
Permissions
Every connection receives only the scopes shown during authorization. Adding a new permission later does not silently expand an existing connection. Reconnect to grant newly available scopes.
| Scope | What it allows |
|---|---|
goals:read | Read goal names, descriptions, deadlines, status, history, and appeal rules. |
goals:write | Create new commitments. It does not directly change or settle an existing goal. |
appeals:write | Submit check-ins, evidence, appeals, skips, edits, deletions, and holidays for Overlord to judge. |
schedule:write | Add, update, or remove reminders. It cannot alter goal judgment jobs or blocking jobs. |
data:read | Read connected chat, health, calendar, location, and saved-place data. |
chat:write | Run a message through a scoped Overlord turn that can use only this connection's allowed actions. |
offline_access | Keep the connection active with rotating refresh tokens. |
Tool reference
Browse the 22 allowed tools by capability, or filter by any documented term.
No tools match that filter.
Goals
Create commitments and inspect active or completed goal performance.
list_goals
List goals
List the goals this user is currently committed to. Call this first when the user talks about their goals: every other goal tool needs a goal_id and this is the only place to get one.
Usage guidance
Each goal returns:
- goal_id: pass this to every other goal tool.
- name, description: what the user committed to in general.
- today_description: present only when today's wording differs from description. When it is present it replaces description for today - it is what the user is judged against today. When absent, description applies.
- today_status: how today stands right now. One of: needsToSubmit (still owed), unverified (not yet actioned), pendingApproval or pendingAppeal (submitted, awaiting a decision), approved (done), failed (missed, and any penalty has run), doesntNeedToSubmit (a rest day on a flexible goal, nothing owed). Absent means there is no entry for today, so nothing is due.
- next_deadline: local time the current day is due, HH:MM.
- frequency: how often it repeats, in the user's own words.
- start_date, end_date, goal_type.
Money is at stake on these goals, so never tell the user something is done, safe or missed unless today_status says so.
Allowed arguments
arguments object.Restrictions and refusal conditions
- Returns only active, non-automation goals owned by the authenticated account.
- Malformed or deleting goals are omitted. The read never repairs or deletes data.
- today_status is authoritative for the user's local date. An absent status means no entry exists for today.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "list_goals",
"arguments": {}
}
}
Returns
Returns count and goals. Use each goal_id in subsequent goal actions.
get_goal_stats
Goal track record
Summarise how consistently one goal has been kept over the last N days: how often it was approved, the current and longest streak, and which weekdays it is missed most. Use it to answer 'how am I doing on this' with real numbers instead of an impression. Takes a goal_id from list_goals.
Allowed arguments
goal_idstringrequiredThe goal's unique ID.
daysintegeroptionalHow many days of history to analyze (default 30).
Accepted: Default: 30
Restrictions and refusal conditions
- goal_id must belong to the authenticated account and should come from list_goals.
- days is an integer lookback. The result includes only dates stored on that goal.
- No history is a valid empty result, not proof that the goal never existed.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "get_goal_stats",
"arguments": {
"goal_id": "GOAL_ID",
"days": 30
}
}
}
Returns
Returns approval counts, streaks, and weekday performance for the requested period.
get_goal_actions
Goal activity
List, day by day, what has already happened on one goal: the status each day ended on and any penalty that was carried out or attempted, such as a charge, text or email. Use it for questions like 'was I charged for Tuesday' or 'did that day ever get marked complete'. Takes a goal_id from list_goals.
Allowed arguments
goal_idstringrequiredThe goal's unique ID.
days_backintegeroptionalHow many days of history to check (default 7). Match this to the user's requested timeframe - e.g. if user asks about the last 30 days, use days_back=30.
Accepted: Default: 7
Restrictions and refusal conditions
- goal_id must resolve to a goal on the authenticated account.
- days_back is an integer and the default is 7.
- A failed charge attempt did not take money. A refunded or pendingRefund action is not a live charge.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "get_goal_actions",
"arguments": {
"goal_id": "GOAL_ID",
"days_back": 7
}
}
}
Returns
Returns dated statuses and recorded penalty actions for the goal.
get_expired_goals
Finished goals
List goals that finished in the last N days, with their final record. A goal that has ended no longer appears in list_goals, so use this when the user refers to a commitment that has already run its course or wants to start an old one again.
Allowed arguments
days_backintegeroptionalNumber of previous days to search. Values are clamped to 1 through 90.
Accepted: Minimum: 1. Maximum: 90. Default: 7
Restrictions and refusal conditions
- days_back is clamped to the inclusive range 1 through 90.
- Only goals that ended in the selected lookback are returned. Active goals remain in list_goals.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "get_expired_goals",
"arguments": {
"days_back": 30
}
}
}
Returns
Returns goals that ended within the requested lookback, capped at 90 days.
create_goal
Create a goal
Create a new commitment. Confirm the wording with the user before calling - the description is what they will be judged against, and a vague one produces unfair verdicts.
Usage guidance
frequency is plain language: 'Every day', 'Every weekday', 'Mon,Wed,Fri'. Omit end_date for an ongoing goal. start_date defaults to today.
evidence_type decides how the day is proved, and almost every kind can be created here:
- custom: a plain commitment, judged from the description alone. Nothing else needed.
- photo / camera / selfVerify: the user submits proof. Need deadline_time.
- timelapse, pomodoro, gpsWithin, gpsAvoid, appleHealth, googleFit: also need evidence_config.
Only screen blocking is refused, because the user has to pick apps on their own phone.
deadline_time is required for every type except custom. Without one the goal counts as already overdue and charges on the first check. Use '23:59' for end of day, never '00:00'.
put THE stake IN penalties, never IN THE DESCRIPTION. On an app-verified goal a stake written into the text is rejected outright, because only the penalties array is read - the user would believe money was on the line while nothing ever fired. (On a custom goal the description is the goal, so a deadline or proof type written there is enforced from the text.)
Two things you cannot guess, so do not try:
- GPS needs a place the user has already saved. Call list_locations and use its coordinates. Invented coordinates are refused. Only subVerificationType='gpsSimple' is available; legacy duration and time-window GPS settings are refused.
- Health goals only settle for STEPS, ACTIVE_ENERGY_BURNED, BASAL_ENERGY_BURNED, DISTANCE_WALKING_RUNNING, DISTANCE_DELTA, FLIGHTS_CLIMBED and WATER. Any other metric never resolves at all - the day hangs forever, neither passed nor failed.
If evidence_config is wrong the error names the required_keys and includes a working example. Fix it and call again rather than guessing.
GPS and pomodoro are enforced ON THE DEVICE and never settle server-side, so never tell the user one of those days will resolve itself if they do not open the app.
Creating the same goal twice is refused rather than duplicated, so if you are unsure whether it already exists, call list_goals rather than creating and hoping.
Allowed arguments
namestringrequiredShort nickname, 2-5 words, e.g. "Morning Workout".
descriptionstringrequiredWhat the user has to actually do, in their own words. This is what they are judged against, so a vague one produces unfair verdicts. never put the stake here for an app-verified type - money in the text is rejected, and the user would believe it was on the line while nothing fired. Use penalties instead.
frequencystringrequiredAccepted values: One-off, Every day, Every weekday, Every weekend, 1 through 6 days/week, Once a fortnight, Once a month, Twice a month, 10% through 100% average in 10% steps, or comma-separated weekday abbreviations such as Mon,Wed,Fri.
Accepted: Allowed: One-off, Every day, Every weekday, Every weekend, 1 day/week, 2 days/week, 3 days/week, 4 days/week, 5 days/week, 6 days/week, Once a fortnight, Once a month, Twice a month, 10% average through 100% average in 10% steps, Mon,Wed,Fri
start_datestringoptionalYYYY-MM-DD. Defaults to today.
Accepted: Format: YYYY-MM-DD
end_datestringoptionalYYYY-MM-DD. Omit for an ongoing goal.
Accepted: Format: YYYY-MM-DD
evidence_typestringoptionalHow the day is proved. "custom" is a plain commitment judged from the description and needs nothing else. Every other type REQUIRES deadline_time, and all but photo/camera/selfVerify also require evidence_config.
Accepted: Allowed: custom, photo, camera, selfVerify, timelapse, pomodoro, gpsWithin, gpsAvoid, appleHealth, googleFit
deadline_timestringoptional24-hour "HH:MM". required for every type except custom - without one the goal counts as already overdue and charges on the first check. Use "23:59" for end of day, never "00:00" (that is midnight at the START of the day).
Accepted: Format: HH:MM
evidence_configstringoptionalA JSON object encoded as a string. The required keys and values depend on evidence_type; see conditional_values.evidence_examples on this action.
penaltiesarray of objectoptionalWhat happens on failure. Each entry is an object with punishmentType "forfeit" (plus amount, whole currency units), "text" or "email" (plus recipients). Omit for no penalty.
penalties[].punishmentType stringrequiredforfeit charges the user; text and email notify someone.
Accepted: Allowed: forfeit, text, email
penalties[].amount numberoptionalThe charge amount in whole currency units. For example, use 1 for a $1 charge, not 100 cents. forfeit only.
penalties[].failureMessage stringoptionalSent to recipients on failure.
penalties[].recipients array of objectoptionalpenalties[].recipients[].name stringoptionalpenalties[].recipients[].phoneNumber stringoptionalE.164, e.g. +447700900000.
penalties[].recipients[].email stringoptionalGoal configuration matrix
Field mapping: send frequency. The accepted value is
stored in the Forfeit app as frequencyInstructions. Sending
frequencyInstructions directly is rejected as an unknown field.
Frequency values: One-off, Every day, Every weekday, Every weekend, 1 day/week, 2 days/week, 3 days/week, 4 days/week, 5 days/week, 6 days/week, Once a fortnight, Once a month, Twice a month, 10% average through 100% average in 10% steps, Mon,Wed,Fri.
- The request field is frequency; the app stores the accepted value as frequencyInstructions.
- One-off is one calendar day. If end_date is supplied it must equal start_date.
- Recurring goals with an end_date require end_date later than start_date.
- N days/week creates separately judged required days, not one end-of-week aggregate.
- Screen-blocking creation is unavailable through both public interfaces and must be completed in the app.
- In the app, screen-blocking cadence is limited to Every day, Every weekday, Every weekend or explicit specific days. N days/week, monthly, fortnightly, and percentage modes are unsupported.
Health metrics: ACTIVE_ENERGY_BURNED, BASAL_ENERGY_BURNED, DISTANCE_DELTA, DISTANCE_WALKING_RUNNING, FLIGHTS_CLIMBED, STEPS, WATER.
Comparison values: moreThan, lessThan. Radius units: m, ft.
GPS mode: gpsSimple is the only supported
subVerificationType. Duration and time-window GPS modes are not
available in the current app, so MCP and the Developer API refuse them.
evidence_config is a JSON object serialized into a string.
Use coordinates returned by list_locations; do not geocode or invent them.
Latitude must be -90 through 90, longitude -180 through 180,
and the radius must be greater than zero.
| evidence_type | Allowed evidence_config fields | evidence_config example |
|---|---|---|
photo | None | None |
camera | None | None |
selfVerify | None | None |
timelapse | timelapseDuration | {"timelapseDuration":10} |
gpsWithin | latitude, longitude, radiusMeters, radiusUnit, placeName, placeAddress, subVerificationType | {"latitude":40.7038,"longitude":-73.9903,"radiusMeters":100,"radiusUnit":"m","placeName":"Gold's Gym","placeAddress":"40 Water St, Brooklyn","subVerificationType":"gpsSimple"} |
gpsAvoid | latitude, longitude, radiusMeters, radiusUnit, placeName, subVerificationType | {"latitude":40.7038,"longitude":-73.9903,"radiusMeters":100,"radiusUnit":"m","placeName":"The Pub","subVerificationType":"gpsSimple"} |
pomodoro | durationMinutes, moreOrLessThan | {"durationMinutes":25,"moreOrLessThan":"moreThan"} |
appleHealth | healthDataType, measurementValue, moreOrLessThan | {"healthDataType":"STEPS","measurementValue":10000,"moreOrLessThan":"moreThan"} |
googleFit | healthDataType, measurementValue, moreOrLessThan | {"healthDataType":"STEPS","measurementValue":10000,"moreOrLessThan":"moreThan"} |
Penalty values: punishmentType is
forfeit, text, or email.
A forfeit requires amount >= 1 in whole currency units
and cannot exceed the account ceiling. Text requires at least one recipient
with an E.164 phoneNumber. Email requires at least one recipient
with email. failureMessage is optional.
Restrictions and refusal conditions
- The public argument is frequency. It is stored in the app as frequencyInstructions; sending frequencyInstructions itself is rejected as an unknown field.
- Frequency must use one canonical mode: One-off, Every day, Every weekday, Every weekend, 1 through 6 days/week, Once a fortnight, Once a month, Twice a month, 10% through 100% average in 10% steps, or comma-separated weekday abbreviations.
- Interval wording such as Every 3 days, Sundays, or Every Mon is not supported. Use a weekly count or an explicit day list.
- One-off requires end_date to equal start_date or be omitted. A recurring goal with end_date requires it to be later than start_date.
- N days/week means each required day is judged separately. End-of-week aggregate charging is not supported.
- Screen-blocking goals cannot be created through MCP or the Developer API at any frequency because app selection and permissions must be completed on the user's device.
- In the app, screen-blocking cadence is limited to Every day, Every weekday, Every weekend or explicit specific days. N days/week, monthly, fortnightly, and percentage modes are unsupported.
- Every app-verified evidence type requires deadline_time. Use 23:59 for end of day and never 00:00.
- timelapse, pomodoro, GPS, and health evidence require a valid evidence_config. GPS and pomodoro are completed on the device.
- GPS creation supports only subVerificationType gpsSimple. Duration and time-window GPS modes are not available in the current app and are refused.
- App-verified penalties belong in penalties, not description. Duplicate name and start-date combinations are refused.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "create_goal",
"arguments": {
"name": "Morning walk",
"description": "Walk at least 30 minutes before breakfast.",
"frequency": "Every day",
"start_date": "2026-10-01",
"evidence_type": "selfVerify",
"deadline_time": "09:00",
"penalties": []
}
}
}
Returns
Returns the created goal identifier and normalized configuration, or a structured refusal.
Appeals and evidence
Submit reviewed requests, provide evidence, and check their outcomes.
request_approval
Request approval for a missed day
Ask Overlord to approve a day the user missed, and reverse any charge for it. This SUBMITS a request - it does not approve anything. Overlord judges it against the user's own appeal instructions and can refuse.
Usage guidance
date must be the missed day as YYYY-MM-DD. user_message_verbatim must be the user's own words, unedited and untranslated - the decider weighs how the user actually put it, and paraphrasing it into a stronger case is a misrepresentation of them. Put your own framing in reason instead.
Do not submit speculatively. If the user has not asked for the day back, do not call this.
Allowed arguments
goal_idstringrequiredThe id of the goal this is about.
reasonstringrequiredYour own one-line factual statement of what is being requested. Do not put the user's words here.
user_message_verbatimstringrequiredCopy the user's own words EXACTLY as they wrote them. Never paraphrase, summarise, translate, correct, tidy or strengthen them, and never write this text yourself. If the user has not given a reason in their own words, ask them for one before calling this tool. This field is quoted to a reviewer as the user's statement, and it is weighed separately from your `reason`.
datestringrequiredThe missed day, as YYYY-MM-DD. It cannot be more than 14 days before today.
Accepted: Format: YYYY-MM-DD
Restrictions and refusal conditions
- This submits a request and never directly approves a goal or reverses money.
- date is required in YYYY-MM-DD format and must identify the missed day.
- The date can be today or up to 14 days earlier. A date exactly 14 days ago is accepted; older dates are refused.
- user_message_verbatim must be copied exactly from the user. Put the caller's summary in reason.
- The goal must belong to the authenticated account and cannot be an automation goal.
- The effective appeal rules can refuse the request or require evidence.
- The Developer API accepts up to three direct JPEG, PNG, or WebP attachments. MCP tool calls do not carry binary files.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "request_approval",
"arguments": {
"goal_id": "GOAL_ID",
"reason": "The factual basis for the request",
"user_message_verbatim": "The user's exact words",
"date": "2026-10-01"
}
}
}
Returns
Returns an immediate verdict or a submitted request_id. Submission is not approval.
request_skip
Request a skip
Ask for a day or a range of days to be skipped, so they are not owed and cannot be charged. Use it ahead of time - illness, travel, a genuine clash. This SUBMITS a request; Overlord decides.
Usage guidance
start_date and end_date are YYYY-MM-DD and may be the same day. user_message_verbatim must be the user's own words, unedited.
Allowed arguments
goal_idstringrequiredThe id of the goal this is about.
reasonstringrequiredYour own one-line factual statement of what is being requested. Do not put the user's words here.
user_message_verbatimstringrequiredCopy the user's own words EXACTLY as they wrote them. Never paraphrase, summarise, translate, correct, tidy or strengthen them, and never write this text yourself. If the user has not given a reason in their own words, ask them for one before calling this tool. This field is quoted to a reviewer as the user's statement, and it is weighed separately from your `reason`.
start_datestringrequiredFirst day to skip, as YYYY-MM-DD. It cannot be more than 14 days before today.
Accepted: Format: YYYY-MM-DD
end_datestringrequiredLast day to skip, as YYYY-MM-DD. Same as start_date for one day.
Accepted: Format: YYYY-MM-DD
Restrictions and refusal conditions
- This submits a request and never directly changes a goal day.
- start_date and end_date are required YYYY-MM-DD values; the range is inclusive and end_date cannot be earlier.
- The range cannot include a date more than 14 days before today. A start_date exactly 14 days ago is accepted.
- user_message_verbatim must be copied exactly from the user.
- Automation and screen-blocking goals cannot be skipped.
- The Developer API accepts up to three direct JPEG, PNG, or WebP attachments. MCP tool calls do not carry binary files.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "request_skip",
"arguments": {
"goal_id": "GOAL_ID",
"reason": "The factual basis for the request",
"user_message_verbatim": "The user's exact words",
"start_date": "2026-10-01",
"end_date": "2026-10-03"
}
}
}
Returns
Returns an immediate verdict or a submitted request_id. Submission is not approval.
request_edit
Request a change to a goal
Ask to change a goal: its wording, its deadline, its schedule, its stake, or its verification settings (health target, location and radius, focus session, timelapse duration). This SUBMITS a request and changes nothing directly - Overlord judges whether the change is reasonable, and refuses one that makes a commitment easier purely to avoid a penalty.
Usage guidance
Describe exactly what should change and to what, in reason. user_message_verbatim must be the user's own words.
Allowed arguments
goal_idstringrequiredThe id of the goal this is about.
reasonstringrequiredYour own one-line factual statement of what is being requested. Do not put the user's words here.
user_message_verbatimstringrequiredCopy the user's own words EXACTLY as they wrote them. Never paraphrase, summarise, translate, correct, tidy or strengthen them, and never write this text yourself. If the user has not given a reason in their own words, ask them for one before calling this tool. This field is quoted to a reviewer as the user's statement, and it is weighed separately from your `reason`.
changesobjectrequiredExactly one supported property. See Restrictions and refusal conditions.
changes.description stringoptionalNew wording of the goal.
changes.date_range objectoptionalchanges.date_range.start_date stringrequiredYYYY-MM-DD
Accepted: Format: YYYY-MM-DD
changes.date_range.end_date stringrequiredYYYY-MM-DD
Accepted: Format: YYYY-MM-DD
changes.amount numberoptionalNew amount staked in whole currency units. For example, use 1 for a $1 charge, not 100 cents.
Accepted: Minimum: 0
changes.frequency stringoptionalOne canonical cadence accepted by create_goal. The approved value is stored as frequencyInstructions.
Accepted: Allowed: One-off, Every day, Every weekday, Every weekend, 1 day/week, 2 days/week, 3 days/week, 4 days/week, 5 days/week, 6 days/week, Once a fortnight, Once a month, Twice a month, 10% average through 100% average in 10% steps, Mon,Wed,Fri
changes.deadline_time stringoptionalNew deadline as 24-hour HH:MM.
Accepted: Format: HH:MM
changes.evidence_type stringoptionalNew type of proof required. Screen blocking is not available here.
Accepted: Allowed: custom, photo, camera, selfVerify, timelapse, pomodoro, gpsWithin, gpsAvoid, appleHealth, googleFit
Restrictions and refusal conditions
- This submits a request and never directly edits the goal.
- Automation and screen-blocking goals cannot be edited.
- changes must contain exactly one of description, date_range, amount, frequency, deadline_time, or evidence_type.
- frequency uses the same canonical values as create_goal and is stored as frequencyInstructions after approval.
- deadline_time must be HH:MM. date_range needs two YYYY-MM-DD values and end_date cannot be earlier.
- amount must be a non-negative number. Empty replacement values are refused.
- user_message_verbatim must be copied exactly from the user.
- The Developer API accepts up to three direct JPEG, PNG, or WebP attachments. MCP tool calls do not carry binary files.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "request_edit",
"arguments": {
"goal_id": "GOAL_ID",
"reason": "The factual basis for the request",
"user_message_verbatim": "The user's exact words",
"changes": {
"deadline_time": "21:30"
}
}
}
}
Returns
Returns an immediate verdict or a submitted request_id. Exactly one change is accepted per request.
request_deletion
Request to end a goal
Ask to delete a goal entirely. This SUBMITS a request; Overlord decides, and will refuse one that reads as escaping a commitment the user is currently failing.
Usage guidance
Deleting a goal ends the commitment the user set up for themselves, so confirm they mean it before calling this. user_message_verbatim must be their own words.
Allowed arguments
goal_idstringrequiredThe id of the goal this is about.
reasonstringrequiredYour own one-line factual statement of what is being requested. Do not put the user's words here.
user_message_verbatimstringrequiredCopy the user's own words EXACTLY as they wrote them. Never paraphrase, summarise, translate, correct, tidy or strengthen them, and never write this text yourself. If the user has not given a reason in their own words, ask them for one before calling this tool. This field is quoted to a reviewer as the user's statement, and it is weighed separately from your `reason`.
Restrictions and refusal conditions
- This submits a request and never directly deletes a goal.
- The goal must belong to the authenticated account and cannot be an automation goal.
- user_message_verbatim must be copied exactly from the user.
- Overlord can refuse a deletion that conflicts with the user's appeal rules.
- The Developer API accepts up to three direct JPEG, PNG, or WebP attachments. MCP tool calls do not carry binary files.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "request_deletion",
"arguments": {
"goal_id": "GOAL_ID",
"reason": "The factual basis for the request",
"user_message_verbatim": "The user's exact words"
}
}
}
Returns
Returns an immediate verdict or a submitted request_id. The goal is not deleted merely because the call succeeded.
get_request_status
Check a request
Where a submitted request got to: submitted, approved, rejected or needs_evidence.
Usage guidance
A request_* tool answers 'submitted' when it went to a person for review, and an upload made from a link is decided somewhere you cannot see. Both leave you without an outcome. This is how you find one - so when a user asks, call it rather than inferring from what you filed.
'needs_evidence' means Overlord asked a question and has not refused. Relay what is needed; never report it as a rejection.
Allowed arguments
request_idstringrequiredThe request_id returned when the request was submitted.
Restrictions and refusal conditions
- request_id must come from a request made for the authenticated account.
- Unknown and cross-account identifiers both return not_found.
- needs_evidence is not a rejection. submitted means there is no verdict yet.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "get_request_status",
"arguments": {
"request_id": "REQUEST_ID"
}
}
}
Returns
Returns submitted, approved, rejected, needs_evidence, or not_found.
get_appeal_rules
Appeal rules
The rules an appeal will actually be judged against, which the user wrote themselves. Pass a goal_id for a goal that has its own rule, or omit it for the account default.
Usage guidance
Read this before submitting any request_* tool, and tell the user what their rules say rather than guessing. If the rules say something will be refused, say so instead of submitting and letting them spend the attempt.
Returns: appeal_instructions (the effective text), source ('goal' or 'account_default'), evidence_type, can_ask_for_evidence, and appeal_rules_locked - when that is true the user has deliberately frozen these rules against themselves and you must not offer to change them.
Allowed arguments
goal_idstringoptionalOptional goal identifier returned by list_goals. Omit it to read the account default rules.
Restrictions and refusal conditions
- Omit goal_id for the account default, or pass a goal owned by the authenticated account.
- A goal-level rule overrides the account default for that goal.
- When appeal_rules_locked is true, the caller must not offer to weaken or replace the rules.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "get_appeal_rules",
"arguments": {
"goal_id": "GOAL_ID"
}
}
}
Returns
Returns the effective rules, their source, evidence requirements, and lock state.
submit_evidence
Submit evidence
Submit evidence that you completed a goal today or on a given date.
Usage guidance
Unlike the request_* tools, this one is judged immediately and can approve the day and reverse a charge. Use it only when you are reporting what you actually completed.
what_you_did must contain your own account of what you did because the decider weighs your exact wording.
The Developer API can attach JPEG, PNG, WebP, or MP4 bytes directly as multipart media. MCP tool calls cannot carry binary files, so pass has_photo_or_video=true there to receive a forfeitapp:// handoff link. Camera, timelapse, GPS, pomodoro, and health verification must still be completed in the app because an uploaded file cannot replace device-captured proof. The result may require human review, so do not treat the submission as accepted until its status says approved. Call get_request_status to check.
Three outcomes. 'approved' means the day is done. 'rejected' means it was not accepted. 'needs_evidence' means Overlord is asking FOR SOMETHING MORE and has not refused - relay what is needed and never report it as a rejection.
Allowed arguments
goal_idstringrequiredGoal identifier returned by list_goals.
what_you_didstringrequiredThe user's own account of what they completed.
datestringoptionalTarget date as YYYY-MM-DD. Omit for today.
Accepted: Format: YYYY-MM-DD
has_photo_or_videobooleanoptionalFor MCP, true requests a phone handoff link. Developer API multipart calls attach media directly and may leave this false.
Accepted: Default: false
Restrictions and refusal conditions
- goal_id must identify a goal owned by the authenticated account. date is optional YYYY-MM-DD and defaults to today.
- custom and selfVerify goals can use the user's non-empty text account for immediate judgment.
- The Developer API accepts direct JPEG, PNG, WebP, or MP4 evidence as multipart form data, with at most three files and one video.
- Direct uploads can support ordinary custom, selfVerify, and photo evidence. Camera, timelapse, GPS, pomodoro, Apple Health, and Google Fit verification must still be completed in the app.
- MCP tool calls do not carry binary files. Set has_photo_or_video=true there to receive a phone handoff link.
- approved is final, rejected is a refusal, needs_evidence is a question, and pending_human_review has no verdict yet.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "submit_evidence",
"arguments": {
"goal_id": "GOAL_ID",
"what_you_did": "I completed the full 30-minute walk.",
"date": "2026-10-01",
"has_photo_or_video": false
}
}
}
Returns
Returns approved, rejected, needs_evidence, pending_human_review, already_approved, or an app handoff when device verification is required.
request_holiday
Request a holiday
Ask for a holiday across a date range, pausing every goal at once rather than one at a time. This SUBMITS a request; Overlord decides.
Usage guidance
Use this instead of several request_skip calls when the user is away. start_date and end_date are YYYY-MM-DD. user_message_verbatim must be the user's own words.
Allowed arguments
reasonstringrequiredYour own one-line factual statement of what is being requested. Do not put the user's words here.
user_message_verbatimstringrequiredCopy the user's own words EXACTLY as they wrote them. Never paraphrase, summarise, translate, correct, tidy or strengthen them, and never write this text yourself. If the user has not given a reason in their own words, ask them for one before calling this tool. This field is quoted to a reviewer as the user's statement, and it is weighed separately from your `reason`.
start_datestringrequiredFirst day of the holiday, as YYYY-MM-DD.
Accepted: Format: YYYY-MM-DD
end_datestringrequiredLast day of the holiday, as YYYY-MM-DD.
Accepted: Format: YYYY-MM-DD
Restrictions and refusal conditions
- This submits one account-wide request and never directly pauses goals.
- start_date and end_date are required YYYY-MM-DD values; the range is inclusive and end_date cannot be earlier.
- user_message_verbatim must be copied exactly from the user.
- The effective appeal rules can refuse the holiday request.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "request_holiday",
"arguments": {
"reason": "The factual basis for the request",
"user_message_verbatim": "The user's exact words",
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}
}
}
Returns
Returns an immediate verdict or a submitted request_id for an account-wide holiday request.
Connected data
Read the account data that the user explicitly granted to this connection.
get_chat_history
Conversation history
Look back at your earlier conversations with Overlord. Pass search_term to find where a topic was discussed, or days_back / hours_back to read a stretch in order. Use it to check what was actually said or agreed before relying on it.
Allowed arguments
days_backintegeroptionalHow many days back to look (default 1 = yesterday).
Accepted: Minimum: 0. Default: 1
limitintegeroptionalMax messages to return (default 30).
Accepted: Default: 30
search_termstringoptionalOptional - search for a topic or phrase across all message history using semantic search. When provided (and hours_back is not set), searches across ALL past messages. Leave empty to get chronological messages for a specific window.
Accepted: Default: ""
hours_backnumberoptionalOptional - retrieve just the last N HOURS of conversation (e.g. hours_back=3 = last 3 hours, may span into last night) instead of a whole calendar day. TAKES PRECEDENCE over days_back. Use this for tight recent recall - messages from earlier today that scrolled out of your live window - without pulling a full day.
Accepted: Minimum: 0. Default: 0
Restrictions and refusal conditions
- hours_back greater than zero takes precedence over days_back.
- search_term uses semantic all-history search only when hours_back is not set; otherwise it filters the bounded window.
- Media is excluded and action markup is reduced to non-executable historical notes.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "get_chat_history",
"arguments": {
"days_back": 1,
"limit": 30
}
}
}
Returns
Returns chronological or semantically matched Overlord messages.
get_location_history
Location history
List timestamped arrivals at and departures from the places the user has saved, over a date range of up to 14 days. Nothing comes back when location tracking was off or no place was visited - that means unknown, so say so rather than inferring where they were.
Allowed arguments
start_datestringoptionalFirst local date as YYYY-MM-DD. Defaults to today.
Accepted: Format: YYYY-MM-DD
end_datestringoptionalLast local date as YYYY-MM-DD. Maximum range is 14 days.
Accepted: Format: YYYY-MM-DD
Restrictions and refusal conditions
- Dates use YYYY-MM-DD in the user's timezone. A range is inclusive and limited to 14 days.
- end_date cannot be before start_date. Omitting both dates uses today.
- At most the 200 most recent matching events are returned. A truncated result says so explicitly.
- No events can also mean tracking was disabled, so absence is not proof of the user's location.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "get_location_history",
"arguments": {
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}
}
}
Returns
Returns recorded arrivals and departures. An empty result does not prove absence.
list_locations
Saved places
The places the user has saved, with coordinates, radius and whether they are there now.
Usage guidance
This is the only safe source of coordinates for a GPS goal. create_goal refuses any latitude and longitude that is not one of these, so never geocode an address or recall coordinates from memory - read the place from here and copy its values. If the place they want is not listed, say so and ask them to add it in the Forfeit app rather than approximating it.
Allowed arguments
arguments object.Restrictions and refusal conditions
- Returns only places saved by the authenticated user.
- Coordinates used for a GPS goal must be copied from this result. Invented or geocoded coordinates are refused.
- Distance and inside-state values reflect the most recent device observation and may not be live.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "list_locations",
"arguments": {}
}
}
Returns
Returns saved places, coordinates, radii, and current-presence state.
query_health_data
Health data
Read the health metrics the user's device has synced, such as steps, sleep and workouts, for a past date, a range of up to 14 days, or a time window inside a single day. start_date is required.
Allowed arguments
start_datestringoptionalFirst local date as YYYY-MM-DD. Provide this for a date range; omission without start_time returns no data.
Accepted: Format: YYYY-MM-DD
end_datestringoptionalOptional last local date as YYYY-MM-DD. Maximum range is 14 days.
Accepted: Format: YYYY-MM-DD
start_timestringoptionalOptional 24-hour start time within a single day.
Accepted: Format: HH:MM
end_timestringoptionalOptional 24-hour end time within a single day.
Accepted: Format: HH:MM
Restrictions and refusal conditions
- Use YYYY-MM-DD dates in the user's timezone. Date ranges are inclusive and limited to 14 days.
- A date range needs start_date; omission without a time window returns no historical data.
- start_time and end_time are optional HH:MM values for a single-day window.
- Only data already synced by the user's device can be returned.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "query_health_data",
"arguments": {
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}
}
}
Returns
Returns device-synced health samples or aggregates for the requested window.
query_calendar_data
Calendar events
Read the user's calendar events for a date or a range of up to 14 days, returned as start time, end time and title. Use it to see what a day actually looked like before discussing whether a goal was realistic that day.
Allowed arguments
start_datestringoptionalOptional first local date as YYYY-MM-DD. Defaults to today.
Accepted: Format: YYYY-MM-DD
end_datestringoptionalOptional last local date as YYYY-MM-DD. Maximum range is 14 days.
Accepted: Format: YYYY-MM-DD
Restrictions and refusal conditions
- Dates use YYYY-MM-DD in the user's timezone. Date ranges are inclusive and limited to 14 days.
- end_date cannot be before start_date. Omitting both dates uses today.
- Only events already synced from the connected calendar can be returned.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "query_calendar_data",
"arguments": {
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}
}
}
Returns
Returns connected calendar events for the requested date range.
Scheduling
Create and manage goal or general reminders.
add_reminder
Add a reminder
Add a reminder. Pass general=true for a check-in on the main chat that is not about one goal, or general=false with a goal_id from list_goals for a goal's own reminder.
Usage guidance
time is the user's local time as HH:MM. schedule defaults to 'daily'. For a location reminder pass schedule='when I arrive at <saved location>' or 'when I leave <saved location>' (a place from list_locations) and time='' - it fires from the phone when they get there, even with the app closed.
This only adds a nudge. It cannot change when a goal is judged or what it requires.
Allowed arguments
namestringrequiredShort reminder name. Use the same name to update or remove it.
timestringrequiredUser-local 24-hour time. Use an empty string only for a location trigger.
Accepted: Format: HH:MM
generalbooleanoptionalTrue for a main-chat reminder. False requires goal_id.
Accepted: Default: false
goal_idstringoptionalRequired when general is false. Obtain it from list_goals.
schedulestringoptionalUse daily or a location phrase such as 'when I arrive at Gym' using a saved place.
Accepted: Default: "daily"
Restrictions and refusal conditions
- general=true targets the main chat. general=false requires a goal_id from list_goals.
- Do not pass master_chat as goal_id; the general flag is the only supported way to select it.
- Clock reminders require an HH:MM time. A saved-place arrival or departure trigger uses time as an empty string.
- Only notification and Overlord check-in reminders can be created. Goal checks and screen-blocking jobs are protected.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "add_reminder",
"arguments": {
"name": "Evening reminder",
"time": "19:30",
"general": false,
"goal_id": "GOAL_ID",
"schedule": "daily"
}
}
}
Returns
Returns the created reminder result. Goal judgment and blocking schedule items cannot be created.
update_reminder
Change a reminder
Change a reminder's time, or turn it on or off with enabled=true/false. Name it as it appears in the goal's schedule from list_goals.
Usage guidance
The goal check - the item that decides the day after the deadline - cannot be changed here and the call will be refused. That is deliberate: it decides whether the user is judged, not when they are reminded.
Allowed arguments
namestringrequiredExisting reminder name exactly as returned by list_goals.
generalbooleanoptionalTrue for a main-chat reminder. False requires goal_id.
Accepted: Default: false
goal_idstringoptionalRequired when general is false. Obtain it from list_goals.
timestringoptionalOptional replacement user-local 24-hour time.
Accepted: Format: HH:MM
enabledbooleanoptionalOptional true or false. Supply time, enabled, or both.
Restrictions and refusal conditions
- Use the existing reminder name exactly and select the same general or goal scope it belongs to.
- Supply a new HH:MM time, enabled=true or false, or both. An empty update is refused.
- Only notification and Overlord check-in reminders can be changed. Goal checks and screen-blocking jobs are protected.
- A missing or unreadable reminder fails closed rather than guessing which item was intended.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "update_reminder",
"arguments": {
"name": "Evening reminder",
"general": false,
"goal_id": "GOAL_ID",
"time": "20:00",
"enabled": true
}
}
}
Returns
Returns the updated reminder result. Goal judgment and blocking schedule items cannot be changed.
remove_reminder
Remove a reminder
Delete a reminder, named as it appears in the goal's schedule from list_goals.
Usage guidance
The goal check cannot be removed here and the call will be refused. Removing reminders makes it easier for the user to miss a goal they are still judged on, so confirm they mean it.
Allowed arguments
namestringrequiredExisting reminder name exactly as returned by list_goals.
generalbooleanoptionalTrue for a main-chat reminder. False requires goal_id.
Accepted: Default: false
goal_idstringoptionalRequired when general is false. Obtain it from list_goals.
Restrictions and refusal conditions
- Use the existing reminder name exactly and select the same general or goal scope it belongs to.
- Only notification and Overlord check-in reminders can be removed. Goal checks and screen-blocking jobs are protected.
- A missing or unreadable reminder fails closed rather than deleting another item.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "remove_reminder",
"arguments": {
"name": "Evening reminder",
"general": false,
"goal_id": "GOAL_ID"
}
}
}
Returns
Returns the removal result. Goal judgment and blocking schedule items cannot be removed.
Messages
Upload photos or videos, then send them to Overlord with or without text.
create_message_upload
Add a photo or video
Get a secure upload URL for one photo or MP4 video. Call this once per file, submit a multipart form to the returned POST URL using every returned field, then pass the media_id to send_message.
Usage guidance
Accepted types are image/jpeg, image/png, image/webp, and video/mp4. The declared size must match the uploaded file and cannot exceed 15 MB. Upload URLs expire after 15 minutes and are bound to this account and connection. The file form field must come last.
Use this only to add media to a message to Overlord. It does not submit evidence or prove that a goal was completed.
Allowed arguments
content_typestringrequiredExact file type. Accepted values are image/jpeg, image/png, image/webp, and video/mp4.
Accepted: Allowed: image/jpeg, image/png, image/webp, video/mp4
size_bytesintegerrequiredExact file size in bytes. The maximum is 15728640 bytes (15 MB).
Accepted: Minimum: 1. Maximum: 15728640
Upload and send workflow
- Read the file's exact MIME type and byte size.
- Call
create_message_uploadonce for that file. - Build a multipart form with every returned
fieldsentry unchanged, then append the file field last. - POST the form to
upload_urlbefore it expires. - Pass
media_idtosend_message. Do not pass the upload URL.
const form = new FormData();
for (const [name, value] of Object.entries(upload.fields)) {
form.append(name, value);
}
form.append("file", file); // The file field must be last.
await fetch(upload.upload_url, {
method: upload.method,
body: form,
});
// After the upload succeeds:
// send_message({ message: "Here it is.", media_ids: [upload.media_id] })
Uploads are private and treated as unverified chat attachments. Media attached to a successful message is retained with that message. Abandoned uploads expire and are deleted.
Restrictions and refusal conditions
- content_type must be image/jpeg, image/png, image/webp, or video/mp4.
- size_bytes must be the exact file size from 1 through 15728640 bytes (15 MB).
- The upload URL expires after 15 minutes. Send every returned field as multipart form data, then append the file field last.
- The resulting media_id is bound to this account, API credential, grant, and API resource.
- Photos must decode as the declared JPEG, PNG, or WebP format and cannot exceed 40 megapixels.
- This upload flow is for MCP and JSON send_message calls. Developer API multipart requests can attach the bytes directly.
- A successfully sent attachment is retained with the chat message. Abandoned uploads are deleted after expiry.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "create_message_upload",
"arguments": {
"content_type": "image/jpeg",
"size_bytes": 248013
}
}
}
Returns
Returns a short-lived POST policy and media_id. Upload the exact file before calling send_message.
send_message
Message Overlord
Message Overlord and receive its response. Use it to share progress, explain what got in the way, ask a question, or send up to three prepared photos or MP4 videos.
Usage guidance
The Developer API can attach files directly as multipart media. MCP and JSON calls use create_message_upload once per file, then pass the returned media_ids here. You may send text, attachments, or both. Each media ID is single-use.
Your message arrives labelled with the connected app that relayed it. When relaying something you said, keep your wording intact.
This is not how a goal gets done. To claim you completed a goal, call submit_evidence - it runs the real check. To ask for a missed day, a skip or a change, call the request_* tools. Saying 'I finished my run' here is not a submission and must never be reported as one.
The reply you get back is what Overlord said, and an empty one is a real answer - it often has nothing to add. Never write a reply yourself.
If it comes back delivered with no reply, the answer was lost, not refused: it is in your Forfeit chat. Do not send it again, because Overlord may already have acted on it.
Allowed arguments
messagestringoptionalThe text to send. It may be empty when media_ids contains at least one uploaded photo or video. Maximum 2000 characters.
Accepted: Maximum length: 2000
media_idsarray of stringoptionalUp to three media IDs returned by create_message_upload. Each ID is single-use and must belong to this connection.
Restrictions and refusal conditions
- Provide message text, one or more media_ids, or both. Text can contain up to 2000 characters after control markers are removed.
- media_ids accepts at most three unique, single-use IDs returned by create_message_upload for this connection.
- Each uploaded file must exist, match its declared byte size and content type, and still be within its 15-minute upload window.
- ACTION_JSON, SYSTEM MESSAGE, and NO_REPLY control markers are stripped and cannot be injected through this field.
- This sends a message to Overlord. It is not evidence, an appeal, or a goal-status change.
- If delivery succeeded but the reply was lost, do not retry because Overlord may already have acted.
- Overlord can use only actions granted to this API credential while handling the message.
- The Developer API can send the files directly as multipart media fields. MCP uses create_message_upload and media_ids instead.
MCP tool call example
{
"method": "tools/call",
"params": {
"name": "send_message",
"arguments": {
"message": "Here is the photo from today's workout.",
"media_ids": [
"media_RETURNED_BY_CREATE_MESSAGE_UPLOAD"
]
}
}
}
Returns
Returns delivery state and Overlord's response when one is available. A delivered request must not be blindly retried.
How requests are judged
Actions beginning with request_ submit a request to the same appeal system used by Forfeit. They do not directly change a goal. Overlord applies the user's appeal rules and may approve, refuse, or ask for more evidence. Use get_request_status when a decision is not immediate.
submit_evidence can be judged immediately. If a photo or video is required, the action returns a link that opens the Forfeit app so the user can capture it there.
Security
Each tool call is checked against the token's owner, audience, live grant, scope, revocation state, and usage budget. Connected apps cannot directly approve or fail goals, charge money, retrieve credentials, or bypass reviewed request flows.
Developer API
Build integrations with Forfeit's goal, evidence, schedule, and connected-data capabilities. The API is available at https://api.forfeit.app for verified Overlord Premium and Pro accounts.
Every request is limited to the permissions on its personal access token. Use the MCP integration when your AI app supports MCP directly.
What it can do
- Call the same 23 goal, data, appeal, reminder, evidence, and message actions available through MCP.
- Start an asynchronous AI request when a task needs Overlord's analysis rather than a single deterministic action.
- Poll or list AI requests and receive their final response and tool-call summary.
- Discover the actions and input schemas available to the current grant.
Quickstart
Open Settings > MCP & API > API. Enter a name, choose only the permissions your script needs, select an expiry, and create the token. You may be asked to sign in again before it is issued.
The complete token is shown once. Treat it like a password. Store it in your operating system's credential store or a secrets manager, never in source control, browser storage, logs, analytics, or a URL.
Replace YOUR_API_KEY with the token you just created, then run this command:
curl https://api.forfeit.app/v1/actions/list_goals \
-H "Authorization: Bearer YOUR_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"arguments":{}}'
The response contains goals owned by the account that created the token. A 401 means the token is missing, invalid, expired, or revoked. A 403 means it does not include goals:read.
Forfeit stores only a non-reversible verifier, not the bearer token. If you lose it, create a replacement and revoke the old token. Choose an expiry of 1, 3, 7, 30, 60, 90, or 365 days.
Authentication
Personal scripts authenticate with a personal access token in the HTTP Authorization header. Do not put the token in a query parameter. The token can act only as the account that created it and only through its selected permissions.
Authorization: Bearer forfeit_pat_YOUR_TOKEN
Personal access tokens do not refresh. Before one expires, create a replacement, update your secret store, verify the replacement, and revoke the old token. Every request also requires the account to remain enabled, verified, and subscribed to Pro. OAuth remains available for MCP and managed third-party integrations that need delegated sign-in for multiple users.
Endpoints
All Developer API requests run on https://api.forfeit.app.
| Method and path | Purpose |
|---|---|
GET /v1/capabilities | Lists the actions and input schemas allowed by the current token. |
POST /v1/actions/{action} | Runs one deterministic action from the shared MCP catalog. |
POST /v1/ai/requests | Queues a scoped AI analysis job and returns its identifier. |
GET /v1/ai/requests/{id} | Returns one owned job and its result when finished. |
GET /v1/ai/requests?limit=25&cursor=... | Lists recent jobs owned by this grant and returns a next cursor when another page exists. |
GET /ai.json | Returns the complete machine-readable API contract, including the x-forfeit-guide quickstart and the authoritative x-forfeit-actions schemas, restrictions, examples, and conditional values. |
Call an action
Most calls use a JSON body containing one arguments object. Actions that document direct media also accept multipart/form-data: put the same argument object in one JSON-encoded arguments field and add each file as a repeated media field. Let your HTTP library set the multipart boundary. First call GET /v1/capabilities to read the exact schema. Mutating actions also require an Idempotency-Key containing 8 to 200 URL-safe characters.
POST https://api.forfeit.app/v1/actions/request_approval
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Idempotency-Key: approval-2026-09-14-001
{
"arguments": {
"goal_id": "GOAL_ID",
"date": "2026-09-14",
"reason": "Why the request may satisfy the user's appeal rules",
"user_message_verbatim": "The user's exact words, copied without edits"
}
}
A completed action returns a stable action envelope:
{
"request_id": "IDEMPOTENCY_RECEIPT_ID",
"action": "request_approval",
"status": "succeeded",
"result": {
"status": "submitted",
"request_id": "REVIEW_REQUEST_ID"
}
}
succeeded means the requested API action completed. For a request_* action, it does not mean the underlying appeal was approved. Poll get_request_status when the result says it was submitted for review.
AI requests
Queue a durable, scoped Overlord analysis when one deterministic action is not enough. This endpoint requires chat:write because your request and Overlord's reply appear in your Overlord chat. The worker can use only the other actions granted to this client and rechecks the live grant before every tool call.
POST https://api.forfeit.app/v1/ai/requests
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
Idempotency-Key: review-2026-09-14-001
{
"message": "Review whether today's missed gym goal should be approved under my appeal rules."
}
The initial response contains a durable request identifier and status URL:
{
"id": "dapi_j_REQUEST_ID",
"status": "queued",
"created_at": 1789411200.0,
"updated_at": 1789411200.0,
"finished_at": null,
"result": null,
"error": null,
"status_url": "/v1/ai/requests/dapi_j_REQUEST_ID"
}
States are queued, running, succeeded, failed, and review_required. Never automatically retry review_required, because a mutation may already have started.
Action reference
This reference is generated from the same runtime catalog as GET /v1/capabilities. Browse by capability or filter by any documented term.
No actions match that filter.
Goals
Create commitments and inspect active or completed goal performance.
/v1/actions/list_goals
List goals
List the goals this user is currently committed to. Call this first when the user talks about their goals: every other goal tool needs a goal_id and this is the only place to get one.
Usage guidance
Each goal returns:
- goal_id: pass this to every other goal tool.
- name, description: what the user committed to in general.
- today_description: present only when today's wording differs from description. When it is present it replaces description for today - it is what the user is judged against today. When absent, description applies.
- today_status: how today stands right now. One of: needsToSubmit (still owed), unverified (not yet actioned), pendingApproval or pendingAppeal (submitted, awaiting a decision), approved (done), failed (missed, and any penalty has run), doesntNeedToSubmit (a rest day on a flexible goal, nothing owed). Absent means there is no entry for today, so nothing is due.
- next_deadline: local time the current day is due, HH:MM.
- frequency: how often it repeats, in the user's own words.
- start_date, end_date, goal_type.
Money is at stake on these goals, so never tell the user something is done, safe or missed unless today_status says so.
Allowed arguments
arguments object.Restrictions and refusal conditions
- Returns only active, non-automation goals owned by the authenticated account.
- Malformed or deleting goals are omitted. The read never repairs or deletes data.
- today_status is authoritative for the user's local date. An absent status means no entry exists for today.
Request example
curl https://api.forfeit.app/v1/actions/list_goals \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"arguments": {}
}'const response = await fetch("https://api.forfeit.app/v1/actions/list_goals", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
"arguments": {}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/list_goals",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'},
json={"arguments": {}},
)
result = response.json()Returns
Returns count and goals. Use each goal_id in subsequent goal actions.
/v1/actions/get_goal_stats
Goal track record
Summarise how consistently one goal has been kept over the last N days: how often it was approved, the current and longest streak, and which weekdays it is missed most. Use it to answer 'how am I doing on this' with real numbers instead of an impression. Takes a goal_id from list_goals.
Allowed arguments
goal_idstringrequiredThe goal's unique ID.
daysintegeroptionalHow many days of history to analyze (default 30).
Accepted: Default: 30
Restrictions and refusal conditions
- goal_id must belong to the authenticated account and should come from list_goals.
- days is an integer lookback. The result includes only dates stored on that goal.
- No history is a valid empty result, not proof that the goal never existed.
Request example
curl https://api.forfeit.app/v1/actions/get_goal_stats \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"arguments": {
"goal_id": "GOAL_ID",
"days": 30
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/get_goal_stats", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
"arguments": {
"goal_id": "GOAL_ID",
"days": 30
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/get_goal_stats",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'},
json={"arguments": {'goal_id': 'GOAL_ID', 'days': 30}},
)
result = response.json()Returns
Returns approval counts, streaks, and weekday performance for the requested period.
/v1/actions/get_goal_actions
Goal activity
List, day by day, what has already happened on one goal: the status each day ended on and any penalty that was carried out or attempted, such as a charge, text or email. Use it for questions like 'was I charged for Tuesday' or 'did that day ever get marked complete'. Takes a goal_id from list_goals.
Allowed arguments
goal_idstringrequiredThe goal's unique ID.
days_backintegeroptionalHow many days of history to check (default 7). Match this to the user's requested timeframe - e.g. if user asks about the last 30 days, use days_back=30.
Accepted: Default: 7
Restrictions and refusal conditions
- goal_id must resolve to a goal on the authenticated account.
- days_back is an integer and the default is 7.
- A failed charge attempt did not take money. A refunded or pendingRefund action is not a live charge.
Request example
curl https://api.forfeit.app/v1/actions/get_goal_actions \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"arguments": {
"goal_id": "GOAL_ID",
"days_back": 7
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/get_goal_actions", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
"arguments": {
"goal_id": "GOAL_ID",
"days_back": 7
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/get_goal_actions",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'},
json={"arguments": {'goal_id': 'GOAL_ID', 'days_back': 7}},
)
result = response.json()Returns
Returns dated statuses and recorded penalty actions for the goal.
/v1/actions/get_expired_goals
Finished goals
List goals that finished in the last N days, with their final record. A goal that has ended no longer appears in list_goals, so use this when the user refers to a commitment that has already run its course or wants to start an old one again.
Allowed arguments
days_backintegeroptionalNumber of previous days to search. Values are clamped to 1 through 90.
Accepted: Minimum: 1. Maximum: 90. Default: 7
Restrictions and refusal conditions
- days_back is clamped to the inclusive range 1 through 90.
- Only goals that ended in the selected lookback are returned. Active goals remain in list_goals.
Request example
curl https://api.forfeit.app/v1/actions/get_expired_goals \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"arguments": {
"days_back": 30
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/get_expired_goals", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
"arguments": {
"days_back": 30
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/get_expired_goals",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'},
json={"arguments": {'days_back': 30}},
)
result = response.json()Returns
Returns goals that ended within the requested lookback, capped at 90 days.
/v1/actions/create_goal
Create a goal
Create a new commitment. Confirm the wording with the user before calling - the description is what they will be judged against, and a vague one produces unfair verdicts.
Usage guidance
frequency is plain language: 'Every day', 'Every weekday', 'Mon,Wed,Fri'. Omit end_date for an ongoing goal. start_date defaults to today.
evidence_type decides how the day is proved, and almost every kind can be created here:
- custom: a plain commitment, judged from the description alone. Nothing else needed.
- photo / camera / selfVerify: the user submits proof. Need deadline_time.
- timelapse, pomodoro, gpsWithin, gpsAvoid, appleHealth, googleFit: also need evidence_config.
Only screen blocking is refused, because the user has to pick apps on their own phone.
deadline_time is required for every type except custom. Without one the goal counts as already overdue and charges on the first check. Use '23:59' for end of day, never '00:00'.
put THE stake IN penalties, never IN THE DESCRIPTION. On an app-verified goal a stake written into the text is rejected outright, because only the penalties array is read - the user would believe money was on the line while nothing ever fired. (On a custom goal the description is the goal, so a deadline or proof type written there is enforced from the text.)
Two things you cannot guess, so do not try:
- GPS needs a place the user has already saved. Call list_locations and use its coordinates. Invented coordinates are refused. Only subVerificationType='gpsSimple' is available; legacy duration and time-window GPS settings are refused.
- Health goals only settle for STEPS, ACTIVE_ENERGY_BURNED, BASAL_ENERGY_BURNED, DISTANCE_WALKING_RUNNING, DISTANCE_DELTA, FLIGHTS_CLIMBED and WATER. Any other metric never resolves at all - the day hangs forever, neither passed nor failed.
If evidence_config is wrong the error names the required_keys and includes a working example. Fix it and call again rather than guessing.
GPS and pomodoro are enforced ON THE DEVICE and never settle server-side, so never tell the user one of those days will resolve itself if they do not open the app.
Creating the same goal twice is refused rather than duplicated, so if you are unsure whether it already exists, call list_goals rather than creating and hoping.
Allowed arguments
namestringrequiredShort nickname, 2-5 words, e.g. "Morning Workout".
descriptionstringrequiredWhat the user has to actually do, in their own words. This is what they are judged against, so a vague one produces unfair verdicts. never put the stake here for an app-verified type - money in the text is rejected, and the user would believe it was on the line while nothing fired. Use penalties instead.
frequencystringrequiredAccepted values: One-off, Every day, Every weekday, Every weekend, 1 through 6 days/week, Once a fortnight, Once a month, Twice a month, 10% through 100% average in 10% steps, or comma-separated weekday abbreviations such as Mon,Wed,Fri.
Accepted: Allowed: One-off, Every day, Every weekday, Every weekend, 1 day/week, 2 days/week, 3 days/week, 4 days/week, 5 days/week, 6 days/week, Once a fortnight, Once a month, Twice a month, 10% average through 100% average in 10% steps, Mon,Wed,Fri
start_datestringoptionalYYYY-MM-DD. Defaults to today.
Accepted: Format: YYYY-MM-DD
end_datestringoptionalYYYY-MM-DD. Omit for an ongoing goal.
Accepted: Format: YYYY-MM-DD
evidence_typestringoptionalHow the day is proved. "custom" is a plain commitment judged from the description and needs nothing else. Every other type REQUIRES deadline_time, and all but photo/camera/selfVerify also require evidence_config.
Accepted: Allowed: custom, photo, camera, selfVerify, timelapse, pomodoro, gpsWithin, gpsAvoid, appleHealth, googleFit
deadline_timestringoptional24-hour "HH:MM". required for every type except custom - without one the goal counts as already overdue and charges on the first check. Use "23:59" for end of day, never "00:00" (that is midnight at the START of the day).
Accepted: Format: HH:MM
evidence_configstringoptionalA JSON object encoded as a string. The required keys and values depend on evidence_type; see conditional_values.evidence_examples on this action.
penaltiesarray of objectoptionalWhat happens on failure. Each entry is an object with punishmentType "forfeit" (plus amount, whole currency units), "text" or "email" (plus recipients). Omit for no penalty.
penalties[].punishmentType stringrequiredforfeit charges the user; text and email notify someone.
Accepted: Allowed: forfeit, text, email
penalties[].amount numberoptionalThe charge amount in whole currency units. For example, use 1 for a $1 charge, not 100 cents. forfeit only.
penalties[].failureMessage stringoptionalSent to recipients on failure.
penalties[].recipients array of objectoptionalpenalties[].recipients[].name stringoptionalpenalties[].recipients[].phoneNumber stringoptionalE.164, e.g. +447700900000.
penalties[].recipients[].email stringoptionalGoal configuration matrix
Field mapping: send frequency. The accepted value is
stored in the Forfeit app as frequencyInstructions. Sending
frequencyInstructions directly is rejected as an unknown field.
Frequency values: One-off, Every day, Every weekday, Every weekend, 1 day/week, 2 days/week, 3 days/week, 4 days/week, 5 days/week, 6 days/week, Once a fortnight, Once a month, Twice a month, 10% average through 100% average in 10% steps, Mon,Wed,Fri.
- The request field is frequency; the app stores the accepted value as frequencyInstructions.
- One-off is one calendar day. If end_date is supplied it must equal start_date.
- Recurring goals with an end_date require end_date later than start_date.
- N days/week creates separately judged required days, not one end-of-week aggregate.
- Screen-blocking creation is unavailable through both public interfaces and must be completed in the app.
- In the app, screen-blocking cadence is limited to Every day, Every weekday, Every weekend or explicit specific days. N days/week, monthly, fortnightly, and percentage modes are unsupported.
Health metrics: ACTIVE_ENERGY_BURNED, BASAL_ENERGY_BURNED, DISTANCE_DELTA, DISTANCE_WALKING_RUNNING, FLIGHTS_CLIMBED, STEPS, WATER.
Comparison values: moreThan, lessThan. Radius units: m, ft.
GPS mode: gpsSimple is the only supported
subVerificationType. Duration and time-window GPS modes are not
available in the current app, so MCP and the Developer API refuse them.
evidence_config is a JSON object serialized into a string.
Use coordinates returned by list_locations; do not geocode or invent them.
Latitude must be -90 through 90, longitude -180 through 180,
and the radius must be greater than zero.
| evidence_type | Allowed evidence_config fields | evidence_config example |
|---|---|---|
photo | None | None |
camera | None | None |
selfVerify | None | None |
timelapse | timelapseDuration | {"timelapseDuration":10} |
gpsWithin | latitude, longitude, radiusMeters, radiusUnit, placeName, placeAddress, subVerificationType | {"latitude":40.7038,"longitude":-73.9903,"radiusMeters":100,"radiusUnit":"m","placeName":"Gold's Gym","placeAddress":"40 Water St, Brooklyn","subVerificationType":"gpsSimple"} |
gpsAvoid | latitude, longitude, radiusMeters, radiusUnit, placeName, subVerificationType | {"latitude":40.7038,"longitude":-73.9903,"radiusMeters":100,"radiusUnit":"m","placeName":"The Pub","subVerificationType":"gpsSimple"} |
pomodoro | durationMinutes, moreOrLessThan | {"durationMinutes":25,"moreOrLessThan":"moreThan"} |
appleHealth | healthDataType, measurementValue, moreOrLessThan | {"healthDataType":"STEPS","measurementValue":10000,"moreOrLessThan":"moreThan"} |
googleFit | healthDataType, measurementValue, moreOrLessThan | {"healthDataType":"STEPS","measurementValue":10000,"moreOrLessThan":"moreThan"} |
Penalty values: punishmentType is
forfeit, text, or email.
A forfeit requires amount >= 1 in whole currency units
and cannot exceed the account ceiling. Text requires at least one recipient
with an E.164 phoneNumber. Email requires at least one recipient
with email. failureMessage is optional.
Restrictions and refusal conditions
- The public argument is frequency. It is stored in the app as frequencyInstructions; sending frequencyInstructions itself is rejected as an unknown field.
- Frequency must use one canonical mode: One-off, Every day, Every weekday, Every weekend, 1 through 6 days/week, Once a fortnight, Once a month, Twice a month, 10% through 100% average in 10% steps, or comma-separated weekday abbreviations.
- Interval wording such as Every 3 days, Sundays, or Every Mon is not supported. Use a weekly count or an explicit day list.
- One-off requires end_date to equal start_date or be omitted. A recurring goal with end_date requires it to be later than start_date.
- N days/week means each required day is judged separately. End-of-week aggregate charging is not supported.
- Screen-blocking goals cannot be created through MCP or the Developer API at any frequency because app selection and permissions must be completed on the user's device.
- In the app, screen-blocking cadence is limited to Every day, Every weekday, Every weekend or explicit specific days. N days/week, monthly, fortnightly, and percentage modes are unsupported.
- Every app-verified evidence type requires deadline_time. Use 23:59 for end of day and never 00:00.
- timelapse, pomodoro, GPS, and health evidence require a valid evidence_config. GPS and pomodoro are completed on the device.
- GPS creation supports only subVerificationType gpsSimple. Duration and time-window GPS modes are not available in the current app and are refused.
- App-verified penalties belong in penalties, not description. Duplicate name and start-date combinations are refused.
Request example
curl https://api.forfeit.app/v1/actions/create_goal \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: create_goal-UNIQUE_REQUEST_ID' \
--data '{
"arguments": {
"name": "Morning walk",
"description": "Walk at least 30 minutes before breakfast.",
"frequency": "Every day",
"start_date": "2026-10-01",
"evidence_type": "selfVerify",
"deadline_time": "09:00",
"penalties": []
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/create_goal", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
"Idempotency-Key": "create_goal-UNIQUE_REQUEST_ID",
},
body: JSON.stringify({
"arguments": {
"name": "Morning walk",
"description": "Walk at least 30 minutes before breakfast.",
"frequency": "Every day",
"start_date": "2026-10-01",
"evidence_type": "selfVerify",
"deadline_time": "09:00",
"penalties": []
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/create_goal",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
'Idempotency-Key': 'create_goal-UNIQUE_REQUEST_ID'},
json={"arguments": {'name': 'Morning walk',
'description': 'Walk at least 30 minutes before breakfast.',
'frequency': 'Every day',
'start_date': '2026-10-01',
'evidence_type': 'selfVerify',
'deadline_time': '09:00',
'penalties': []}},
)
result = response.json()Returns
Returns the created goal identifier and normalized configuration, or a structured refusal.
Appeals and evidence
Submit reviewed requests, provide evidence, and check their outcomes.
/v1/actions/request_approval
Request approval for a missed day
Ask Overlord to approve a day the user missed, and reverse any charge for it. This SUBMITS a request - it does not approve anything. Overlord judges it against the user's own appeal instructions and can refuse.
Usage guidance
date must be the missed day as YYYY-MM-DD. user_message_verbatim must be the user's own words, unedited and untranslated - the decider weighs how the user actually put it, and paraphrasing it into a stronger case is a misrepresentation of them. Put your own framing in reason instead.
Do not submit speculatively. If the user has not asked for the day back, do not call this.
Allowed arguments
goal_idstringrequiredThe id of the goal this is about.
reasonstringrequiredYour own one-line factual statement of what is being requested. Do not put the user's words here.
user_message_verbatimstringrequiredCopy the user's own words EXACTLY as they wrote them. Never paraphrase, summarise, translate, correct, tidy or strengthen them, and never write this text yourself. If the user has not given a reason in their own words, ask them for one before calling this tool. This field is quoted to a reviewer as the user's statement, and it is weighed separately from your `reason`.
datestringrequiredThe missed day, as YYYY-MM-DD. It cannot be more than 14 days before today.
Accepted: Format: YYYY-MM-DD
Attach up to three supporting images to the approval request. Send multipart/form-data
with one arguments field containing the same JSON object shown above, plus one
media field per file. Accepted types: image/jpeg, image/png, image/webp. Each file can be up to 15 MB;
the request can contain up to 3 files.
Do not send remote URLs, Firebase paths, base64 strings, or a caller-selected filename as storage identity. The API validates the bytes and stores accepted media in the account's private Firebase Storage area.
Restrictions and refusal conditions
- This submits a request and never directly approves a goal or reverses money.
- date is required in YYYY-MM-DD format and must identify the missed day.
- The date can be today or up to 14 days earlier. A date exactly 14 days ago is accepted; older dates are refused.
- user_message_verbatim must be copied exactly from the user. Put the caller's summary in reason.
- The goal must belong to the authenticated account and cannot be an automation goal.
- The effective appeal rules can refuse the request or require evidence.
- The Developer API accepts up to three direct JPEG, PNG, or WebP attachments. MCP tool calls do not carry binary files.
Request example
curl https://api.forfeit.app/v1/actions/request_approval \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Idempotency-Key: request_approval-UNIQUE_REQUEST_ID' \
-F 'arguments={"goal_id":"GOAL_ID","reason":"The factual basis for the request","user_message_verbatim":"The user's exact words","date":"2026-10-01"}' \
-F 'media=@evidence.jpg;type=image/jpeg'const form = new FormData();
form.append("arguments", "{\"goal_id\":\"GOAL_ID\",\"reason\":\"The factual basis for the request\",\"user_message_verbatim\":\"The user's exact words\",\"date\":\"2026-10-01\"}");
form.append("media", file);
const response = await fetch("https://api.forfeit.app/v1/actions/request_approval", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Idempotency-Key": "request_approval-UNIQUE_REQUEST_ID",
},
body: form,
});
const result = await response.json();import json
import requests
with open("evidence.jpg", "rb") as media:
response = requests.post(
"https://api.forfeit.app/v1/actions/request_approval",
headers={
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"Idempotency-Key": "request_approval-UNIQUE_REQUEST_ID",
},
data={"arguments": json.dumps({'goal_id': 'GOAL_ID',
'reason': 'The factual basis for the request',
'user_message_verbatim': "The user's exact words",
'date': '2026-10-01'})},
files={"media": ("evidence.jpg", media, "image/jpeg")},
)
result = response.json()Returns
Returns an immediate verdict or a submitted request_id. Submission is not approval.
/v1/actions/request_skip
Request a skip
Ask for a day or a range of days to be skipped, so they are not owed and cannot be charged. Use it ahead of time - illness, travel, a genuine clash. This SUBMITS a request; Overlord decides.
Usage guidance
start_date and end_date are YYYY-MM-DD and may be the same day. user_message_verbatim must be the user's own words, unedited.
Allowed arguments
goal_idstringrequiredThe id of the goal this is about.
reasonstringrequiredYour own one-line factual statement of what is being requested. Do not put the user's words here.
user_message_verbatimstringrequiredCopy the user's own words EXACTLY as they wrote them. Never paraphrase, summarise, translate, correct, tidy or strengthen them, and never write this text yourself. If the user has not given a reason in their own words, ask them for one before calling this tool. This field is quoted to a reviewer as the user's statement, and it is weighed separately from your `reason`.
start_datestringrequiredFirst day to skip, as YYYY-MM-DD. It cannot be more than 14 days before today.
Accepted: Format: YYYY-MM-DD
end_datestringrequiredLast day to skip, as YYYY-MM-DD. Same as start_date for one day.
Accepted: Format: YYYY-MM-DD
Attach up to three supporting images to the skip request. Send multipart/form-data
with one arguments field containing the same JSON object shown above, plus one
media field per file. Accepted types: image/jpeg, image/png, image/webp. Each file can be up to 15 MB;
the request can contain up to 3 files.
Do not send remote URLs, Firebase paths, base64 strings, or a caller-selected filename as storage identity. The API validates the bytes and stores accepted media in the account's private Firebase Storage area.
Restrictions and refusal conditions
- This submits a request and never directly changes a goal day.
- start_date and end_date are required YYYY-MM-DD values; the range is inclusive and end_date cannot be earlier.
- The range cannot include a date more than 14 days before today. A start_date exactly 14 days ago is accepted.
- user_message_verbatim must be copied exactly from the user.
- Automation and screen-blocking goals cannot be skipped.
- The Developer API accepts up to three direct JPEG, PNG, or WebP attachments. MCP tool calls do not carry binary files.
Request example
curl https://api.forfeit.app/v1/actions/request_skip \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Idempotency-Key: request_skip-UNIQUE_REQUEST_ID' \
-F 'arguments={"goal_id":"GOAL_ID","reason":"The factual basis for the request","user_message_verbatim":"The user's exact words","start_date":"2026-10-01","end_date":"2026-10-03"}' \
-F 'media=@evidence.jpg;type=image/jpeg'const form = new FormData();
form.append("arguments", "{\"goal_id\":\"GOAL_ID\",\"reason\":\"The factual basis for the request\",\"user_message_verbatim\":\"The user's exact words\",\"start_date\":\"2026-10-01\",\"end_date\":\"2026-10-03\"}");
form.append("media", file);
const response = await fetch("https://api.forfeit.app/v1/actions/request_skip", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Idempotency-Key": "request_skip-UNIQUE_REQUEST_ID",
},
body: form,
});
const result = await response.json();import json
import requests
with open("evidence.jpg", "rb") as media:
response = requests.post(
"https://api.forfeit.app/v1/actions/request_skip",
headers={
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"Idempotency-Key": "request_skip-UNIQUE_REQUEST_ID",
},
data={"arguments": json.dumps({'goal_id': 'GOAL_ID',
'reason': 'The factual basis for the request',
'user_message_verbatim': "The user's exact words",
'start_date': '2026-10-01',
'end_date': '2026-10-03'})},
files={"media": ("evidence.jpg", media, "image/jpeg")},
)
result = response.json()Returns
Returns an immediate verdict or a submitted request_id. Submission is not approval.
/v1/actions/request_edit
Request a change to a goal
Ask to change a goal: its wording, its deadline, its schedule, its stake, or its verification settings (health target, location and radius, focus session, timelapse duration). This SUBMITS a request and changes nothing directly - Overlord judges whether the change is reasonable, and refuses one that makes a commitment easier purely to avoid a penalty.
Usage guidance
Describe exactly what should change and to what, in reason. user_message_verbatim must be the user's own words.
Allowed arguments
goal_idstringrequiredThe id of the goal this is about.
reasonstringrequiredYour own one-line factual statement of what is being requested. Do not put the user's words here.
user_message_verbatimstringrequiredCopy the user's own words EXACTLY as they wrote them. Never paraphrase, summarise, translate, correct, tidy or strengthen them, and never write this text yourself. If the user has not given a reason in their own words, ask them for one before calling this tool. This field is quoted to a reviewer as the user's statement, and it is weighed separately from your `reason`.
changesobjectrequiredExactly one supported property. See Restrictions and refusal conditions.
changes.description stringoptionalNew wording of the goal.
changes.date_range objectoptionalchanges.date_range.start_date stringrequiredYYYY-MM-DD
Accepted: Format: YYYY-MM-DD
changes.date_range.end_date stringrequiredYYYY-MM-DD
Accepted: Format: YYYY-MM-DD
changes.amount numberoptionalNew amount staked in whole currency units. For example, use 1 for a $1 charge, not 100 cents.
Accepted: Minimum: 0
changes.frequency stringoptionalOne canonical cadence accepted by create_goal. The approved value is stored as frequencyInstructions.
Accepted: Allowed: One-off, Every day, Every weekday, Every weekend, 1 day/week, 2 days/week, 3 days/week, 4 days/week, 5 days/week, 6 days/week, Once a fortnight, Once a month, Twice a month, 10% average through 100% average in 10% steps, Mon,Wed,Fri
changes.deadline_time stringoptionalNew deadline as 24-hour HH:MM.
Accepted: Format: HH:MM
changes.evidence_type stringoptionalNew type of proof required. Screen blocking is not available here.
Accepted: Allowed: custom, photo, camera, selfVerify, timelapse, pomodoro, gpsWithin, gpsAvoid, appleHealth, googleFit
Attach up to three supporting images to the edit request. Send multipart/form-data
with one arguments field containing the same JSON object shown above, plus one
media field per file. Accepted types: image/jpeg, image/png, image/webp. Each file can be up to 15 MB;
the request can contain up to 3 files.
Do not send remote URLs, Firebase paths, base64 strings, or a caller-selected filename as storage identity. The API validates the bytes and stores accepted media in the account's private Firebase Storage area.
Restrictions and refusal conditions
- This submits a request and never directly edits the goal.
- Automation and screen-blocking goals cannot be edited.
- changes must contain exactly one of description, date_range, amount, frequency, deadline_time, or evidence_type.
- frequency uses the same canonical values as create_goal and is stored as frequencyInstructions after approval.
- deadline_time must be HH:MM. date_range needs two YYYY-MM-DD values and end_date cannot be earlier.
- amount must be a non-negative number. Empty replacement values are refused.
- user_message_verbatim must be copied exactly from the user.
- The Developer API accepts up to three direct JPEG, PNG, or WebP attachments. MCP tool calls do not carry binary files.
Request example
curl https://api.forfeit.app/v1/actions/request_edit \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Idempotency-Key: request_edit-UNIQUE_REQUEST_ID' \
-F 'arguments={"goal_id":"GOAL_ID","reason":"The factual basis for the request","user_message_verbatim":"The user's exact words","changes":{"deadline_time":"21:30"}}' \
-F 'media=@evidence.jpg;type=image/jpeg'const form = new FormData();
form.append("arguments", "{\"goal_id\":\"GOAL_ID\",\"reason\":\"The factual basis for the request\",\"user_message_verbatim\":\"The user's exact words\",\"changes\":{\"deadline_time\":\"21:30\"}}");
form.append("media", file);
const response = await fetch("https://api.forfeit.app/v1/actions/request_edit", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Idempotency-Key": "request_edit-UNIQUE_REQUEST_ID",
},
body: form,
});
const result = await response.json();import json
import requests
with open("evidence.jpg", "rb") as media:
response = requests.post(
"https://api.forfeit.app/v1/actions/request_edit",
headers={
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"Idempotency-Key": "request_edit-UNIQUE_REQUEST_ID",
},
data={"arguments": json.dumps({'goal_id': 'GOAL_ID',
'reason': 'The factual basis for the request',
'user_message_verbatim': "The user's exact words",
'changes': {'deadline_time': '21:30'}})},
files={"media": ("evidence.jpg", media, "image/jpeg")},
)
result = response.json()Returns
Returns an immediate verdict or a submitted request_id. Exactly one change is accepted per request.
/v1/actions/request_deletion
Request to end a goal
Ask to delete a goal entirely. This SUBMITS a request; Overlord decides, and will refuse one that reads as escaping a commitment the user is currently failing.
Usage guidance
Deleting a goal ends the commitment the user set up for themselves, so confirm they mean it before calling this. user_message_verbatim must be their own words.
Allowed arguments
goal_idstringrequiredThe id of the goal this is about.
reasonstringrequiredYour own one-line factual statement of what is being requested. Do not put the user's words here.
user_message_verbatimstringrequiredCopy the user's own words EXACTLY as they wrote them. Never paraphrase, summarise, translate, correct, tidy or strengthen them, and never write this text yourself. If the user has not given a reason in their own words, ask them for one before calling this tool. This field is quoted to a reviewer as the user's statement, and it is weighed separately from your `reason`.
Attach up to three supporting images to the deletion request. Send multipart/form-data
with one arguments field containing the same JSON object shown above, plus one
media field per file. Accepted types: image/jpeg, image/png, image/webp. Each file can be up to 15 MB;
the request can contain up to 3 files.
Do not send remote URLs, Firebase paths, base64 strings, or a caller-selected filename as storage identity. The API validates the bytes and stores accepted media in the account's private Firebase Storage area.
Restrictions and refusal conditions
- This submits a request and never directly deletes a goal.
- The goal must belong to the authenticated account and cannot be an automation goal.
- user_message_verbatim must be copied exactly from the user.
- Overlord can refuse a deletion that conflicts with the user's appeal rules.
- The Developer API accepts up to three direct JPEG, PNG, or WebP attachments. MCP tool calls do not carry binary files.
Request example
curl https://api.forfeit.app/v1/actions/request_deletion \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Idempotency-Key: request_deletion-UNIQUE_REQUEST_ID' \
-F 'arguments={"goal_id":"GOAL_ID","reason":"The factual basis for the request","user_message_verbatim":"The user's exact words"}' \
-F 'media=@evidence.jpg;type=image/jpeg'const form = new FormData();
form.append("arguments", "{\"goal_id\":\"GOAL_ID\",\"reason\":\"The factual basis for the request\",\"user_message_verbatim\":\"The user's exact words\"}");
form.append("media", file);
const response = await fetch("https://api.forfeit.app/v1/actions/request_deletion", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Idempotency-Key": "request_deletion-UNIQUE_REQUEST_ID",
},
body: form,
});
const result = await response.json();import json
import requests
with open("evidence.jpg", "rb") as media:
response = requests.post(
"https://api.forfeit.app/v1/actions/request_deletion",
headers={
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"Idempotency-Key": "request_deletion-UNIQUE_REQUEST_ID",
},
data={"arguments": json.dumps({'goal_id': 'GOAL_ID',
'reason': 'The factual basis for the request',
'user_message_verbatim': "The user's exact words"})},
files={"media": ("evidence.jpg", media, "image/jpeg")},
)
result = response.json()Returns
Returns an immediate verdict or a submitted request_id. The goal is not deleted merely because the call succeeded.
/v1/actions/get_request_status
Check a request
Where a submitted request got to: submitted, approved, rejected or needs_evidence.
Usage guidance
A request_* tool answers 'submitted' when it went to a person for review, and an upload made from a link is decided somewhere you cannot see. Both leave you without an outcome. This is how you find one - so when a user asks, call it rather than inferring from what you filed.
'needs_evidence' means Overlord asked a question and has not refused. Relay what is needed; never report it as a rejection.
Allowed arguments
request_idstringrequiredThe request_id returned when the request was submitted.
Restrictions and refusal conditions
- request_id must come from a request made for the authenticated account.
- Unknown and cross-account identifiers both return not_found.
- needs_evidence is not a rejection. submitted means there is no verdict yet.
Request example
curl https://api.forfeit.app/v1/actions/get_request_status \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"arguments": {
"request_id": "REQUEST_ID"
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/get_request_status", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
"arguments": {
"request_id": "REQUEST_ID"
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/get_request_status",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'},
json={"arguments": {'request_id': 'REQUEST_ID'}},
)
result = response.json()Returns
Returns submitted, approved, rejected, needs_evidence, or not_found.
/v1/actions/get_appeal_rules
Appeal rules
The rules an appeal will actually be judged against, which the user wrote themselves. Pass a goal_id for a goal that has its own rule, or omit it for the account default.
Usage guidance
Read this before submitting any request_* tool, and tell the user what their rules say rather than guessing. If the rules say something will be refused, say so instead of submitting and letting them spend the attempt.
Returns: appeal_instructions (the effective text), source ('goal' or 'account_default'), evidence_type, can_ask_for_evidence, and appeal_rules_locked - when that is true the user has deliberately frozen these rules against themselves and you must not offer to change them.
Allowed arguments
goal_idstringoptionalOptional goal identifier returned by list_goals. Omit it to read the account default rules.
Restrictions and refusal conditions
- Omit goal_id for the account default, or pass a goal owned by the authenticated account.
- A goal-level rule overrides the account default for that goal.
- When appeal_rules_locked is true, the caller must not offer to weaken or replace the rules.
Request example
curl https://api.forfeit.app/v1/actions/get_appeal_rules \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"arguments": {
"goal_id": "GOAL_ID"
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/get_appeal_rules", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
"arguments": {
"goal_id": "GOAL_ID"
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/get_appeal_rules",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'},
json={"arguments": {'goal_id': 'GOAL_ID'}},
)
result = response.json()Returns
Returns the effective rules, their source, evidence requirements, and lock state.
/v1/actions/submit_evidence
Submit evidence
Submit evidence that you completed a goal today or on a given date.
Usage guidance
Unlike the request_* tools, this one is judged immediately and can approve the day and reverse a charge. Use it only when you are reporting what you actually completed.
what_you_did must contain your own account of what you did because the decider weighs your exact wording.
The Developer API can attach JPEG, PNG, WebP, or MP4 bytes directly as multipart media. MCP tool calls cannot carry binary files, so pass has_photo_or_video=true there to receive a forfeitapp:// handoff link. Camera, timelapse, GPS, pomodoro, and health verification must still be completed in the app because an uploaded file cannot replace device-captured proof. The result may require human review, so do not treat the submission as accepted until its status says approved. Call get_request_status to check.
Three outcomes. 'approved' means the day is done. 'rejected' means it was not accepted. 'needs_evidence' means Overlord is asking FOR SOMETHING MORE and has not refused - relay what is needed and never report it as a rejection.
Allowed arguments
goal_idstringrequiredGoal identifier returned by list_goals.
what_you_didstringrequiredThe user's own account of what they completed.
datestringoptionalTarget date as YYYY-MM-DD. Omit for today.
Accepted: Format: YYYY-MM-DD
has_photo_or_videobooleanoptionalFor MCP, true requests a phone handoff link. Developer API multipart calls attach media directly and may leave this false.
Accepted: Default: false
Attach photos or one MP4 video directly as evidence. Send multipart/form-data
with one arguments field containing the same JSON object shown above, plus one
media field per file. Accepted types: image/jpeg, image/png, image/webp, video/mp4. Each file can be up to 15 MB;
the request can contain up to 3 files. At most 1 attachment may be an MP4 video.
Do not send remote URLs, Firebase paths, base64 strings, or a caller-selected filename as storage identity. The API validates the bytes and stores accepted media in the account's private Firebase Storage area.
Restrictions and refusal conditions
- goal_id must identify a goal owned by the authenticated account. date is optional YYYY-MM-DD and defaults to today.
- custom and selfVerify goals can use the user's non-empty text account for immediate judgment.
- The Developer API accepts direct JPEG, PNG, WebP, or MP4 evidence as multipart form data, with at most three files and one video.
- Direct uploads can support ordinary custom, selfVerify, and photo evidence. Camera, timelapse, GPS, pomodoro, Apple Health, and Google Fit verification must still be completed in the app.
- MCP tool calls do not carry binary files. Set has_photo_or_video=true there to receive a phone handoff link.
- approved is final, rejected is a refusal, needs_evidence is a question, and pending_human_review has no verdict yet.
Request example
curl https://api.forfeit.app/v1/actions/submit_evidence \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Idempotency-Key: submit_evidence-UNIQUE_REQUEST_ID' \
-F 'arguments={"goal_id":"GOAL_ID","what_you_did":"I completed the full 30-minute walk.","date":"2026-10-01","has_photo_or_video":false}' \
-F 'media=@evidence.jpg;type=image/jpeg'const form = new FormData();
form.append("arguments", "{\"goal_id\":\"GOAL_ID\",\"what_you_did\":\"I completed the full 30-minute walk.\",\"date\":\"2026-10-01\",\"has_photo_or_video\":false}");
form.append("media", file);
const response = await fetch("https://api.forfeit.app/v1/actions/submit_evidence", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Idempotency-Key": "submit_evidence-UNIQUE_REQUEST_ID",
},
body: form,
});
const result = await response.json();import json
import requests
with open("evidence.jpg", "rb") as media:
response = requests.post(
"https://api.forfeit.app/v1/actions/submit_evidence",
headers={
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"Idempotency-Key": "submit_evidence-UNIQUE_REQUEST_ID",
},
data={"arguments": json.dumps({'goal_id': 'GOAL_ID',
'what_you_did': 'I completed the full 30-minute walk.',
'date': '2026-10-01',
'has_photo_or_video': False})},
files={"media": ("evidence.jpg", media, "image/jpeg")},
)
result = response.json()Returns
Returns approved, rejected, needs_evidence, pending_human_review, already_approved, or an app handoff when device verification is required.
/v1/actions/request_holiday
Request a holiday
Ask for a holiday across a date range, pausing every goal at once rather than one at a time. This SUBMITS a request; Overlord decides.
Usage guidance
Use this instead of several request_skip calls when the user is away. start_date and end_date are YYYY-MM-DD. user_message_verbatim must be the user's own words.
Allowed arguments
reasonstringrequiredYour own one-line factual statement of what is being requested. Do not put the user's words here.
user_message_verbatimstringrequiredCopy the user's own words EXACTLY as they wrote them. Never paraphrase, summarise, translate, correct, tidy or strengthen them, and never write this text yourself. If the user has not given a reason in their own words, ask them for one before calling this tool. This field is quoted to a reviewer as the user's statement, and it is weighed separately from your `reason`.
start_datestringrequiredFirst day of the holiday, as YYYY-MM-DD.
Accepted: Format: YYYY-MM-DD
end_datestringrequiredLast day of the holiday, as YYYY-MM-DD.
Accepted: Format: YYYY-MM-DD
Restrictions and refusal conditions
- This submits one account-wide request and never directly pauses goals.
- start_date and end_date are required YYYY-MM-DD values; the range is inclusive and end_date cannot be earlier.
- user_message_verbatim must be copied exactly from the user.
- The effective appeal rules can refuse the holiday request.
Request example
curl https://api.forfeit.app/v1/actions/request_holiday \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: request_holiday-UNIQUE_REQUEST_ID' \
--data '{
"arguments": {
"reason": "The factual basis for the request",
"user_message_verbatim": "The user's exact words",
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/request_holiday", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
"Idempotency-Key": "request_holiday-UNIQUE_REQUEST_ID",
},
body: JSON.stringify({
"arguments": {
"reason": "The factual basis for the request",
"user_message_verbatim": "The user's exact words",
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/request_holiday",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
'Idempotency-Key': 'request_holiday-UNIQUE_REQUEST_ID'},
json={"arguments": {'reason': 'The factual basis for the request',
'user_message_verbatim': "The user's exact words",
'start_date': '2026-10-01',
'end_date': '2026-10-07'}},
)
result = response.json()Returns
Returns an immediate verdict or a submitted request_id for an account-wide holiday request.
Connected data
Read the account data that the user explicitly granted to this connection.
/v1/actions/get_chat_history
Conversation history
Look back at your earlier conversations with Overlord. Pass search_term to find where a topic was discussed, or days_back / hours_back to read a stretch in order. Use it to check what was actually said or agreed before relying on it.
Allowed arguments
days_backintegeroptionalHow many days back to look (default 1 = yesterday).
Accepted: Minimum: 0. Default: 1
limitintegeroptionalMax messages to return (default 30).
Accepted: Default: 30
search_termstringoptionalOptional - search for a topic or phrase across all message history using semantic search. When provided (and hours_back is not set), searches across ALL past messages. Leave empty to get chronological messages for a specific window.
Accepted: Default: ""
hours_backnumberoptionalOptional - retrieve just the last N HOURS of conversation (e.g. hours_back=3 = last 3 hours, may span into last night) instead of a whole calendar day. TAKES PRECEDENCE over days_back. Use this for tight recent recall - messages from earlier today that scrolled out of your live window - without pulling a full day.
Accepted: Minimum: 0. Default: 0
Restrictions and refusal conditions
- hours_back greater than zero takes precedence over days_back.
- search_term uses semantic all-history search only when hours_back is not set; otherwise it filters the bounded window.
- Media is excluded and action markup is reduced to non-executable historical notes.
Request example
curl https://api.forfeit.app/v1/actions/get_chat_history \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"arguments": {
"days_back": 1,
"limit": 30
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/get_chat_history", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
"arguments": {
"days_back": 1,
"limit": 30
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/get_chat_history",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'},
json={"arguments": {'days_back': 1, 'limit': 30}},
)
result = response.json()Returns
Returns chronological or semantically matched Overlord messages.
/v1/actions/get_location_history
Location history
List timestamped arrivals at and departures from the places the user has saved, over a date range of up to 14 days. Nothing comes back when location tracking was off or no place was visited - that means unknown, so say so rather than inferring where they were.
Allowed arguments
start_datestringoptionalFirst local date as YYYY-MM-DD. Defaults to today.
Accepted: Format: YYYY-MM-DD
end_datestringoptionalLast local date as YYYY-MM-DD. Maximum range is 14 days.
Accepted: Format: YYYY-MM-DD
Restrictions and refusal conditions
- Dates use YYYY-MM-DD in the user's timezone. A range is inclusive and limited to 14 days.
- end_date cannot be before start_date. Omitting both dates uses today.
- At most the 200 most recent matching events are returned. A truncated result says so explicitly.
- No events can also mean tracking was disabled, so absence is not proof of the user's location.
Request example
curl https://api.forfeit.app/v1/actions/get_location_history \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"arguments": {
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/get_location_history", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
"arguments": {
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/get_location_history",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'},
json={"arguments": {'start_date': '2026-10-01', 'end_date': '2026-10-07'}},
)
result = response.json()Returns
Returns recorded arrivals and departures. An empty result does not prove absence.
/v1/actions/list_locations
Saved places
The places the user has saved, with coordinates, radius and whether they are there now.
Usage guidance
This is the only safe source of coordinates for a GPS goal. create_goal refuses any latitude and longitude that is not one of these, so never geocode an address or recall coordinates from memory - read the place from here and copy its values. If the place they want is not listed, say so and ask them to add it in the Forfeit app rather than approximating it.
Allowed arguments
arguments object.Restrictions and refusal conditions
- Returns only places saved by the authenticated user.
- Coordinates used for a GPS goal must be copied from this result. Invented or geocoded coordinates are refused.
- Distance and inside-state values reflect the most recent device observation and may not be live.
Request example
curl https://api.forfeit.app/v1/actions/list_locations \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"arguments": {}
}'const response = await fetch("https://api.forfeit.app/v1/actions/list_locations", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
"arguments": {}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/list_locations",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'},
json={"arguments": {}},
)
result = response.json()Returns
Returns saved places, coordinates, radii, and current-presence state.
/v1/actions/query_health_data
Health data
Read the health metrics the user's device has synced, such as steps, sleep and workouts, for a past date, a range of up to 14 days, or a time window inside a single day. start_date is required.
Allowed arguments
start_datestringoptionalFirst local date as YYYY-MM-DD. Provide this for a date range; omission without start_time returns no data.
Accepted: Format: YYYY-MM-DD
end_datestringoptionalOptional last local date as YYYY-MM-DD. Maximum range is 14 days.
Accepted: Format: YYYY-MM-DD
start_timestringoptionalOptional 24-hour start time within a single day.
Accepted: Format: HH:MM
end_timestringoptionalOptional 24-hour end time within a single day.
Accepted: Format: HH:MM
Restrictions and refusal conditions
- Use YYYY-MM-DD dates in the user's timezone. Date ranges are inclusive and limited to 14 days.
- A date range needs start_date; omission without a time window returns no historical data.
- start_time and end_time are optional HH:MM values for a single-day window.
- Only data already synced by the user's device can be returned.
Request example
curl https://api.forfeit.app/v1/actions/query_health_data \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"arguments": {
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/query_health_data", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
"arguments": {
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/query_health_data",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'},
json={"arguments": {'start_date': '2026-10-01', 'end_date': '2026-10-07'}},
)
result = response.json()Returns
Returns device-synced health samples or aggregates for the requested window.
/v1/actions/query_calendar_data
Calendar events
Read the user's calendar events for a date or a range of up to 14 days, returned as start time, end time and title. Use it to see what a day actually looked like before discussing whether a goal was realistic that day.
Allowed arguments
start_datestringoptionalOptional first local date as YYYY-MM-DD. Defaults to today.
Accepted: Format: YYYY-MM-DD
end_datestringoptionalOptional last local date as YYYY-MM-DD. Maximum range is 14 days.
Accepted: Format: YYYY-MM-DD
Restrictions and refusal conditions
- Dates use YYYY-MM-DD in the user's timezone. Date ranges are inclusive and limited to 14 days.
- end_date cannot be before start_date. Omitting both dates uses today.
- Only events already synced from the connected calendar can be returned.
Request example
curl https://api.forfeit.app/v1/actions/query_calendar_data \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
--data '{
"arguments": {
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/query_calendar_data", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({
"arguments": {
"start_date": "2026-10-01",
"end_date": "2026-10-07"
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/query_calendar_data",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json'},
json={"arguments": {'start_date': '2026-10-01', 'end_date': '2026-10-07'}},
)
result = response.json()Returns
Returns connected calendar events for the requested date range.
Scheduling
Create and manage goal or general reminders.
/v1/actions/add_reminder
Add a reminder
Add a reminder. Pass general=true for a check-in on the main chat that is not about one goal, or general=false with a goal_id from list_goals for a goal's own reminder.
Usage guidance
time is the user's local time as HH:MM. schedule defaults to 'daily'. For a location reminder pass schedule='when I arrive at <saved location>' or 'when I leave <saved location>' (a place from list_locations) and time='' - it fires from the phone when they get there, even with the app closed.
This only adds a nudge. It cannot change when a goal is judged or what it requires.
Allowed arguments
namestringrequiredShort reminder name. Use the same name to update or remove it.
timestringrequiredUser-local 24-hour time. Use an empty string only for a location trigger.
Accepted: Format: HH:MM
generalbooleanoptionalTrue for a main-chat reminder. False requires goal_id.
Accepted: Default: false
goal_idstringoptionalRequired when general is false. Obtain it from list_goals.
schedulestringoptionalUse daily or a location phrase such as 'when I arrive at Gym' using a saved place.
Accepted: Default: "daily"
Restrictions and refusal conditions
- general=true targets the main chat. general=false requires a goal_id from list_goals.
- Do not pass master_chat as goal_id; the general flag is the only supported way to select it.
- Clock reminders require an HH:MM time. A saved-place arrival or departure trigger uses time as an empty string.
- Only notification and Overlord check-in reminders can be created. Goal checks and screen-blocking jobs are protected.
Request example
curl https://api.forfeit.app/v1/actions/add_reminder \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: add_reminder-UNIQUE_REQUEST_ID' \
--data '{
"arguments": {
"name": "Evening reminder",
"time": "19:30",
"general": false,
"goal_id": "GOAL_ID",
"schedule": "daily"
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/add_reminder", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
"Idempotency-Key": "add_reminder-UNIQUE_REQUEST_ID",
},
body: JSON.stringify({
"arguments": {
"name": "Evening reminder",
"time": "19:30",
"general": false,
"goal_id": "GOAL_ID",
"schedule": "daily"
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/add_reminder",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
'Idempotency-Key': 'add_reminder-UNIQUE_REQUEST_ID'},
json={"arguments": {'name': 'Evening reminder',
'time': '19:30',
'general': False,
'goal_id': 'GOAL_ID',
'schedule': 'daily'}},
)
result = response.json()Returns
Returns the created reminder result. Goal judgment and blocking schedule items cannot be created.
/v1/actions/update_reminder
Change a reminder
Change a reminder's time, or turn it on or off with enabled=true/false. Name it as it appears in the goal's schedule from list_goals.
Usage guidance
The goal check - the item that decides the day after the deadline - cannot be changed here and the call will be refused. That is deliberate: it decides whether the user is judged, not when they are reminded.
Allowed arguments
namestringrequiredExisting reminder name exactly as returned by list_goals.
generalbooleanoptionalTrue for a main-chat reminder. False requires goal_id.
Accepted: Default: false
goal_idstringoptionalRequired when general is false. Obtain it from list_goals.
timestringoptionalOptional replacement user-local 24-hour time.
Accepted: Format: HH:MM
enabledbooleanoptionalOptional true or false. Supply time, enabled, or both.
Restrictions and refusal conditions
- Use the existing reminder name exactly and select the same general or goal scope it belongs to.
- Supply a new HH:MM time, enabled=true or false, or both. An empty update is refused.
- Only notification and Overlord check-in reminders can be changed. Goal checks and screen-blocking jobs are protected.
- A missing or unreadable reminder fails closed rather than guessing which item was intended.
Request example
curl https://api.forfeit.app/v1/actions/update_reminder \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: update_reminder-UNIQUE_REQUEST_ID' \
--data '{
"arguments": {
"name": "Evening reminder",
"general": false,
"goal_id": "GOAL_ID",
"time": "20:00",
"enabled": true
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/update_reminder", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
"Idempotency-Key": "update_reminder-UNIQUE_REQUEST_ID",
},
body: JSON.stringify({
"arguments": {
"name": "Evening reminder",
"general": false,
"goal_id": "GOAL_ID",
"time": "20:00",
"enabled": true
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/update_reminder",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
'Idempotency-Key': 'update_reminder-UNIQUE_REQUEST_ID'},
json={"arguments": {'name': 'Evening reminder',
'general': False,
'goal_id': 'GOAL_ID',
'time': '20:00',
'enabled': True}},
)
result = response.json()Returns
Returns the updated reminder result. Goal judgment and blocking schedule items cannot be changed.
/v1/actions/remove_reminder
Remove a reminder
Delete a reminder, named as it appears in the goal's schedule from list_goals.
Usage guidance
The goal check cannot be removed here and the call will be refused. Removing reminders makes it easier for the user to miss a goal they are still judged on, so confirm they mean it.
Allowed arguments
namestringrequiredExisting reminder name exactly as returned by list_goals.
generalbooleanoptionalTrue for a main-chat reminder. False requires goal_id.
Accepted: Default: false
goal_idstringoptionalRequired when general is false. Obtain it from list_goals.
Restrictions and refusal conditions
- Use the existing reminder name exactly and select the same general or goal scope it belongs to.
- Only notification and Overlord check-in reminders can be removed. Goal checks and screen-blocking jobs are protected.
- A missing or unreadable reminder fails closed rather than deleting another item.
Request example
curl https://api.forfeit.app/v1/actions/remove_reminder \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: remove_reminder-UNIQUE_REQUEST_ID' \
--data '{
"arguments": {
"name": "Evening reminder",
"general": false,
"goal_id": "GOAL_ID"
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/remove_reminder", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
"Idempotency-Key": "remove_reminder-UNIQUE_REQUEST_ID",
},
body: JSON.stringify({
"arguments": {
"name": "Evening reminder",
"general": false,
"goal_id": "GOAL_ID"
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/remove_reminder",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
'Idempotency-Key': 'remove_reminder-UNIQUE_REQUEST_ID'},
json={"arguments": {'name': 'Evening reminder', 'general': False, 'goal_id': 'GOAL_ID'}},
)
result = response.json()Returns
Returns the removal result. Goal judgment and blocking schedule items cannot be removed.
Messages
Upload photos or videos, then send them to Overlord with or without text.
/v1/actions/create_message_upload
Add a photo or video
Get a secure upload URL for one photo or MP4 video. Call this once per file, submit a multipart form to the returned POST URL using every returned field, then pass the media_id to send_message.
Usage guidance
Accepted types are image/jpeg, image/png, image/webp, and video/mp4. The declared size must match the uploaded file and cannot exceed 15 MB. Upload URLs expire after 15 minutes and are bound to this account and connection. The file form field must come last.
Use this only to add media to a message to Overlord. It does not submit evidence or prove that a goal was completed.
Allowed arguments
content_typestringrequiredExact file type. Accepted values are image/jpeg, image/png, image/webp, and video/mp4.
Accepted: Allowed: image/jpeg, image/png, image/webp, video/mp4
size_bytesintegerrequiredExact file size in bytes. The maximum is 15728640 bytes (15 MB).
Accepted: Minimum: 1. Maximum: 15728640
Upload and send workflow
- Read the file's exact MIME type and byte size.
- Call
create_message_uploadonce for that file. - Build a multipart form with every returned
fieldsentry unchanged, then append the file field last. - POST the form to
upload_urlbefore it expires. - Pass
media_idtosend_message. Do not pass the upload URL.
const form = new FormData();
for (const [name, value] of Object.entries(upload.fields)) {
form.append(name, value);
}
form.append("file", file); // The file field must be last.
await fetch(upload.upload_url, {
method: upload.method,
body: form,
});
// After the upload succeeds:
// send_message({ message: "Here it is.", media_ids: [upload.media_id] })
Uploads are private and treated as unverified chat attachments. Media attached to a successful message is retained with that message. Abandoned uploads expire and are deleted.
Restrictions and refusal conditions
- content_type must be image/jpeg, image/png, image/webp, or video/mp4.
- size_bytes must be the exact file size from 1 through 15728640 bytes (15 MB).
- The upload URL expires after 15 minutes. Send every returned field as multipart form data, then append the file field last.
- The resulting media_id is bound to this account, API credential, grant, and API resource.
- Photos must decode as the declared JPEG, PNG, or WebP format and cannot exceed 40 megapixels.
- This upload flow is for MCP and JSON send_message calls. Developer API multipart requests can attach the bytes directly.
- A successfully sent attachment is retained with the chat message. Abandoned uploads are deleted after expiry.
Request example
curl https://api.forfeit.app/v1/actions/create_message_upload \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: create_message_upload-UNIQUE_REQUEST_ID' \
--data '{
"arguments": {
"content_type": "image/jpeg",
"size_bytes": 248013
}
}'const response = await fetch("https://api.forfeit.app/v1/actions/create_message_upload", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Content-Type": "application/json",
"Idempotency-Key": "create_message_upload-UNIQUE_REQUEST_ID",
},
body: JSON.stringify({
"arguments": {
"content_type": "image/jpeg",
"size_bytes": 248013
}
}),
});
const result = await response.json();import requests
response = requests.post(
"https://api.forfeit.app/v1/actions/create_message_upload",
headers={'Authorization': 'Bearer YOUR_ACCESS_TOKEN',
'Content-Type': 'application/json',
'Idempotency-Key': 'create_message_upload-UNIQUE_REQUEST_ID'},
json={"arguments": {'content_type': 'image/jpeg', 'size_bytes': 248013}},
)
result = response.json()Returns
Returns a short-lived POST policy and media_id. Upload the exact file before calling send_message.
/v1/actions/send_message
Message Overlord
Message Overlord and receive its response. Use it to share progress, explain what got in the way, ask a question, or send up to three prepared photos or MP4 videos.
Usage guidance
The Developer API can attach files directly as multipart media. MCP and JSON calls use create_message_upload once per file, then pass the returned media_ids here. You may send text, attachments, or both. Each media ID is single-use.
Your message arrives labelled with the connected app that relayed it. When relaying something you said, keep your wording intact.
This is not how a goal gets done. To claim you completed a goal, call submit_evidence - it runs the real check. To ask for a missed day, a skip or a change, call the request_* tools. Saying 'I finished my run' here is not a submission and must never be reported as one.
The reply you get back is what Overlord said, and an empty one is a real answer - it often has nothing to add. Never write a reply yourself.
If it comes back delivered with no reply, the answer was lost, not refused: it is in your Forfeit chat. Do not send it again, because Overlord may already have acted on it.
Allowed arguments
messagestringoptionalThe text to send. It may be empty when media_ids contains at least one uploaded photo or video. Maximum 2000 characters.
Accepted: Maximum length: 2000
media_idsarray of stringoptionalUp to three media IDs returned by create_message_upload. Each ID is single-use and must belong to this connection.
Attach up to three photos or MP4 videos directly to the message. Send multipart/form-data
with one arguments field containing the same JSON object shown above, plus one
media field per file. Accepted types: image/jpeg, image/png, image/webp, video/mp4. Each file can be up to 15 MB;
the request can contain up to 3 files.
Do not send remote URLs, Firebase paths, base64 strings, or a caller-selected filename as storage identity. The API validates the bytes and stores accepted media in the account's private Firebase Storage area.
Restrictions and refusal conditions
- Provide message text, one or more media_ids, or both. Text can contain up to 2000 characters after control markers are removed.
- media_ids accepts at most three unique, single-use IDs returned by create_message_upload for this connection.
- Each uploaded file must exist, match its declared byte size and content type, and still be within its 15-minute upload window.
- ACTION_JSON, SYSTEM MESSAGE, and NO_REPLY control markers are stripped and cannot be injected through this field.
- This sends a message to Overlord. It is not evidence, an appeal, or a goal-status change.
- If delivery succeeded but the reply was lost, do not retry because Overlord may already have acted.
- Overlord can use only actions granted to this API credential while handling the message.
- The Developer API can send the files directly as multipart media fields. MCP uses create_message_upload and media_ids instead.
Request example
curl https://api.forfeit.app/v1/actions/send_message \
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
-H 'Idempotency-Key: send_message-UNIQUE_REQUEST_ID' \
-F 'arguments={"message":"Here is the photo from today's workout."}' \
-F 'media=@evidence.jpg;type=image/jpeg'const form = new FormData();
form.append("arguments", "{\"message\":\"Here is the photo from today's workout.\"}");
form.append("media", file);
const response = await fetch("https://api.forfeit.app/v1/actions/send_message", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_ACCESS_TOKEN",
"Idempotency-Key": "send_message-UNIQUE_REQUEST_ID",
},
body: form,
});
const result = await response.json();import json
import requests
with open("evidence.jpg", "rb") as media:
response = requests.post(
"https://api.forfeit.app/v1/actions/send_message",
headers={
"Authorization": "Bearer YOUR_ACCESS_TOKEN",
"Idempotency-Key": "send_message-UNIQUE_REQUEST_ID",
},
data={"arguments": json.dumps({'message': "Here is the photo from today's workout."})},
files={"media": ("evidence.jpg", media, "image/jpeg")},
)
result = response.json()Returns
Returns delivery state and Overlord's response when one is available. A delivered request must not be blindly retried.
Idempotency and safe retries
- Use a unique
Idempotency-Keyfor each intended mutation or AI request. - Retry with the same key only when the intended request body is byte-for-byte equivalent.
- The same key and payload replays the stored result after completion. The response includes
Idempotent-Replayed: true. - The same key with different arguments or a different message returns
409 Conflict. - A pre-effect request interrupted by a server restart becomes retryable after its claim lease expires. Stale workers are fenced from recording or starting an effect.
- Never automatically retry a
review_requiredrequest. Check the affected goal or contact support with the request identifier.
Errors and retry behavior
Send Content-Type: application/json for JSON calls. For a documented direct-media call, let the HTTP library set multipart/form-data with its boundary. Error bodies use stable problem codes and include the same request identifier returned in the X-Request-ID response header. Include that identifier when contacting support.
401: missing, invalid, expired, or wrong-audience access token.403: missing scope, revoked grant, wrong owner, or non-Developer API client.404: unknown action, route, or AI request owned by another grant.405: the HTTP method is not supported for that route.409: idempotency conflict, request already running, or ambiguous mutation requiring review.413: a JSON body exceeds 64 KB, a media file exceeds 15 MB, or a multipart request exceeds 46 MB.422: invalid arguments or an action that could not be completed.429: rate or daily AI-job budget exceeded. HonorRetry-After.500: an unexpected internal error occurred. Retry only read requests or mutations that have a safe idempotency receipt.503: feature disabled, encryption unavailable, or authorization storage temporarily unavailable.
Security
Developer API access is audience-bound, permission-scoped, owner-bound, rate-limited, and revocable. Direct approval, failure, charging, credential access, third-party communication, and arbitrary HTTP operations are not exposed.
Read the complete MCP and Developer API security model.
Versioning and deprecation
Stable endpoints are namespaced under /v1. Additive response fields and new capabilities may be introduced without a version change. Removing or changing an existing field, action, scope, or behavior requires a new API version. Deprecated versions will be announced in these docs before removal.
API security
The MCP integration and Developer API share one explicit capability allowlist, while keeping separate clients and token audiences. A token issued for one surface is rejected by the other.
Authentication and identity
- Personal access tokens contain a 256-bit random bearer secret. The complete token is shown once and only a dedicated keyed HMAC verifier is stored.
- Creating or revoking a personal access token requires a verified, non-anonymous account and a sign-in no more than ten minutes old.
- Personal access tokens have an explicit permission set and expire after 1, 3, 7, 30, 60, 90, or 365 days. They never receive
offline_accessand cannot refresh themselves. - OAuth access tokens remain audience-bound to MCP or the Developer API. OAuth refresh tokens rotate on use and expire after a fixed 90-day family lifetime.
- Account identity comes only from the verified credential. Request bodies cannot select an email address, account, or user ID.
- Every action checks the credential type, owner, exact permission, live grant, revocation state, expiry, current rollout eligibility, current Pro entitlement, and usage budget.
- Write access is rechecked without a grant-status cache, so revoking a token or connection stops new writes immediately.
- MCP and Developer API credentials are explicitly rejected by the main Forfeit application before its Firebase and service-token verifiers run.
Abuse prevention
- All
/v1traffic is rate-limited by the trusted client address before token parsing or database reads. Action and AI routes have additional lower limits. - Each credential also has cross-worker daily read and write budgets. AI requests have a separate daily budget, bounded concurrency, attempt limits, and execution deadlines.
- Token creation is limited per account and per client address. Failed authentication responses are deliberately identical for unknown, malformed, expired, revoked, and incorrect personal tokens.
- Request bodies, multipart uploads, file counts, file sizes, image dimensions, content types, pagination, and execution time are all bounded.
- Mutations require idempotency keys, and ambiguous post-effect failures are held for review instead of being retried automatically.
- Forfeit personal access token patterns and bearer headers are redacted from application and access logs.
Capability boundaries
REST and MCP use the same handlers and validation rules. Neither surface exposes approve_goal, fail_goal, charge_user, direct status changes, direct goal deletion, email, SMS, calling, credential retrieval, or arbitrary HTTP.
Use request_approval, request_edit, request_deletion, and the other reviewed request actions. These submit work to the existing judgment flow instead of bypassing it.
AI request isolation
- Creating an AI request requires
chat:write. Reading its result requires the current token to retain every data permission that request used. - The scoped AI receives only basic account gates, date, time, timezone, locale, and the tools granted to that client.
- It does not automatically receive memories, prior chat, saved credentials, raw goals, health data, location data, or account prompt overrides.
- Queued messages, identifiers, and results are encrypted with AES-256-GCM and record-bound authenticated data.
- Jobs use leases, fenced claims, attempt limits, effect checkpoints, bounded execution time, and conservative recovery.
Revocation and incident handling
Users can review and revoke personal tokens and OAuth connections from Settings > MCP & API. Personal-token revocation invalidates its token record, client, and grant together. Device and authorization codes are stored only as hashes and are single-use. OAuth token responses retained briefly for safe retry are encrypted at rest. Refresh-token replay can revoke the affected token family. Error responses include an X-Request-ID that should be included when reporting unexpected behavior.
Do not place personal access, OAuth access, or refresh tokens in URLs, analytics, logs, browser local storage, source control, or client-side bundles that another user can inspect.
Contact
Need help? Here are the ways to get in touch with us:
Email Support
- josh@forfeit.app - Direct line to Josh (co-founder)
- support@forfeit.app - General support inquiries
In-App Support
Access the support chat directly within the Overlord app. We answer these 2x/day, 7 days/week.
Discord Community
Join our very active Discord community! You'll get the invite link once you sign up for Overlord.