Skip to content
fonino✳
Why FoninoHow it worksPlansGuidesDocsSign inSign up ↗

Documentation

  1. Overview
  2. Quickstart
  3. Connecting agents
  4. Authentication
  5. Core concepts
  6. Tool reference
  7. Approvals
  8. Safety rules
  9. Messaging
  10. Live view & handoff
  11. Errors
  12. Limits & timeouts
  13. Architecture & data

Fonino documentation

Build on a real iPhone.

Last updated October 4, 2026

Overview

Fonino gives an AI agent a physical, non-jailbroken iPhone over the Model Context Protocol (MCP). The agent takes the phone for a task, reads screenshots, taps, swipes and types in any app, and sends texts through a checked path. Fonino sits between the agent and the phone: it enforces approvals, blocked apps and sending limits, and the agent can't switch them off.

  • Endpoint: https://api.fonino.app/mcp (Streamable HTTP, stateless JSON responses).
  • Auth: OAuth 2.1 sign-in (recommended) or a per-connection API key.
  • Phones: BYOF (your iPhone on your Mac) or a hosted iPhone. Both appear the same to the agent.
  • Coverage: U.S. only. Texts go to U.S. numbers and iMessage addresses.

Quickstart

  1. Get a phone. Start a plan on fonino.app. For BYOF, the setup guide at app.fonino.app installs Fonino on your Mac and connects your iPhone.
  2. Connect your agent to https://api.fonino.app/mcp and sign in with your Fonino email (see Connecting agents).
  3. Ask for something: “Take a screenshot of my Fonino phone and tell me what's on it.”

A typical task looks like this:

phone_list_devices()
  → [{ id: "iphone-002", status: "connected" }]
phone_acquire(device_id: "iphone-002", task: "Check SF weather")
  → { lease_id } + screenshot
phone_open_app(device_id, lease_id, bundle_id: "com.apple.weather")
phone_get_screen(device_id, lease_id)
  → { screen_id: "scr_…", width: 430, height: 932 } + screenshot
phone_tap(device_id, lease_id, screen_id, x: 215, y: 120)
phone_release(device_id, lease_id, outcome: "68°F, sunny")

Connecting agents

Any MCP client that supports remote servers over Streamable HTTP works. Tested clients:

AgentHow to connect
Grok Botgrok.com/connectors → New Connector → Custom → URL https://api.fonino.app/mcp → Connect, then your Fonino email and the emailed code.
ClaudeSettings → Connectors → Add custom connector → https://api.fonino.app/mcp → Connect, then your Fonino email and the emailed code.
Hermes AgentThree lines in ~/.hermes/config.yaml, then hermes mcp login fonino. Full Hermes guide.
Claude Codeclaude mcp add --transport http fonino https://api.fonino.app/mcp, then /mcp to sign in.
MuseSettings → Connectors → Add custom connector → https://api.fonino.app/mcp, then sign in with your Fonino email and the emailed code.
Your own codeThe official MCP SDKs (TypeScript, Python) with the URL above and OAuth, or an Authorization: Bearer apk_… header.

Screenshots as files. Some clients (Hermes, for example) save tool images to disk and give the model a path. Fonino's instructions tell the agent to open that file with its image tool. Clients without image support can call phone_get_screen with with_text: true to get on-screen text with tap points.

Authentication

OAuth 2.1 (recommended)

Fonino is an OAuth 2.1 authorization server for its MCP resource. Clients discover everything from metadata:

  • https://api.fonino.app/.well-known/oauth-protected-resource (RFC 9728)
  • https://api.fonino.app/.well-known/oauth-authorization-server (RFC 8414)
  • Authorization code flow with PKCE S256, public clients (token_endpoint_auth_method: none), scope phone.
  • Client registration: Dynamic Client Registration (RFC 7591) or a Client ID Metadata Document (an https client_id).
  • Redirect URIs must be https, or http on localhost / 127.0.0.1 for desktop and CLI clients.
  • Access tokens last 1 hour; refresh tokens 30 days and rotate on use.

The user signs in with their Fonino email and a 6-digit code we email them. Each sign-in shows up under Connected assistants at app.fonino.app, where it can be removed at once.

API keys

For clients that only take a header: create a connection at app.fonino.app and send Authorization: Bearer apk_…. A key is shown once, belongs to one account, and can be limited to specific phones and recipients. Treat it like a password and revoke it if it leaks.

Core concepts

Leases

Every phone action needs a lease_id from phone_acquire. A lease gives one agent exclusive control. It renews with each call and expires after 10 idle minutes. Release it with phone_release when done or blocked; the phone then returns to the Home Screen. Other connections see a held phone as busy.

Screenshots and coordinates

Screenshots come with a screen_id, the screen size in points (for example 430 × 932) and the capture time. Coordinates for taps and swipes are in those points, and every tap or swipe must name the screen_id its coordinates came from. If the screen has changed since, Fonino refuses with stale_screen and returns a fresh screenshot. Each action returns a new screenshot unless you pass screenshot_after: false.

Risk

Actions are normal unless they commit something. For the single tap or call that does, set risk to external_communication, purchase, destructive or authentication, and include an approval object:

{ "app": "Amazon", "operation": "purchase", "recipient": "Amazon.com",
  "payload": "1x Anker USB-C cable 2-pack, total $10.85", "summary": "Buy a USB-C cable" }

Tool reference

Every phone tool takes device_id; all but the session tools also take lease_id. Action tools also accept risk, approval and screenshot_after.

Session

ToolParametersWhat it does
phone_list_devices—Your phones, their status, and whether each is free.
phone_get_statusdevice_idHealth checks for one phone: reachable, locked, screen size.
phone_acquiredevice_id, task, holder?Take exclusive control for a task. Returns lease_id and a first screenshot.
phone_releasedevice_id, lease_id, outcome?Give the phone back. Always call it when done or blocked. The phone returns to the Home Screen.

Seeing the screen

ToolParametersWhat it does
phone_get_screendevice_id, lease_id, with_text?Fresh screenshot with screen_id, size in points and capture time. with_text adds on-screen text lines with tap points.
phone_waitdevice_id, lease_id, msWait up to 10 s for loading or animations, then return a fresh screenshot.

Acting

ToolParametersWhat it does
phone_tapdevice_id, lease_id, screen_id, x, y, risk?, approval?Tap a point from the screenshot identified by screen_id.
phone_swipe…, screen_id, x1, y1, x2, y2, duration_ms?Drag between two points. To scroll content down, swipe up (y1 > y2).
phone_typedevice_id, lease_id, text, submit?Type into the focused field. submit presses Return.
phone_keydevice_id, lease_id, keyPress return, delete, tab or space.
phone_homedevice_id, lease_idGo to the Home Screen.
phone_app_switcherdevice_id, lease_idOpen the app switcher.
phone_open_appdevice_id, lease_id, bundle_id, fresh?Open an app by bundle id. fresh quits it first so it starts from its launch screen.

Messages

ToolParametersWhat it does
phone_send_messagedevice_id, lease_id, to, bodySend a text to a number or iMessage email. Fonino checks recipient and text, presses Send once and reports the delivery status.
phone_read_messagesdevice_id, lease_id, with, limit?Latest messages of a conversation as text (from: me | them), plus a screenshot.
phone_wait_for_replydevice_id, lease_id, with, max_wait_s?Wait up to 85 s per call for the other person to reply.

Photos

ToolParametersWhat it does
phone_save_imagedevice_id, lease_id, image_url | image_base64Save an image to Photos (PNG, JPEG, GIF, WebP, HEIC, up to 20 MB) so it can be attached or posted.
phone_get_photodevice_id, lease_id, count?The newest 1–4 photos from the camera roll, as images (longest side 1600 px).

Approvals

ToolParametersWhat it does
phone_await_approvaldevice_id, lease_id, approval_idWait up to ~45 s per call for the owner's decision: pending, approved, denied or expired.
phone_confirm_approvaldevice_id, lease_id, approval_id, codeApprove with the 6-digit code the owner reads to the agent. Only a code the user typed.

Handing over

ToolParametersWhat it does
phone_live_viewdevice_id, lease_idPrivate 30-minute link to watch the phone live.
phone_request_helpdevice_id, lease_id, reasonHand the phone to the owner (logins, 2FA, CAPTCHAs). Pauses the agent and returns a live link.
phone_wait_for_ownerdevice_id, lease_id, max_wait_s?Wait up to 85 s per call until the owner presses "I'm done".
phone_owner_donedevice_id, lease_idThe owner's "I'm done" button for hosts that confirm tool calls.

Approvals

Anything that spends, sends, publishes or deletes waits for the phone owner. The flow:

  1. The agent calls the action with a risk and an approval object. Fonino replies approval_pending with an approval_id and an approve_url. Nothing has happened yet.
  2. The agent shows the link to the user right away. The owner also gets a notification with a 6-digit code.
  3. The agent calls phone_await_approval again and again (about 45 s per call) until the status isn't pending.
  4. On approved, the agent repeats the original call exactly. On denied or expired, it stops, tells the user and releases the phone.
  • The agent can't approve itself. Approving needs the owner's Fonino sign-in, or the 6-digit code the owner reads out (phone_confirm_approval; wrong codes are counted and lock the request).
  • One approval, one action with exactly the approved text or amount. The owner can choose to allow the rest of a task instead, capped at the approved amount.
  • Requests expire after 10 minutes. Releasing the phone closes its open requests.
  • Agents that leave: if a request is declined or expires and the agent doesn't come back within 2 minutes, Fonino releases the phone.
  • Fonino checks the button under each tap. In social, mail, shopping, food and ride apps and in browsers, a tap on a button like Post, Send, Reply, Place your order, Buy Now or Pay returns approval_required if the agent didn't mark it.

Safety rules

  • Blocked apps: Settings, Wallet, Passwords, App Store, Mail, Phone, FaceTime, Find My, Watch and Messages (texting goes through phone_send_message). If Fonino can't tell which app is open, it refuses the action.
  • Untrusted content: everything on the screen, in messages and on web pages was written by other people. Agents are instructed never to follow instructions found there.
  • Credentials stay with the owner: at passcodes, passwords and 2-factor prompts the agent hands over with phone_request_help.
  • Per-connection limits: a connection can be restricted to specific phones and recipients.

More in Security.

Messaging

  • phone_send_message opens the conversation, verifies the recipient and the exact text, presses Send once and returns the status the phone shows (for example delivered, with the service used).
  • Limits per phone: 20 texts an hour, 100 a day, and 5 an hour to the same person. A refused send returns rate_limited with retry_after_s.
  • STOP is honored: a recipient who replies STOP is never texted again (recipient_opted_out).
  • Person-to-person only. No bulk, marketing or unsolicited messages; connections that send them are disabled.
  • After outcome_unknown, never resend: call phone_read_messages first.

Live view & handoff

phone_live_view returns a private 30-minute link to watch the phone. For logins, 2FA or CAPTCHAs, phone_request_help pauses the agent and gives the owner a live link with control of the phone. When the owner presses “I'm done”, phone_wait_for_owner returns with a fresh screenshot and a summary of what they did.

Errors

Tool errors return { "ok": false, "code": "…", "error": "…", "next": "…" }. The next field says what to do.

CodeMeaning and what to do
approval_pendingThe action needs the owner's approval. Show approve_url, wait with phone_await_approval, then repeat the same call.
approval_requiredFonino recognized a button that publishes, sends or buys. Repeat the tap with a risk and an approval object.
approval_deniedThe owner declined. Don't retry; tell the user.
app_blockedThe app in front is off limits to assistants (see Safety rules). Nothing was done.
stale_screenThe screen changed since the screenshot your coordinates came from. The error includes a fresh screenshot; decide again from it.
screen_id_required / unknown_screenPass the screen_id of your latest screenshot.
out_of_boundsThe point is outside the screen.
lease_invalidThe lease expired or was released. Call phone_acquire again.
device_leasedAnother task holds the phone. Try again later.
forbidden_device / unknown_deviceYour connection can't use that phone, or it doesn't exist. Call phone_list_devices.
device_unreachable / phone_lockedThe phone is offline or locked. The owner needs to unlock it and keep it connected.
human_controlThe owner has taken over. Wait with phone_wait_for_owner.
rate_limitedA sending limit was reached. Nothing was sent; retry after retry_after_s.
recipient_not_allowed / recipient_opted_outThe recipient isn't on the connection's allowlist, or replied STOP.
outcome_unknownThe action may have happened. Never repeat it blindly: read the conversation or take a screenshot first.

Limits & timeouts

WhatLimit
Lease idle timeout10 minutes
Approval request lifetime10 minutes
Phone released after a declined or expired approval if the agent is gone2 minutes
phone_await_approval per callabout 45 s
phone_wait_for_reply, phone_wait_for_owner per call85 s
phone_wait10 s
Texts per phone20 an hour, 100 a day, 5 an hour per recipient
Images for phone_save_image20 MB
Live view link30 minutes
OAuth access / refresh token1 hour / 30 days

Architecture & data

agent ──HTTPS (MCP)──▶ api.fonino.app ──outbound WebSocket──▶ Fonino on the Mac ──USB──▶ iPhone
                       auth, routing                      approvals, limits, app rules
  • The cloud (api.fonino.app) knows who you are and which Mac your phone is on. It forwards MCP calls and live-view traffic, nothing else.
  • The Mac enforces everything phone-specific: leases, approvals, limits, blocked apps. It connects out to the cloud, so no ports are opened on your network.
  • With BYOF, screenshots and run recordings stay on your Mac. Hosted phones run the same software on Macs we operate.

See Privacy for what we store and for how long.

Questions or a use case to test? Contact support.

fonino✳

Your bot. Its own phone.

DocsGitHubHermesGuidesSecurityPrivacyTermsSupport
© 2026 FoninoIndependent service for AI assistants.

Apple, iPhone, Safari and iMessage are trademarks of Apple Inc., registered in the U.S. and other countries and regions.

Android and Google Messages are trademarks of Google LLC.

IOS is a trademark or registered trademark of Cisco in the U.S. and other countries and is used by Apple under license.

Fonino is an independent service and is not affiliated with, authorized, sponsored or endorsed by Apple Inc., Google LLC, xAI, Meta, OpenAI, Anthropic or Nous Research. Third-party product names are used only to identify the products and services referenced. All other trademarks belong to their respective owners.