Developer docs
Ask Pufy from your own code with a personal API key, or connect it to any app that speaks MCP.
Base URL
export PUFY_API=https://api.pufy.app
The examples on this page read the address from PUFY_API.
API keys
Keys are personal and part of Pufy+. A key asks Pufy as you, with your memory, notes, to-dos, web search and connected apps.
- In the Pufy app, open Settings, then API and MCP.
- Tap New key and name it after where you will use it.
- Copy the key. It starts with
bear_and is shown only once. Pufy keeps only a hash of it.
You can have up to 5 keys. Delete one in the same place to stop it at once. Send the key in the Authorization header:
Authorization: Bearer bear_...
POST /v1/ask
Asks Pufy one question and returns the whole answer as JSON when Pufy has finished. A full turn can take a while if Pufy searches or reads pages, so allow a long timeout (a minute or two).
Request body
| Field | Type | Meaning |
|---|---|---|
text | string, required | What to ask or ask for. Up to 4,000 characters; longer text is cut. |
conversation | string, optional | The id of one of your agents' chats. Leave it out to use your default agent, Pufy. Group chats are not accepted. |
Example
curl $PUFY_API/v1/ask \
-H "Authorization: Bearer $PUFY_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text": "What is on my to-do list today?"}'
Response
{
"text": "You have 3 open to-dos today: ...",
"cards": [],
"conversationId": "..."
}
| Field | Meaning |
|---|---|
text | Pufy's reply. |
cards | Actions waiting for approval in the app, each {id, summary}. Empty when there are none. |
conversationId | The chat the question went to. Pass it back as conversation to keep using it. |
error | Only when the turn failed part way: {code, message}. |
When Pufy wants to act, the reply says so and the card is listed:
{
"text": "I've drafted the event. Approve it in the app.",
"cards": [{ "id": "...", "summary": "Create event: Lunch with Ana, Fri 13:00" }],
"conversationId": "..."
}
The question appears in your chat in the app, marked "(Asked through the API key "name")". Times are read in UTC, so give a time zone when it matters. Files and temporary chats are not available through the API.
GET /v1/cards/{id}
Follow up on a card from an answer: same key, the card's id. You get its summary and what became of it, never what the action returned. Cards from temporary chats and groups are not shown. If you poll, wait a minute or more between checks.
curl $PUFY_API/v1/cards/CARD_ID \
-H "Authorization: Bearer $PUFY_API_KEY"
{ "id": "...", "summary": "Create event: Lunch with Ana, Fri 13:00", "status": "done" }
status is pending (waiting in the app), running, done, failed, declined or expired. An unknown id answers 404 with bear.api_card_not_found.
Errors and limits
Errors are JSON with a message and a code:
{ "message": "That API key is not valid.", "code": "bear.api_key_invalid" }
| Status | Code | Meaning |
|---|---|---|
| 401 | bear.api_key_invalid | The key is missing, wrong or deleted. |
| 400 | bear.api_body | The body is not JSON with a non-empty text. |
| 404 | bear.conversation_not_found | conversation is not one of your agents' chats. |
| 403 | bear.agent_plus_required | The key is valid but its account does not have Pufy+. On /mcp it comes back as a JSON-RPC error (-32001). |
| 429 | common.rate_limited | Too many requests. About 10 a minute for each key (a wrong key counts against its network address instead). Connecting an MCP client (initialize, notifications, tools/list) does not count. Wait the seconds in the Retry-After header, also in the body as retryAfter. |
| 429 | bear.daily_limit | Your daily turns are used up. API questions count like questions in the app; a refused question does not count. The body has resetAt (Unix seconds, the next midnight UTC) and limit. |
MCP server
Pufy is an MCP server, so AI apps can ask it things. The address is the base URL followed by /mcp:
https://api.pufy.app/mcp
- Transport: Streamable HTTP. Every request is one
POSTand gets one JSON reply. There is no event stream and no session. - Protocol versions: 2025-06-18, 2025-03-26 and 2024-11-05.
- Authentication: the same
Authorization: Bearerkey. - One tool,
ask_bear, with one argument,text. It returns Pufy's reply as text, plus a line for each action waiting for approval in the app.
Clients that take a remote MCP address with headers
Add Pufy to your client's mcpServers settings, with the base URL in place of PUFY_API. The app's Copy the MCP settings button gives you this with the address and your key filled in.
{
"mcpServers": {
"pufy": {
"url": "https://api.pufy.app/mcp",
"headers": { "Authorization": "Bearer YOUR_PUFY_KEY" }
}
}
}
Claude Code
claude mcp add --transport http pufy $PUFY_API/mcp --header "Authorization: Bearer YOUR_PUFY_KEY"
Calling it by hand
curl $PUFY_API/mcp \
-H "Authorization: Bearer $PUFY_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": "ask_bear", "arguments": {"text": "Summarise my notes tagged #trip"}}}'
Approvals
Through the API and MCP, Pufy reads, searches and answers on its own. Anything that sends, books or changes something becomes an approval card that waits in the Pufy app. Only you can approve it there. A key can never approve, decline or change a card, and it cannot create automations.
More for people using Pufy: Use Pufy from other apps.