A camera for headless agents.
Your agent can't hold a phone. Your human can. Squint turns one API call into a link and a QR code, checks the answer while the human is still standing there, and hands back JSON instead of a photo.
Hover to show Squint.
Be the agent for a minute
Pick a kind of ask and create it. Scan the QR code with your phone (or open the link in another tab), do what it says, and watch the JSON land here. The page below is talking to the real API.
Beta, no signup. Limited to 30 new asks an hour per IP. Don't type anything private, the ask is shown to whoever opens the link.
Nothing yet. Create an ask and a QR code shows up here, with a live timeline underneath.
What changes for your agent
Asking a human for a photo is a conversation. Every retake is another turn, and every attempt lands in your context as image tokens. Squint moves the retakes to the phone, where the human is, and keeps them out of your context.
Without Squint
You receive every photo and judge it yourself.
- agent Send a photo of the water meter.
- human photo ~1,500 tok
- agent Too blurry, retake?
- human photo ~1,500 tok
- agent Digits are cut off. Once more?
- human photo ~1,500 tok
- agent Got it: 48213.
- 3 images in context, 6 turns, about 4,800 tokens
With Squint
One call out, one small JSON back. The retakes happen on the phone.
- agent POST /v1/asks {"kind":"photo",
- "ask":"the water meter",
- "extract":{"reading":"number"}}
- phone blurry, retake. cut off, retake. good.
- squint {"fields":{"reading":48213},
- "confidence":0.97,"quality":"good"}
- 0 images in context, 1 turn, about 60 tokens
Numbers are illustrative (about 1,500 tokens per phone photo, 120 per extra turn). The demo above shows the estimate for your actual run.
Four things a phone can give you
Each one returns a small typed object. Only photos go through vision.
Photo
Claude checks it before the human puts the phone down. Blurry, dark, cropped or the wrong thing means an instant retake.
{"fields":{"reading":48213},
"description":"Water meter, digits clear",
"confidence":0.97,"quality":"good"}
Location
One tap, browser geolocation, with an accuracy figure so you know how much to trust it.
{"lat":52.52,"lng":13.405,
"accuracy_m":18}
Choice
Two to eight short options as big tap targets. Good for "which one did you mean".
{"choice":"tacos","index":1}
Text
A short typed answer, up to 280 characters, with an optional placeholder hint.
{"text":"Leave it with the neighbour"}
Plug it in
No signup, no API key. You get a bearer token back when you create an ask, and it only opens that one ask.
Create the ask, show the url to your human, then long-poll for the answer.
# 1. create
curl -s https://squint.elsisi.workers.dev/v1/asks \
-H 'content-type: application/json' \
-d '{"kind":"photo","ask":"a clear photo of the water meter",
"extract":{"reading":"number: the digits on the meter"}}'
# -> {"id":"...","url":"https://.../s/...","token":"sq_...","status":"pending",...}
# 2. send `url` to your human, then wait (returns when done, or after 25s)
curl -s 'https://squint.elsisi.workers.dev/v1/asks/ID?wait=25' \
-H 'authorization: Bearer sq_TOKEN'
# -> {"status":"done","result":{"fields":{"reading":48213},...},"tokens":{...}}
Remote MCP over streamable HTTP. Tools: squint_ask, squint_wait, squint_cancel.
claude mcp add --transport http squint https://squint.elsisi.workers.dev/mcp
Other clients: point them at the same URL as a remote HTTP server.
{ "mcpServers": { "squint": { "type": "http", "url": "https://squint.elsisi.workers.dev/mcp" } } }
Plain fetch, no SDK. Works in Workers, Node 18+ and Deno.
const base = "https://squint.elsisi.workers.dev";
const res = await fetch(`${base}/v1/asks`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
kind: "photo",
ask: "a clear photo of the water meter",
extract: { reading: "number: the digits on the meter" },
}),
});
if (!res.ok) throw new Error(JSON.stringify(await res.json()));
const { id, url, token } = await res.json();
// tell your human to open `url`, however you talk to them
let ask;
do {
const r = await fetch(`${base}/v1/asks/${id}?wait=25`, {
headers: { authorization: `Bearer ${token}` },
});
ask = await r.json();
if (!r.ok) throw new Error(JSON.stringify(ask));
} while (ask.status === "pending" || ask.status === "opened");
console.log(ask.result); // { fields: { reading: 48213 }, confidence: 0.97, ... }
Everything an agent needs to integrate without reading this page.
- /llms.txt (also at /agents.md): the short plain-text guide
- /openapi.json: the full OpenAPI description
POST /mcp: the MCP endpoint- /healthz: liveness
Webhook instead of polling? Pass callback_url (https, public hosts only) when you create the ask.
What we keep, and what we don't
- Photos are never stored. The image goes to Claude for the check, lives in memory for that one request, and is dropped. It is not written to disk or logged, and your agent never sees it.
- Sensitive asks are blocked. Asks for passwords, card numbers, IDs, one-time codes or seed phrases get a 422. The phone page also tells people never to share them, and rejects photos of documents.
- Links expire. 15 minutes by default, up to an hour. Finished asks are deleted after 24 hours.
- Rate limited per IP, and webhooks only go to public https hosts, with no redirects followed.