Verified 2026-10-11: POST /rest/perplexity_ask/graphql with the persisted
read-only query LibraryThreadsRelayQuery answers 401 'Authentication required'
for a dead credential (or a browser cookie) and 200 with data for a live OTP
Bearer. Unlike /api/auth/session ({} for dead credentials, ambiguous) it is
unambiguous, and unlike the incognito query probe it consumes no query and adds
nothing to the account.
- probe.ts: probeGraphql() is now the primary probe in probeCredential();
probeSession() and probeWithAsk() remain as fallbacks when it is inconclusive.
ask:false still stops before the query-costing probe.
- tests: GraphQL probe mapping (200/401/500/error payload), request shape
(persisted query hash, Bearer header), and probeCredential ordering
- README/AGENTS/CHANGELOG: document the auth model (Bearer-only; browser cookies
are rejected by the API) and the new probe
Measured on the live credential: valid in ~340 ms, no query consumed.
pi-perplexity
A pi extension that gives your coding agent real-time web search powered by your Perplexity Pro or Max subscription
Requirements
- pi coding agent with its bundled Node runtime (Node 18.14.1+ if running outside pi)
- A Perplexity Pro or Max subscription
- macOS (for zero-interaction auth), an interactive terminal (for email OTP), or a signed-in browser session cookie/token
Installation
pi install npm:pi-perplexity
Or from GitHub:
pi install github:ivanrvpereira/pi-perplexity
Authentication
Run login once:
/perplexity-login
This usually reuses an existing cached login, borrows the Perplexity macOS app login if available, or asks for your email OTP code.
The email is asked only once: after a successful OTP login it is remembered in ~/.config/pi-perplexity/profile.json (mode 0600, email only, no credentials), which survives /perplexity-login --force. To sign in with a different account, pass it explicitly:
/perplexity-login --force --email other@example.com
On Linux the macOS desktop-app token is never available, so email OTP (or browser login) is the path used there.
If login fails with a Cloudflare “Just a moment...” page, use browser login instead:
/perplexity-login --browser
Browser login
Use this on Linux/headless machines when direct OTP is blocked.
- Open
https://www.perplexity.aiand sign in. - Open browser DevTools → Network.
- Reload the page, or ask one Perplexity question.
- Right-click a
www.perplexity.airequest, preferablyperplexity_ask. - Choose Copy → Copy as cURL.
- Paste the copied cURL command into the pi prompt.
The copied text must include cookies. A good copy contains one of -b, --cookie, or Cookie:, and should include __Secure-next-auth.session-token. If it does not, copy a different request.
You can also paste just the request Cookie: header, or just the __Secure-next-auth.session-token cookie value. Full cURL is recommended because it also includes Cloudflare cookies like cf_clearance.
The token is saved to ~/.config/pi-perplexity/auth.json (mode 0600) and reused across sessions. On auth failure, run /perplexity-login --force to clear and re-authenticate; the remembered email is kept in profile.json, so the login flow does not ask for it again.
Session lifetime and renewal
Perplexity sessions last about 30 days, and there is no refresh-token grant: an expired credential cannot be renewed, only replaced. When the stored session dies, a search is answered with a login wall (HTTP 200, body Sign up and repeat your request.) instead of a 401/403, which the extension reports as an AUTH error.
Browser cookies do not help (verified 2026-10-11). With a live, freshly rotated Firefox session — the browser was signed in and able to search — replaying the cookies from outside the browser was rejected: /api/auth/session answered {} and perplexity_ask answered the login wall. That held with the session cookie alone, with the full cookie set (cf_clearance, __cf_bm, __cflb, pplx.session-id, …), with Chrome and Firefox user agents, and even with a browser-matching TLS fingerprint (curl_cffi impersonate="chrome131"/"firefox133"). The API appears to accept only the app-issued Bearer credential from the OTP flow.
Consequences:
/perplexity-login --force(email + OTP) is the only working renewal path, needed at most once per ~30 days. The email is remembered, so only the OTP code is required.- The Firefox-cookie channel still exists in the code (
src/auth/browser-cookies.ts,src/auth/renew.ts) but is opt-in and off by default: setPI_PERPLEXITY_BROWSER_RENEW=1to let the automatic paths try it./perplexity-status --renewattempts it explicitly regardless. - A candidate cookie is always probed before being written, so a stale browser cookie — browsers keep session cookies long after the server-side session is gone — is never stored. The probe is a persisted read-only GraphQL query (
LibraryThreadsRelayQuery):401 Authentication requiredmeans dead,200with data means live. It costs no query and nothing is added to the account; the session endpoint and a tiny incognito query are only fallbacks when the GraphQL probe is inconclusive. /perplexity-statusreports the credential type, validity, when it was obtained and the estimated expiry; a warning appears at startup after ~25 days.
/perplexity-status # validity, when the credential was obtained, estimated expiry
/perplexity-status --renew # try to adopt a live browser session (opt-in channel, not expected to work)
/perplexity-login --force # email + OTP login: the only working path
Environment variables
| Variable | Description |
|---|---|
PI_AUTH_NO_BORROW=1 |
Skip macOS desktop app extraction and go straight to email OTP |
PI_PERPLEXITY_TOKEN |
Raw Perplexity session token/JWT/JWE copied from a browser or another machine |
PI_PERPLEXITY_COOKIE / PI_PERPLEXITY_COOKIES |
Full Perplexity Cookie header copied from a signed-in browser |
PI_PERPLEXITY_EMAIL |
Pre-fill the email prompt (takes precedence over the remembered profile email; useful for non-interactive setups) |
PI_PERPLEXITY_OTP |
Pre-fill the OTP prompt |
PI_PERPLEXITY_BROWSER_RENEW=1 |
Opt in to the browser-cookie renewal channel (verified ineffective against the API; off by default) |
Usage
Once installed, the agent automatically calls perplexity_search whenever it needs current information. You can also ask it directly:
"Search Perplexity for the latest React 19 release notes"
Tool parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
query |
string | ✅ | The search query |
recency |
string | — | Filter by age: hour · day · week · month · year |
focus |
string | — | Perplexity Focus Mode (source domain): internet · academic · writing · wolfram · youtube · reddit · math · social · finance (default internet) |
limit |
number | — | Max sources to include (1–50) |
Focus Modes let the agent route the search to a specific knowledge source:
academic → peer-reviewed/scholarly (PubMed, arXiv, Semantic Scholar), youtube → video transcripts, reddit → community/opinions, wolfram/math → computations, social → social platforms, finance → financial/SEC data, writing → model-only (no live search).
Model selection is configured globally with /perplexity-config or PI_PERPLEXITY_MODEL; it is not exposed as a tool parameter, so agent-generated tool calls cannot accidentally override your configured model.
Output format
The tool returns structured text the agent can reason over:
## Answer
React 19 introduces Actions, use() hook, and improved Server Components...
## Sources
3 sources
[1] React 19 Release Notes (1d ago)
https://react.dev/blog/2024/12/05/react-19
React 19 is now stable. This release includes Actions for async...
[2] What's New in React 19 (3d ago)
https://vercel.com/blog/react-19
A deep dive into the new primitives landing in React 19...
## Meta
Provider: perplexity (oauth)
Model: pplx_pro_upgraded
Queries always send is_incognito: true, so searches never appear in your Perplexity web history.
How It Works
The extension calls Perplexity's internal SSE endpoint (perplexity_ask) using your subscription credentials obtained from the macOS app, email OTP, or a browser-imported session. Responses stream as incremental events that are merged into a final result. Network calls use the Node runtime already provided by pi; no extra runtime is required. Email OTP auth requires Headers.getSetCookie() support so auth cookies are exposed reliably.
Development
npm install # Install dev dependencies
npm test # Run tests
npm run typecheck # Type check
Optional live model-selection E2E test (requires cached auth from /perplexity-login):
PI_PERPLEXITY_E2E=1 npm test
PI_PERPLEXITY_E2E=1 PI_PERPLEXITY_E2E_MODELS=pplx_pro_upgraded,gpt54 npm test
License
MIT — see LICENSE for details.
Disclaimer
This project is intended for educational and demonstration purposes only. It reverse-engineers an undocumented internal endpoint and uses credentials borrowed from the Perplexity macOS desktop app. This likely violates Perplexity's Terms of Service. Use at your own risk — your account may be suspended. The author makes no warranties and accepts no liability for any consequences of its use.