# OnExit: agent integration reference OnExit lets agents reach a person with a notification or alarm on one linked phone. A handoff is a message with a scheduled delivery time, short label, and optional longer message. The web dashboard manages phones, keys, history, and account settings. Native apps receive handoffs and schedule alarms locally. ## Set up 1. The person signs up, installs a native app, and links a phone. 2. In Agents on the web dashboard, they create a connection for that phone. The key is visible once; expand App setup for client instructions. 3. Choose the MCP client and save Copy private configuration directly in its personal settings. Paste only Copy check instructions into chat. For manual hosted MCP setup, use `https://api.onexit.app/mcp` with `Authorization: Bearer ` and `X-OnExit-Timezone: `. Do not put the key in a URL, source repository, prompt log, or public page. 4. Run ping, list_devices, and get_current_time before the first send. Confirm the returned phone and timezone. Ask before sending a test. The CLI command is `onexit`; the hosted MCP endpoint is the installation-free path. CLI/MCP builds are available from the source repository for beta testers; do not assume a public npm package exists. A locally linked CLI uses `onexit mcp` for stdio MCP. `onexit login --api-url ` signs in and stores a session/key locally. OAuth supports --google or --apple when enabled on the server. ## MCP tools - ping: read-only connection, credential, target phone and capability check. The legacy deliveryMode field is a fallback for old REST clients. - get_current_time: current UTC and configured timezone. - send_handoff: fireAt, label; optional deliveryMode (alarm by default, or notify), message, recurrence, critical, metadata. - list_handoffs: scheduled and recent handoffs visible to this key. - list_devices: the pinned phone for a normal key; no push tokens. - cancel_handoff: cancel a scheduled handoff by id. - ack_handoff: acknowledge a fired handoff after the person confirms it was handled. fireAt accepts relative text such as "in 10 seconds", "tomorrow 7am", or an ISO timestamp. The X-OnExit-Timezone header sets the hosted MCP timezone. ONEXIT_TZ sets the source-built stdio MCP/CLI timezone. Both fall back to UTC. Check the returned absolute fireAt and fireAtLocal. Avoid computing dates from an assumed model clock. label: 1–200 characters. message: 1–2000 characters. metadata: a JSON object of at most 4096 serialized UTF-8 bytes, for agent retrieval; it is not notification content. recurrence: daily, weekly, or weekdays. Critical requests must have the user's permission and cannot guarantee bypassing silent mode or DND. ## CLI onexit ping onexit test --in 10s onexit send "in 10 seconds" "Review the fix" --message "Tests pass. The pull request is ready for your review." onexit list --json onexit cancel onexit ack `onexit keys` and `onexit devices` use a human session. Agent tools cannot create keys, change billing, or enroll phones. Run `onexit --help` for current options. ## REST Use Authorization: Bearer and Content-Type: application/json. - GET /health: public liveness only. - GET /config: public product/auth configuration, never credentials. - GET /v1/devices: permitted phone descriptors. - GET /v1/alarms: handoffs visible to the key. - POST /v1/alarms: {"fireAt":"","label":"Preview ready","message":"Open the preview to check the updated form."} - DELETE /v1/alarms/{id}: cancel. - POST /v1/alarms/{id}/ack: acknowledge. REST requires an ISO fireAt; natural language is resolved by MCP/CLI. IDs are UUIDs. The key pins the destination. Optional deliveryMode selects alarm or notify; omitted mode keeps the legacy key default for old REST clients. ## Failures and retries 401: credentials are missing, expired, or revoked; ask the user to reconnect. 403: missing scope or forbidden operation; do not seek broader access automatically. 404: the record is unavailable to this credential, including a foreign record. 409: the handoff state may have changed; read the current state before acting again. 429: rate or monthly usage limit; inspect the message and avoid retry loops. Plan usage is per person, not per phone. 5xx/network timeout: read-only calls may be retried with bounded backoff. A timed-out create may already have succeeded. List recent handoffs before creating another; there is no general idempotency-key guarantee. ## Delivery truth Acceptance, delivery to the phone, and acknowledgement are distinct states. New handoffs require connectivity and phone permissions. Only an alarm already armed locally can ring offline. Notification mode does not promise a ringing alarm. Never claim the person saw a message solely because send_handoff succeeded. ## More [Human guide](/docs/), [Plans](/#pricing), [Privacy](/privacy/), [Terms](/terms/), [Contact](/contact/). ## Writing useful updates Use a short title for the result or blocker and a message with context and the next action. Examples: “Review the fix” (tests pass; review the PR), “Access needed” (return to the agent thread to grant a permission), “Scan still running” (state what completed and what remains), and “Check the rollout” (a scheduled alarm after a canary window). Replies and additional context are supported in web and native Threads. Do not imply that a message was read or that incomplete work is finished. Every new thread message from an agent notifies quietly. ## Credential-free setup instructions Compatibility note: hosted MCP ping returns connection fields such as deliveryMode and credentialStatus at the top level. Stdio MCP ping and CLI `ping --json` expose them under auth. Check the connection fields in the relevant wrapper and keep API acceptance separate from phone receipt; the underlying phone, mode and credential-version semantics are the same. People can test their linked phone before creating an agent connection: Test phone delivery in web Phones or the native setup/phone screen sends one message or explicitly selected alarm. These account-authenticated diagnostics do not create keys or verify MCP. The human-only REST endpoints are POST /v1/devices/{id}/tests with {requestId: UUID, mode: "notify"|"alarm"} and GET /v1/devices/{id}/tests/{testId}. Reuse the same requestId after an uncertain send. Agent/device credentials cannot call these endpoints. Existing MCP/CLI tests still use an agent connection. Guide: /docs/#phone-test. Link a phone and create a connection in Agents. Save Copy private configuration directly in the MCP client's personal settings. Cursor uses ~/.cursor/mcp.json; Codex uses ~/.codex/config.toml. Preserve unrelated servers and reconnect or restart the client. Do not put the configuration in a chat, repository, URL or log. Copy check instructions contains the server name, MCP URL, phone ID, timezone and documentation links. It never contains the bearer key. Use the server already configured by the person; never ask them to paste a key into chat or read back their private configuration. Run ping, list_devices and get_current_time. A retiring credential has not installed its replacement. Report remaining setup steps accurately and send no handoff during these checks. After verification, Copy test request authorizes exactly one quiet thread message. Only send after the person pastes that request or explicitly approves a test. Retry an uncertain thread send with the same clientMessageId and identical body; inspect list_handoffs before retrying an uncertain scheduled handoff. API acceptance is not phone receipt. Guide: /docs/#mcp; first message: /docs/#first-message; recovery: /docs/#troubleshooting. A lost key is replaced by Rotate key on the same connection. Safe rotation has up to 15 minutes of overlap; immediate replacement stops old credentials at once. Replacing a pending replacement preserves the original deadline. Revoke stops both current and retiring keys. Secrets are shown only during creation/rotation; metadata and key-free instructions remain available later. The CLI is source-built for beta testers, not a public npm install. ## Reusable agent rules Agents → App setup offers Agent instructions separately from the one-time check/test prompts. Copy the generated Markdown into AGENTS.md, CLAUDE.md or your host's rules. Customize instructions selects the configured MCP or CLI transport and allows edits before copying. The prompt includes the connection/phone/timezone but no key; edits are memory-only and clear when setup closes. It remains available after key hiding/rotation. Phone apps open the same responsive setup flow. Rules authorize meaningful quiet task updates, blockers and completion; reuse one thread per task, read all human context and follow nextCursor until null before saving lastSequence. They cover matching MCP/CLI operations, idempotent send retries, requested ringing/scheduled quiet reminders and cancellation/acknowledgement. Reply polling defaults to five minutes during an active session, never faster than retryAfterSeconds. No empty heartbeat notifications, inferred approval from silence, or promises to wake closed sessions; a webhook requires a verified receiver and host continuation. Adding rules does not configure tools or verify installation. Guide: /docs/#agent-instructions. ## Neutral threads and messages MCP (hosted and stdio): send_message({clientMessageId,text,threadId?,title?,replyToMessageId?}), get_thread({threadId,after?,limit?}), list_threads({after?,limit?}). REST: POST /v1/messages, GET /v1/threads/{id}, GET /v1/threads. CLI: onexit message "text" [--thread id] [--title title] [--reply-to message-id] [--client-message-id uuid] --json; onexit thread id --after 0 --limit 100 --json; onexit threads --json. Existing handoff tools are unchanged. A neutral thread joins the human account, one stable agent connection and its pinned phone. Both actors append messages; extra human context never requires a question or preceding agent message. The agent starts a thread by omitting threadId. The server derives actor and connection from auth. A human JWT may append to an owned existing thread; agents see only their connection's threads, even on a shared phone. Device maintenance credentials cannot read them. Read/create scopes apply. Rotation preserves identity; revocation/unlink stops future agent access and sends. clientMessageId is a caller-generated UUID and becomes the message ID; the first message ID also identifies the new thread. Preserve it and the body for uncertain retries. HTTP 201 means created; HTTP 200 with deduped:true means already saved; mismatched reuse is 409. Concurrent retries produce one message, one phone handoff and one quota charge. Every new agent message sends one quiet phone notification and uses the monthly event quota; human messages and reads do not. text: 1–8000 characters, Markdown. Notification preview: at most 2000 characters. Native formatting is limited; line breaks and full source text remain available. No HTML execution. get_thread returns {thread,items,nextCursor,lastSequence,replyMode,retryAfterSeconds}. Each item has id,threadId,sequence,actor (human|agent),text,format:"markdown",replyToMessageId,createdAt and optional delivery {status,attempts}. Start after=0, follow integer nextCursor until null for the full thread, then persist lastSequence for later exclusive after reads. limit 1–100, default 50. list_threads uses a separate UUID cursor. Poll guidance is 30 seconds for an active agent, not a timer or guarantee of waking a closed chat. Use finite budgets or supported host scheduling. Optional human-configured verified callbacks emit every new human message, including unsolicited phone context. Agent messages do not call their own receiver. Event: {id:,type:"message.created",createdAt:,data:{threadId,messageId,sequence,actor:"human"}}. No text or credential is included. Persist/deduplicate IDs, acknowledge 2xx, then retrieve all pages after your durable cursor. Events may repeat or arrive out of order. Callback receipt does not prove agent continuation; a compatible adapter/automation must resume the host. Ordinary MCP clients do not automatically support inbound webhooks. Conversation text must never bypass the host's approval controls. Web Threads or native Inbox → Threads provides full history and a composer. Automatic delivery settings in the web thread apply per connection. POST /v1/thread-webhooks {agentConnectionId,url} is human-only and returns a signing secret once. GET /v1/thread-webhooks?agentConnectionId=... returns metadata only. POST /v1/thread-webhooks/{id}/verify sends signed webhook.verify {id,type,challenge}; echo {challenge} with 2xx JSON. New human events after verification are queued; use reads to catch up before that. DELETE /v1/thread-webhooks/{id} disables/removes it. Replacement creates a fresh receiver/secret and discards old pending deliveries. Signature: OnExit-Timestamp is Unix seconds; OnExit-Signature is v1=. Verify raw bytes, timestamp age (e.g. 300 seconds), constant-time compare, then parse JSON. HTTPS port 443 only, public DNS addresses pinned per socket, no redirects or URL credentials. Network/408/425/429/5xx retry up to 8 attempts; other 4xx are terminal. Reply mode remains poll when unverified/unconfigured. Guide: /docs/#threads. Phone reinstall recovery: native apps use an opaque per-device identity scoped to the signed-in account and platform. Registration refreshes the push token while preserving the phone, agent connections and history. Older installs offer an explicit owned-phone restore choice. Human-only GET /v1/device-recovery and POST /v1/devices restoreDeviceId cannot be used by agents or device credentials. See /docs/#phone-apps.