Using your own server
Quick Ask can get its answers from a server you run instead of OpenRouter. That's how I use it myself: I've got a server (I call it my agent archive) with every conversation I've had with my AI agents in it, so when I'm out I can ask "didn't I talk to my agent about…?" and get the answer from those old chats, with the threads it came from shown underneath. You could do the same with your own notes, or anything else you'd like to be able to ask about from your phone.
Your server needs to answer two requests, which are below. In Quick Ask, go to Settings › Answers from, pick Your own server, then type in its address (like https://quick.example.com) and paste a token for it. Quick Ask checks you're happy for your questions to go there before it sends anything at all.
GET /api/quick/ping
Quick Ask calls this whenever it comes to the front, and again when you start talking if it's been 25 seconds, so the connection's already open by the time your question's ready. It has no token and no body. Answer with a 204 and nothing else, as quickly as you can.
GET /api/quick/ping
<- 204 No Content
POST /api/quick/ask
This is the question itself, with the token you pasted into Quick Ask as a bearer token:
POST /api/quick/ask
Authorization: Bearer <your token>
Content-Type: application/json
Accept: text/plain
{
"question": "What did I decide about the veggie patch?",
"history": [
{ "question": "Earlier question", "answer": "Its answer" }
],
"timezone": "Australia/Perth",
"clientTime": "2026-10-23T14:06:00+08:00"
}
| Field | What it is |
|---|---|
question | What you asked, as iOS heard it, trimmed. |
history | Up to three earlier questions and answers from the same conversation, oldest first, so follow-ups like "and the Sun?" make sense. Left out when there aren't any. |
timezone | The phone's time zone, as an IANA name. |
clientTime | The phone's local time in ISO 8601, with its UTC offset, so the answer can know what "today" or "this morning" means. |
The token's whatever you like. Quick Ask keeps it in the iPhone's Keychain and only ever sends it to your server, in that header.
Sending the answer back
Reply with a 200 and a text/plain; charset=utf-8 body, and stream it as it's written rather than all at the end. Quick Ask shows the text as it arrives and starts speaking at the end of the first sentence (or at a comma, once it's 40 characters long), so the sooner the first few words get to the phone, the sooner you hear something.
It's read out loud, so write it the way you'd say it: a sentence to three, no markdown, no lists and no links. If nothing arrives for 30 seconds, Quick Ask gives up on it.
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
X-Content-Type-Options: nosniff
X-Quick-Sources: W3sidGl0bGUiOiJQbGFubmluZyB0aGUgc3ByaW5nIHZlZ2dpZSBwYXRjaCIsInRocmVhZElkIjoidDEiLCJkYXRlIjoiMjAyNi0wOS0xNCIsInVybCI6Imh0dHBzOi8vcXVpY2suZXhhbXBsZS5jb20vdC8xIn1d
Server-Timing: auth;dur=9, retrieve;dur=236
You decided on cherry tomatoes and basil along the back fence...
Send X-Content-Type-Options: nosniff with the 200. Without it, iOS holds back the first 512 bytes of a text/plain answer while it works out what type it really is, so a short answer turns up all at once at the very end instead of word by word. Against my test server the first byte took 1.79 seconds without the header and 0.36 seconds with it.
X-Quick-Sources (optional)
If the answer came from things you can link to, like old chats or notes, send up to three of them and Quick Ask shows each one as a card under the answer that opens its link when tapped. The header's value is JSON, base64url encoded with no padding:
[
{ "title": "Planning the spring veggie patch", "threadId": "t1",
"date": "2026-09-14", "url": "https://quick.example.com/t/1" }
]
That example is the header in the answer above. title and url are needed (an entry without them is skipped), date is YYYY-MM-DD and shown under the title, and threadId is just for you. If the header doesn't decode, Quick Ask shows the answer without cards.
Server-Timing (optional)
Quick Ask times everything, and if you tap the timing line under an answer it shows a breakdown of where the time went. Send a standard Server-Timing header, like auth;dur=9, retrieve;dur=236, and your own steps show up in that breakdown too, which is really handy for working out what's slow. Any names work. Cloudflare's own entries (the cf ones it adds on the way through) are left out.
Errors
When something goes wrong, send the status code with a small JSON body. Quick Ask shows its own message for the status, with yours on the end in brackets (apart from a 401 or 403, where it just says to check the token):
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
{ "error": "Slow down a bit" }
| Status | What Quick Ask says |
|---|---|
401 or 403 | Your server didn't accept the token. Check it in Settings. |
429 | That's a lot of questions in a short time. Give it a minute and try again. |
Any other 4xx | The server didn't accept that question. |
5xx | The server had a problem (the status). Try again in a moment. |
If the answer stops part way through, Quick Ask keeps what it got and says it was cut off.
Questions can come in more than once
To be quick, Quick Ask sends your question at the first short pause (0.3 seconds), while it's still waiting to be sure you've finished. If you carry on talking, it cancels that request and sends the longer question at the next pause. So your server will see some requests that get cancelled part way, and the start of a question before the whole thing. Stop work on a request when the connection closes if you can, and if you rate limit, leave a bit of room for this.
Help
If something here doesn't make sense, or your server's doing what it says and Quick Ask still isn't happy, email me at quickask@mikecann.app.