Connect any agent
Connect any agent to Pushary over MCP or plain HTTP so it can ask you a question on your phone and wait for the answer. Works with n8n, Zapier and LangChain.
Updated
Pushary works with any agent that can call an MCP server or send an HTTP request: an ops bot, a research agent, an n8n or Zapier workflow, a LangGraph graph, or a Python script. The agent sends you a question, your phone shows it, and the agent continues with your answer. You need an API key and nothing else.
Set PUSHARY_API_KEY to your key, then ask your first question:
curl -X POST https://pushary.com/api/v1/server/ask \
-H "Authorization: Bearer $PUSHARY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"question":"Deploy the new build to production?"}'Your phone shows a yes or no question. Answer it and the call returns "answered": true with "value": "yes" or "value": "no".
Using Claude Code, Codex, Gemini CLI, Cursor or Hermes? Run the quickstart instead. One command installs the hooks, so risky actions stop until you approve them.
Get your API key
Open Settings > API keys at pushary.com/dashboard/agent/settings and click Create Key. The key looks like pk_xxx.xxx.
Export it where your agent runs:
export PUSHARY_API_KEY="pk_xxx.xxx"Already ran npx pushary@latest setup? The key it saved works for /ask and /send too. The subscriber list, campaign, template and flow endpoints need a key created here.
Run the curl command above. Your phone shows the question.
No question on your phone? Install the Pushary app from the App Store or Google Play, sign in with the same account, and try again.
Pushary can only block an action before it runs when the agent has a hook for it, as in the supported agents with enforced approvals. Any other agent gets questions and notifications. It decides for itself when to ask and must respect your answer.
Ask a question and wait for the answer
POST https://pushary.com/api/v1/server/ask sends the question to your phone app. It can also reach your browser and Slack, if you connected them. By default the call blocks until you answer, for up to 30 seconds.
curl -X POST https://pushary.com/api/v1/server/ask \
-H "Authorization: Bearer $PUSHARY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"question": "Send the campaign to all 4,000 contacts?",
"type": "confirm",
"agentName": "Marketing agent",
"timeoutSeconds": 50
}'An answered question returns:
{
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"question": "Send the campaign to all 4,000 contacts?",
"type": "confirm",
"status": "answered",
"answered": true,
"value": "yes"
}The response also carries liveUrl (a page where you can answer), pollUrl, expiresInSeconds (600) and mode (your approval mode).
Proceed only when answered is true and value is "yes". Anything else is not a yes.
| Result | What it means | What to do |
|---|---|---|
"status": "answered" | You answered. value holds the answer. | Act on value. |
"status": "timeout" | The call stopped waiting. The question is still open for 10 minutes. | Poll pollUrl, or stop and do not act. |
"status": "stopped" with "handoffAction": "stop" | The kill switch is on for this agent. Nothing was sent. | Stop the run. |
Request fields
| Field | Required | Notes |
|---|---|---|
question | Yes | Up to 8000 characters. |
type | No | confirm (default), select or input. |
options | For select | At least 2 choices. The MCP ask_user tool accepts 2 to 6, so keep to 6 if you use both. value is the chosen option. |
placeholder | No | Hint text for an input question. value is the typed text, up to 5,000 characters. |
context | No | Background shown above the question. |
agentName | No | Shown in the push title as "Marketing agent is asking". |
sessionId | No | Lets a kill switch for this session stop the ask. |
wait | No | true (default) blocks. false returns at once. |
timeoutSeconds | No | How long the call blocks. Default 30, maximum 55. |
callbackUrl | No | HTTPS URL that receives the answer. See the webhook section below. |
Ask without blocking, then poll
Set "wait": false and the call returns at once with "status": "pending" and a pollUrl:
{ "correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "status": "pending", "answered": false, "pollUrl": "https://pushary.com/api/v1/server/ask/a1b2c3d4-e5f6-7890-abcd-ef1234567890" }Poll it with GET. Add ?wait=30 to hold the request open for up to 30 seconds (maximum 55):
curl "https://pushary.com/api/v1/server/ask/a1b2c3d4-e5f6-7890-abcd-ef1234567890?wait=30" \
-H "Authorization: Bearer $PUSHARY_API_KEY"status is pending, answered, expired or cancelled. An unknown ID returns 404.
Cancel a question you no longer need:
curl -X DELETE "https://pushary.com/api/v1/server/ask/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
-H "Authorization: Bearer $PUSHARY_API_KEY"The response says "cancelled": true when a pending question was removed. false means it was already answered, expired or cancelled.
Get the answer on a webhook
Use a webhook when your process cannot wait, for example an n8n or Make workflow. In n8n or Make, pass the URL of a node that waits for a webhook as callbackUrl. The workflow resumes when the answer arrives.
Fetch your signing secret once. The first call creates it:
curl https://pushary.com/api/v1/server/webhook-secret \
-H "Authorization: Bearer $PUSHARY_API_KEY"It returns { "webhookSecret": "whsec_..." }. A POST to the same URL rotates it. Do this before your first webhook ask, or the callback is signed with a secret you cannot check.
Add callbackUrl to the ask. It must be HTTPS and must not point at localhost or a private network address:
curl -X POST https://pushary.com/api/v1/server/ask \
-H "Authorization: Bearer $PUSHARY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"question": "Email the refund confirmation to the customer?",
"wait": false,
"callbackUrl": "https://your-app.com/webhooks/pushary"
}'Verify each callback. When you answer, Pushary sends a POST with this body:
{
"correlationId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"answer": "yes",
"value": "yes",
"answeredAt": "2026-10-04T12:00:00.000Z",
"context": "Only present when you passed context"
}The X-Pushary-Signature header is the hex HMAC-SHA256 of the raw request body, keyed with your webhook secret:
import crypto from 'node:crypto'
const isFromPushary = (rawBody: string, signature: string, secret: string): boolean => {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex')
return expected.length === signature.length
&& crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))
}The callback fires only when someone answers, never on a timeout or a cancel. Return a 2xx status within 10 seconds. Pushary does not follow redirects, and it retries a failed delivery up to 12 attempts in total, so handle the same correlationId twice safely.
The REST API and the MCP server share one question store. An answer from your phone, the Mac app, the web page or Slack settles the question however it was asked.
Send a one-way notification
The MCP send_notification tool reaches your phone app and your browser. You can call it over plain HTTP, with no MCP library:
curl -X POST https://pushary.com/api/mcp/mcp \
-H "Authorization: Bearer $PUSHARY_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"send_notification","arguments":{"title":"Backfill finished","body":"Processed 12,400 rows with 0 errors."}}}'title takes up to 100 characters and body up to 500. The limit is 10 notifications per 5 minutes per site.
POST /api/v1/server/send reaches browser push subscribers only. It does not reach the Pushary phone app. If nobody has subscribed in a browser, it returns 400 No subscribers found matching criteria.
Connect an MCP client
Point any MCP client at the Pushary endpoint. The agent then has ask_user, wait_for_answer, cancel_question, send_notification and propose_scope as tools.
{
"mcpServers": {
"pushary": {
"url": "https://pushary.com/api/mcp/mcp",
"headers": {
"Authorization": "Bearer ${PUSHARY_API_KEY}"
}
}
}
}The official MCP SDKs for TypeScript and Python connect with only the URL and the Authorization header. A custom agent, or a LangChain or LangGraph tool, can use the same settings. For the approval patterns, see Human in the loop.
Cline
Run npx pushary@latest setup --agents cline. It adds Pushary to Cline's MCP settings with your key, and a global rule that tells Cline to ask and notify through Pushary.
To add it by hand, edit ~/.cline/data/settings/cline_mcp_settings.json, the file Cline 4 reads for both the VS Code extension and the Cline CLI. Cline does not fill in ${PUSHARY_API_KEY}, so paste the key itself. Keep "type": "streamableHttp": without it Cline falls back to the older SSE transport and cannot connect to this URL.
{
"mcpServers": {
"pushary": {
"type": "streamableHttp",
"url": "https://pushary.com/api/mcp/mcp",
"headers": {
"Authorization": "Bearer pk_your_key"
},
"timeout": 90
}
}
}Then reload VS Code (Developer: Reload Window). Cline asks before each MCP tool call unless Use MCP servers is ticked in its Auto-approve menu. That setting covers every MCP server in Cline, not only Pushary, so tick it only if you are happy for all of them to run without a click.
Name your agent
Pass agentName (and sessionId if you have one) on every call. The dashboard and the Fleet Board then label the work with your agent's name instead of a generic session. Several agents running at once each show up on their own.
Errors and limits
| Response | Cause |
|---|---|
| 400 | question missing or over 8000 characters, a select with fewer than 2 options, or a callbackUrl that is not HTTPS or points at a private host. |
| 401 | The API key is missing, wrong or revoked. |
403 forbidden, This key cannot access this endpoint. | The key cannot call this endpoint. A key from setup or phone pairing cannot call the subscriber list, campaign, template or flow endpoints. Create a key in Settings > API keys for those. |
403 subscription_required | The workspace has no active plan or trial. |
403 partner_decision_required | Partner plan workspaces use POST /api/v1/server/decisions instead. |
| 429 | 25 questions are already pending for this site, or you hit your plan's request limit. |
If your approval mode is In agent, /ask sends no push and the response says "mode": "terminal_only". To get every question on your phone, open the Pushary app, tap Questions & approvals at the top of your inbox, and pick Always. See Permission policies for all four modes.
Next steps
Ask for approval the safe way
Read the answer, handle a question nobody answered, and cancel stale questions.
Copy a ready-made prompt
Prompts and code for alerts, approvals and backend notifications.
Read the tools reference
Every MCP tool and argument.