Livi for agents
Everything an agent needs to connect to Livi and use it. Point your agent at this page, at /skill.md, or at the agent kit on GitHub.
Set up Livi for me: https://livi-widgets.vercel.app/start. It shows the user a code to type into Livi, waits for it, and gets its own token. The full walkthrough is at /start.Stay current. Livi changes. At the start of every Livi task, fetch the latest guide from https://livi-widgets.vercel.app/skill.md and follow it over this copy. If you keep it as a local skill, overwrite your copy with it.
Livi is an iPhone app whose widgets are drawn by you, the agent. Everything Apple's WidgetKit can show, you control with JSON: what each size shows, how it looks, when it changes, and what happens when the user taps it.
The one rule that keeps widgets from breaking: build, then look, then fix. Every time you make or change a widget, call livi_preview_widget (or read the check that livi_set_widget returns) and fix every warning before you tell the user it's done. Section 4 explains the loop.
1. What you can control
Every widget capability in iOS 26, and the field that controls it:
| On the iPhone | What it is | Livi field |
|---|---|---|
| Home Screen, small / medium / large | The three iPhone widget sizes | blocks, layout, sizes |
| iPad and Mac, extra large | The wide iPad widget | sizes.extra_large |
| Lock Screen, rectangular | Two lines under the clock | lock.rectangular |
| Lock Screen, circular | A ring or a short value in a circle | lock.circular |
| Lock Screen, inline | One line of text above the clock | lock.inline, lock.symbol |
| StandBy, CarPlay | The small widget, full screen, background removed | automatic (design small well) |
| Text that ticks by itself | Clocks, countdowns, timers, live progress bars | clock, countdown blocks |
| Charts | Sparklines from your numbers | chart block |
| Gauges | Rings and capacity bars | gauge, progress blocks |
| Photos | Pictures in a block or as the background | image block, theme.style: "image" |
| Buttons on the widget | The user taps; you get an event | buttons block, livi_get_events |
| Checklists on the widget | The user ticks items right on the widget | checklist block |
| Control Center, Lock Screen corners, Action button | Any of your widget buttons, placed by the user | automatic for every buttons item without a url |
| Tap to open | Open a web page, call, map, Shortcut or app | url on the widget, any block, list row or button |
| Smart Stack | Rotate your widget to the top when it matters | relevance (0-100), per version too |
| Privacy | Hide content while the phone is locked | private, or sensitive on a block |
| Light, dark, tinted, clear (Liquid Glass) | How the Home Screen is styled; the default look is frosted glass, fully see-through in Clear | theme (system, light, dark, glass, solid, gradient, image) |
| Fonts | SF Pro, SF Rounded, New York, SF Mono | theme.font |
| Versions by time, day, place | Phone switches content with no agent running | variants |
| Updates with no agent | Livi fetches a URL every N minutes | livi_schedule |
| Updates you push | Changes reach the phone in seconds | livi_set_widget (Livi sends the widget push) |
What widgets can't do, so don't promise it: scroll, play video or sound, take typed text, show a web view, load anything from the network while drawing, animate freely, or update more than about 40 to 70 times a day on their own. Buttons and checklists don't work while the phone is locked.
2. Connect (once)
You show the user a code; they type it into the Livi app (like pairing a TV). Full walkthrough, including getting the app from TestFlight: https://livi-widgets.vercel.app/start
- Get a code, with your own name (URL-encoded):
curl -s "https://ujtuxanqlzmhnrzijfoo.supabase.co/functions/v1/connect?start=1&name=Claude%20Code"
- Show them the 8-character code (like
K7QX-29PL) and say: "Open Livi, tap +, and type this code." The reply also gives a QR that opens Livi with the code filled in. - Run the wait command from the reply (
connect?wait=lvw_…) until they enter it. It then prints your Livi token (lv_…), shown only once, plus setup steps. Codes work once, for 15 minutes; if one expires, get a new one.
Older builds of the app showed the code instead. If the user gives you one, run curl -s "https://ujtuxanqlzmhnrzijfoo.supabase.co/functions/v1/connect?code=K7QX29PL&name=Claude%20Code" with it.
Then add the MCP server.
Claude Code
claude mcp add --transport http --scope user livi https://ujtuxanqlzmhnrzijfoo.supabase.co/functions/v1/mcp --header "Authorization: Bearer lv_YOUR_TOKEN"
Run /mcp or restart so the livi_* tools load. Until then, use the HTTP API in section 15.
Any MCP client (JSON config)
{
"mcpServers": {
"livi": {
"type": "http",
"url": "https://ujtuxanqlzmhnrzijfoo.supabase.co/functions/v1/mcp",
"headers": { "Authorization": "Bearer lv_YOUR_TOKEN" }
}
}
}
Clients that only take a URL (Claude and ChatGPT custom connectors): the token goes in the path.
https://ujtuxanqlzmhnrzijfoo.supabase.co/functions/v1/mcp/lv_YOUR_TOKEN
Treat the token like a password. Don't print it back to the user unless they ask, and don't commit it to a repo.
3. Tools
| Tool | What it does |
|---|---|
livi_set_widget |
Create or replace a widget by slug. Send the whole widget each time. Returns check: what each size shows, plus warnings. |
livi_preview_widget |
See a widget: an image of every size and Lock Screen shape, plus warnings. Pass a slug, or a full draft to check before saving. Only you see it. |
livi_list_widgets |
Every widget and its blocks. |
livi_delete_widget |
Remove a widget by slug. |
livi_get_events |
Taps on your widget buttons and checklist items. |
livi_schedule |
Keep a widget updated automatically: Livi fetches a URL every N minutes and fills your template. |
livi_test_job |
Try a job once without saving it. |
livi_list_jobs / livi_delete_job |
See or stop scheduled jobs. |
livi_ask |
Ask the user a question on their phone and wait up to wait_seconds (default 45) for the answer. |
livi_get_answer |
Keep waiting on a question (call again while status is pending). |
livi_list_requests / livi_cancel_request |
Questions still waiting, or take one back. |
livi_get_phone_context |
What the phone shares: place, battery, time zone, local time, city. |
4. Build, look, fix (every time)
- Draft the widget JSON.
- Preview it: call
livi_preview_widgetwith the draft (same fields aslivi_set_widget). You get back:- an image of the small, medium and large Home Screen widgets and the rectangular, circular and inline Lock Screen widgets, drawn with the phone's layout rules, theme and the user's time zone;
sizes: in words, which blocks each size shows and how many are hidden;warnings: things that look broken (text cut off, blocks running off the bottom, versions that never show, unreadable colours, bad indexes) with how to fix each;notes: things worth knowing (glass needs Clear mode, a picture loads after a refresh).
- Fix every warning, and look at the image like a designer would. Is the most important thing on the small size? Does anything look cramped?
- Save with
livi_set_widget. Itscheckrepeats the warnings; it should be empty. - For versions, preview each one with
at("07:30") orplace("home") to see what shows then and there.
Preview a saved widget with just { "slug": "news" }. The image is approximate (the phone uses SF Pro and real SF Symbols; the preview draws symbols as dots) but the layout, sizes, cut-offs and colours match. Never show the preview image to the user as if it were their phone; it's for you.
Over HTTP, preview_widget returns png_base64; add "raw": true to get the PNG itself (curl … -o preview.png).
5. A widget
{
"slug": "brief",
"title": "Morning brief",
"subtitle": "9:00 AM",
"symbol": "newspaper.fill",
"accent": "#0A84FF",
"url": "https://news.ycombinator.com",
"relevance": 40,
"theme": { "style": "system", "font": "default" },
"layout": "auto",
"blocks": [
{ "type": "metric", "label": "AAPL", "value": "$291.13", "change": "+1.2%" },
{ "type": "list", "title": "Top stories", "items": [
{ "title": "Apple ships iOS 27.1", "detail": "Verge", "url": "https://www.theverge.com/…" },
{ "title": "Fed holds rates steady", "detail": "Reuters" }
] },
{ "type": "clock", "label": "New York", "timezone": "America/New_York" }
],
"sizes": { "small": [0] },
"lock": { "inline": "AAPL $291 ▲1.2%", "circular": 0 }
}
| Field | What it does |
|---|---|
slug |
Stable id you reuse to update it: lowercase letters, digits, dashes, up to 40. |
title |
Shown at the top, up to 40 characters. About 15 fit on small, 35 on medium. |
subtitle |
Small grey text at the top right on medium and larger. Good for a time ("9:00 AM"). |
symbol |
SF Symbol name next to the title, like newspaper.fill, bitcoinsign.circle.fill, airplane. |
accent |
#RRGGBB. Colours the symbol, list dots, bars, rings, charts and buttons. |
url |
Opened when the widget is tapped. Default opens it in Livi. |
relevance |
0-100. How much this matters now; the Smart Stack rotates the most relevant widget to the top. |
private |
true hides the content (not the title) while the phone is locked. |
theme |
How it looks. Section 9. |
layout |
auto, hero, grid or list. Section 7. |
blocks |
Up to 8 blocks, most important first. Section 6. |
sizes |
Which blocks each size shows, by index from 0. Section 7. |
lock |
What the Lock Screen widgets show. Section 8. |
variants |
Versions by time, day or place. Section 10. |
Up to 12 widgets per user. Send null for url, relevance, sizes or lock to clear them; fields you leave out keep their current value.
6. Blocks
Every block has a type. These fields work on any block:
url: open this when the block is tapped (medium and larger; on small, the whole widget opens the widgeturl).color:#RRGGBBfor the block's main mark (the value, ring, bar, line or buttons) instead of the accent.sensitive:truehides this block while the phone is locked.
Links can be https://, tel:, sms:, mailto:, facetime:, maps:, shortcuts:// (run a Shortcut: shortcuts://run-shortcut?name=…), music://, calshow: or livi://.
text
{ "type": "text", "text": "Rain after 3pm. Take an umbrella.", "style": "body" }
style: title (bold, 17-19 pt), body (14 pt, default) or caption (12 pt, grey). Up to 400 characters, but small shows about 4 lines (60 characters), medium 3 lines (120), large 6 lines.
metric
{ "type": "metric", "label": "Bitcoin", "value": "$67,240", "change": "+2.1%" }
A big number. value is a string you format ("$67.2k", "72°", "4"); keep it to about 8 characters or it shrinks. change is short ("+2.1%"); its sign colours it green or red, or set trend: up, down, flat. Compact: two sit side by side.
list
{ "type": "list", "title": "Top stories", "items": [ { "title": "Fed holds rates", "detail": "Reuters", "url": "https://…" } ] }
Up to 8 rows (strings work as shorthand). Small shows 3 rows, medium 4, large 6. On small and medium each row is one line (about 40 characters on its own, 23 next to numbers); large allows two lines and shows detail on the right. title shows on large only. Each row can open its own url on medium and large.
progress
{ "type": "progress", "label": "Sprint", "value": 0.6 }
A bar with the percentage. value 0-1 or 0-100.
gauge
{ "type": "gauge", "label": "Steps", "value": 7200, "max": 10000, "text": "72%", "style": "ring" }
style: "ring" (default) is a ring with text in the middle (4 characters or fewer reads best; default is the percentage). It's compact, so two rings or a ring and a metric sit side by side. style: "bar" is a capacity bar. min defaults to 0, max to 1 (or 100 when value is over 1). On the circular Lock Screen widget a gauge becomes the system ring.
chart
{ "type": "chart", "label": "BTC 24h", "values": [66.0, 66.4, 67.1, 66.8, 67.5, 68.2, 67.9], "value": "$67.9k", "change": "+2.1%", "style": "line" }
A sparkline: no axes, just the shape. values: 2-48 numbers, oldest first. style: line (default), area (line with a fill) or bar. value and change are optional and show above it. Takes the full width.
countdown
{ "type": "countdown", "label": "Boarding", "to": "2026-10-27T17:00:00Z", "from": "2026-10-27T09:00:00Z", "style": "timer" }
Ticks on the phone with no updates from you. to: an ISO 8601 date. style:
relative(default): "2 hr, 5 min", then "Now" once it passes.timer: "2:05:09", counting every second; once the date passes it counts up (use it as a stopwatch: "time since").date: "October 27, 2026".time: "5:00 PM".
from adds a bar that fills from from to to, drawn live by the phone (a flight, a download, a cooking timer).
clock
{ "type": "clock", "label": "Tokyo", "timezone": "Asia/Tokyo" }
The live time in an IANA zone. Compact. "digit_sum": true also shows the digits added up (1:26 → 1+2+6 = 9).
checklist
{ "type": "checklist", "title": "Today", "items": [ { "title": "Gym", "done": true }, "Buy milk", "Call mom" ] }
The user ticks items right on the widget (and in the app). Livi saves the tick and gives you an event (section 11). Up to 8 items; small shows 3, medium 4, large 6. Titles up to 80 characters; keep them short. When you update the widget, send the current done states (read them with livi_list_widgets) or you'll untick what the user ticked.
buttons
{ "type": "buttons", "items": [
{ "label": "Snooze", "symbol": "moon.fill", "id": "snooze" },
{ "label": "Done", "symbol": "checkmark" },
{ "label": "Call", "symbol": "phone.fill", "url": "tel:+15555550100" }
] }
1-4 buttons in a row. A button without url sends you an event with its id (defaults to the label, slugified) and shows "Sent" for two minutes. A button with url opens it. When the labels don't fit (small fits about two short ones), the buttons show only their symbols, so give every button a symbol. The user can also put any event button in Control Center, on the Lock Screen corners or on the Action button.
image
{ "type": "image", "url": "https://images.example.com/photo.jpg", "caption": "Today's photo", "fit": "fill", "link": "https://example.com" }
A picture from an https URL (PNG, JPEG, HEIC or WebP, under 8 MB). Here url is the picture; use link for what a tap opens. fit: fill (crop to fill, default) or fit (show it whole). It fills the space left over, at least 60 pt tall. The phone downloads it when it refreshes and keeps a small copy; until then a placeholder shows. Use stable URLs: a new URL is a new download.
7. How each size lays out
These are the exact rules the phone uses. Sizes are in points on a current iPhone; 16 pt padding on every side, and a header (symbol, title, subtitle) about 16 pt tall.
Small (158 × 158). One block, the first, with room for about 100 pt of content. With sizes.small you choose the block, or two (two compact blocks sit side by side; otherwise they stack). Best: one metric, gauge, clock, countdown, a short text, a 3-row list, or a photo. This is also what StandBy and CarPlay show, larger and without its background.
Medium (338 × 158). About 100 pt of content height.
auto: up to two compact blocks (metric, clock, ring gauge) in a 112 pt column on the left, and the first other block on the right. Only compact blocks: side by side. Only other blocks: the first one, full width.- With
sizes.medium: your blocks, stacked (compact ones in pairs), up to 4.
Large (338 × 354). Up to 6 blocks, stacked top to bottom with 14 pt between rows; consecutive compact blocks pair up side by side. About 290 pt of content height.
Extra large (iPad and Mac). Up to 8 blocks, stacked like large. Choose them with sizes.extra_large.
Layouts (medium and larger; small always shows its one block):
auto: the rules above.hero: the first block drawn bigger, then up to 2 (medium) or 3 (large) more in one row under it.grid: blocks in a two-column grid: 2 on medium, 6 on large.list: one block per row: 2 on medium, 6 on large.
Too much content is cut off at the bottom edge, never above the header. The preview shows it and check warns which blocks get cut; fix it with fewer blocks, sizes, or grid.
Choosing blocks per size. sizes maps a size to block indexes (0 is the first block):
{ "sizes": { "small": [2], "medium": [0, 2], "large": [0, 1, 2, 3] } }
Sizes you leave out use the defaults. The same indexes apply inside each version, so keep versions' blocks in the same order.
Approximate character budgets (SF Pro):
| Where | Fits about |
|---|---|
| Title on small / medium and large | 15 / 35 characters |
| Metric value (small / next to a list on medium) | 8 / 9 characters |
| List or checklist row, medium, full width / next to numbers | 40 / 23 characters |
| Text body, small / medium / large | 60 / 120 / 280 characters |
| Lock Screen inline | 20-28 characters |
| Lock Screen rectangular | 2 lines of ~22 characters |
8. Lock Screen
Three shapes, all from the same widget:
- Rectangular (170 × 72): the title, then one line from a block. A gauge shows a bar; a timer countdown shows the live timer (and its bar if it has
from). Pick the block withlock.rectangular(default: the first). - Circular (72 × 72): a ring for a gauge or progress, a short value for a metric, the time for a clock, a live timer for a countdown, or short text. Pick the block with
lock.circular(default: the first that suits). - Inline (one line above the clock):
lock.inline, up to 40 characters (20-28 fit), withlock.symbolin front. Default: a line from the first block.
{ "lock": { "inline": "Flight in 5 hr", "circular": 0, "rectangular": 1, "symbol": "airplane" } }
The Lock Screen draws everything in one tint over the wallpaper, so colours don't show there; shape and text do. Don't put secrets on a Lock Screen widget without private or sensitive.
9. Look
{ "theme": { "style": "glass", "font": "serif" }, "layout": "hero" }
theme.style:system(default) andglass: Livi's frosted glass. Pearl in light mode, smoky glass in dark mode, and real see-through Liquid Glass when the user's Home Screen appearance is Clear or Tinted (touch and hold the Home Screen, Edit, Customize, Clear). iOS only allows see-through widgets in those two appearances; in Default and Dark every widget is opaque. If the user asks for "transparent" or "liquid glass", use this and tell them about Clear.light,dark: plain white or near-black, always.solid: one colour,"color": "#1E3A8A".gradient: twocolors, top-left to bottom-right.image: a photo background,"image": "https://…", darkened bydim(0-0.9, default 0.35) so text reads. Text turns white.
theme.font:default(SF Pro),rounded(SF Rounded),serif(New York) ormono(SF Mono). Numbers use SF Rounded unless you pick a font.theme.text:auto,lightordark. Auto picks readable text for the background.
What iOS does to your colours. In the Home Screen's Tinted and Clear appearances, iOS removes your background and draws everything in one tint, with your accent marks (symbol, dots, rings, charts, buttons, checkmarks) brighter than the rest; photos turn grey. In StandBy and CarPlay the background is removed. So never rely on colour alone to say something: a red number should also have a "-" sign.
Redesigning: when the user asks you to redesign or restyle a widget, call livi_list_widgets, keep its content, and change theme, layout, symbol, accent, sizes and how the blocks are grouped. Preview two or three directions, pick the best, then describe the new look in a line.
10. Versions: by time, day or place
Give a widget variants and the phone switches between them on its own, with no agent running and no network needed:
{
"slug": "my-day", "title": "My day", "symbol": "sun.max.fill",
"blocks": [{ "type": "text", "text": "Nothing planned." }],
"variants": [
{ "when": { "from": "05:00", "to": "10:00" }, "title": "Morning news", "relevance": 60,
"blocks": [{ "type": "list", "items": ["Markets open higher", "Rain after 3pm"] }] },
{ "when": { "from": "10:00", "to": "17:00", "days": ["mon","tue","wed","thu","fri"] }, "title": "Work",
"blocks": [{ "type": "metric", "label": "Open PRs", "value": "4" }] },
{ "when": { "from": "17:00", "to": "22:00" }, "title": "Shopping list",
"blocks": [{ "type": "checklist", "items": ["Milk", "Eggs", "Coffee beans"] }] },
{ "when": { "place": "home" }, "title": "At home",
"blocks": [{ "type": "text", "text": "Bins out tonight.", "style": "title" }] }
]
}
whencombines any of:fromandto: the phone's local 24-hour time. The window can wrap past midnight.days:montosun.place:home,work,away(not at any saved place), or any other place the user saved in Livi. Thecheckwarns if they haven't saved it.
- The first matching variant wins, so put the most specific first. If none match, the widget's own
blocksshow. - Up to 6 variants. Each has its own
blocks, and optionallytitle,subtitleandrelevance. - Preview each version:
livi_preview_widgetwith"at": "07:30"or"place": "home".
11. Taps: buttons, checklists and Control Center
When the user taps an event button or ticks a checklist item, Livi records it. Read them:
// livi_get_events { "wait_seconds": 30 }
{ "events": [
{ "widget": "showcase", "kind": "button", "target": "snooze", "value": { "label": "Snooze" }, "source": "widget", "created_at": "…" },
{ "widget": "showcase", "kind": "check", "target": "Call mom", "value": { "done": true, "block": 2, "item": 2 }, "source": "widget", "created_at": "…" }
] }
- Each event comes back once. Pass
peek: trueto look without marking them read,slugfor one widget, andwait_seconds(up to 50) to wait for the next tap. sourceiswidget,apporcontrol(Control Center, Lock Screen corner or Action button).- Checklist ticks are already saved on the widget; you don't need to update it. Button presses do nothing else until you act, so act: for example, on "snooze", move the reminder and update the widget.
- Taps only reach you when you look. For buttons that need a quick response, run a loop that calls
livi_get_eventswithwait_seconds, or check on your schedule. Tell the user how quickly you'll respond. - Buttons and checklists don't work while the phone is locked (iOS rule); they work on the Home Screen, in StandBy when unlocked, and in the app.
Control Center: every button without a url can also be added by the user as a control: open Control Center, tap +, Add a Control, search Livi, choose Agent button, then pick yours. The same control works on the Lock Screen corners and the Action button. Mention it when a button is something they'll press often.
12. Keeping it updated
Widgets change when something changes them. Pick the right tool:
| The content | Use |
|---|---|
| Time, countdowns, timers, time-of-day or place versions | Blocks and variants. No updates needed. |
| Public data from a URL (prices, feeds, weather, status pages) | livi_schedule: Livi fetches it in the cloud. |
| Anything that needs your reasoning or private data (summaries, inbox, judgement) | Schedule yourself and call livi_set_widget. |
Every livi_set_widget reaches the phone within seconds (Livi sends a widget push). iOS limits how often a widget redraws on its own (about 40-70 times a day), so updating more than every 15 minutes rarely helps.
Scheduled jobs. livi_schedule saves a job that Livi runs in the cloud every N minutes, even when you and the user's computer are off:
{
"name": "btc-price", "widget_slug": "crypto", "every_minutes": 15,
"fetch": { "url": "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd&include_24hr_change=true" },
"values": { "btc": "bitcoin.usd", "change": "bitcoin.usd_24h_change" },
"widget": {
"title": "Bitcoin", "subtitle": "{{now|time}}", "symbol": "bitcoinsign.circle.fill",
"theme": { "style": "gradient", "colors": ["#1E1B4B", "#4338CA"], "font": "rounded" },
"blocks": [{ "type": "metric", "label": "BTC", "value": "{{btc|money}}", "change": "{{change|percent}}", "trend": "auto" }]
}
}
fetch.url: any https URL that returns JSON, RSS/Atom or text (1 MB max). No secrets in URLs: they're stored.values: names mapped to JSON paths ("data.0.price"), or a list such as{"stories": {"path": "articles", "title": "title", "detail": "source.name", "url": "url"}}. RSS feeds automatically giveitems(title and link) andtitle.widget: the template: any widget field, blocks included. Strings can use{{name}},{{name|money}},{{name|money:EUR}},{{name|number}},{{name|round:2}},{{name|percent}},{{name|upper}}and{{now|time}}. Use"items": "{{stories}}"to fill a list from a list value.- Timing:
every_minutesis 5 to 1440; 15 or more is kinder. - First run: the job runs once immediately and returns
first_run(the values and the widget) andcheck. A broken URL or path fails right away with the reason.livi_test_jobtries a job without saving. - Reuse the same
nameto change a job;livi_delete_jobstops it.livi_list_jobsshowslast_statusandlast_error. The user can pause or delete jobs in the app.
Scheduling yourself: a Claude Code scheduled task or routine (/schedule), or cron running claude -p "Update my Livi news widget with today's top 5 AI headlines" --allowedTools mcp__livi__livi_set_widget,mcp__livi__livi_preview_widget,WebSearch. Tell the user how often it will refresh. Don't promise "live" data you won't update.
Placing it: a new widget appears in the app right away. To put it on the Home Screen, the user touches and holds the Home Screen, taps Edit, then Add Widget, chooses Livi, then Agent widget, picks a size, and chooses yours with Edit Widget. For the Lock Screen: touch and hold the Lock Screen, Customize, Lock Screen, tap the widget area, Livi. Tell them this the first time.
13. Asking the human
Ask before anything risky, irreversible, costly or outward-facing: deploys, sending emails or messages, payments, deleting data, posting publicly.
{ "question": "Deploy v2.3 to production?", "details": "12 files changed, all tests green.", "risk": "high" }
kind:decision(default): two buttons.optionsdefaults to["Approve", "Reject"], or pass your own two.choice: 2 to 4options.input: a free-text reply, with an optional one-tapdefault_answer.
risk:low,medium(default) orhigh. High risk shows in red.- The phone gets a notification, and the Needs you widget shows the question with buttons.
livi_ask waits up to wait_seconds (0 to 50). If it comes back with status: "pending", call livi_get_answer again. Keep waiting while your task is blocked. Never guess or assume an answer, and never proceed on a risky step without one. If the question no longer matters, call livi_cancel_request.
{ "request_id": "…", "status": "answered", "answer": "Approve", "answered_from": "widget" }
status can be pending, answered or cancelled. The user can always answer in their own words ("Other"), so answer may not be one of your options. Read it, and if it changes the plan, follow it.
Questions are for decisions. For something the user does again and again (snooze, mark done, log a glass of water), a widget button or checklist is better.
14. Reading the phone
livi_get_phone_context (HTTP action get_context) returns what the user chose to share:
{ "shared": true, "place": "home", "places": ["home", "work"], "battery": 64, "charging": false,
"low_power": false, "timezone": "Europe/London", "local_time": "2026-10-27T07:58:00+0000", "city": "London, UK",
"updated_at": "…" }
placeis a saved place's name,away, orunknown. Livi never shares exact coordinates.- Fields are missing when the user turned that kind of sharing off.
shared: falsemeans sharing is off; suggest Livi, Settings, Your phone. - The phone updates this when it arrives at or leaves a place and when the app opens, so check
updated_at. - Use it to decide what to show and when to ask: skip a "leaving soon?" question if they're home, hold a non-urgent question while the battery is low, use
placesfor place versions.
15. HTTP API (no MCP)
The same actions over plain HTTP:
curl -s https://ujtuxanqlzmhnrzijfoo.supabase.co/functions/v1/agent-api \
-H "Authorization: Bearer lv_YOUR_TOKEN" \
-d '{"action":"set_widget","slug":"hello","title":"Hello","blocks":[{"type":"text","text":"Connected.","style":"title"}]}'
Actions: whoami, list_widgets, set_widget, preview_widget ("raw": true returns the PNG), delete_widget, get_events, ask (returns at once with a request_id), get_answer, list_requests, cancel_request, get_context, schedule, test_job, list_jobs, delete_job.
Parameters are the same as for the tools. Errors come back as {"error": "…"} with a 4xx status.
16. Errors
| Error | Meaning |
|---|---|
| 401 "token isn't valid" | The user disconnected you. Ask for a new code. |
| 403 "paused" | The user paused you in the app. Tell them, and wait. |
| 400 with a field name | Fix that field. The message says exactly what's wrong, like blocks[2]: chart needs "values", 2 to 48 numbers. |
| 404 | No widget with that slug. livi_list_widgets shows them. |
| 409 on a tap | The widget changed under the user's tap; nothing to do. |
| 429 | Widget or job limit reached. Delete one or reuse a slug. |
17. Recipes
Stock with a chart, small shows just the price, Lock Screen ring for the day's move
{ "slug": "aapl", "title": "Apple", "symbol": "apple.logo", "accent": "#30B856",
"blocks": [
{ "type": "metric", "label": "AAPL", "value": "$291.13", "change": "+1.2%" },
{ "type": "chart", "label": "Today", "values": [287.1, 288.4, 289.9, 289.2, 290.5, 291.1], "style": "area" },
{ "type": "gauge", "label": "Day range", "value": 0.82, "text": "82%" } ],
"sizes": { "small": [0], "medium": [0, 1] }, "lock": { "circular": 2, "inline": "AAPL $291 +1.2%" } }
Flight: live timer with a progress bar, photo background, high relevance
{ "slug": "trip", "title": "LHR → JFK", "symbol": "airplane", "accent": "#FFD60A", "relevance": 90,
"theme": { "style": "image", "image": "https://images.unsplash.com/photo-1436491865332-7a61a109cc05?w=800", "dim": 0.45 },
"layout": "hero",
"blocks": [
{ "type": "countdown", "label": "Boarding", "to": "2026-10-27T17:00:00Z", "from": "2026-10-27T09:00:00Z", "style": "timer" },
{ "type": "metric", "label": "Gate", "value": "B32" }, { "type": "metric", "label": "Seat", "value": "14A" } ],
"sizes": { "small": [0], "medium": [0, 1, 2] }, "lock": { "inline": "Boarding 5:00 PM · B32", "rectangular": 0 } }
Habits the user ticks on the widget
{ "slug": "habits", "title": "Today", "symbol": "checkmark.circle.fill", "accent": "#FF9F0A",
"blocks": [ { "type": "checklist", "items": ["Water 2L", "Walk 30 min", "Read 20 pages", "No phone after 10"] },
{ "type": "gauge", "label": "Done", "value": 0, "max": 4, "text": "0/4" } ],
"lock": { "circular": 1 } }
Then poll livi_get_events, and on each tick update the gauge (value and text), keeping the done states.
Approve or snooze a reminder from the Home Screen
{ "slug": "reminder", "title": "Pay rent", "symbol": "house.fill", "accent": "#FF453A", "relevance": 80,
"blocks": [ { "type": "text", "text": "Due tomorrow, $1,850", "style": "title" },
{ "type": "buttons", "items": [ { "label": "Paid", "symbol": "checkmark", "id": "paid" },
{ "label": "Tomorrow", "symbol": "moon.fill", "id": "snooze" },
{ "label": "Bank", "symbol": "building.columns", "url": "https://bank.example.com" } ] } ],
"sizes": { "small": [0, 1] } }
Photo of the day
{ "slug": "photo", "title": "Photo of the day", "symbol": "photo",
"blocks": [ { "type": "image", "url": "https://images.example.com/today.jpg", "caption": "Lofoten, Norway", "link": "https://example.com/today" } ] }
Good habits
- Preview before you tell the user it's done (section 4). Fix every warning.
- Reuse slugs. One widget per topic, updated in place, beats ten stale ones.
- Put the most important block first; small shows only that unless you choose with
sizes. - Numbers as short preformatted strings:
"$67.2k", not"67241.3378". - Put a time in
subtitle("9:00 AM") when freshness matters. - Don't rely on colour alone: tinted and clear Home Screens remove it.
- Give every button a symbol; on small only the symbols may fit.
- One question at a time, in one short line, with the detail in
details. Say what you'll do with each answer before you ask.