"If the browser can do it, the browser has to know how."
So here's the thing. Pi doesn't give you an API key. It just doesn't exist. But the website works, right? You go to pi.ai, you talk to Pi, it responds.
The browser is literally making HTTP requests to somewhere. It has to be.
So I went looking.
This is what I found.
Pi is made by Inflection AI. It's not trying to be ChatGPT or Claude or any of the "here's a model endpoint" things.
It's designed to actually talk to you. Like a real conversation. No model selector, no system prompt visible, no "answer format". Just you and Pi having a dialogue.
The vibe is closer to texting a thoughtful friend than using an API. Pi actually remembers context, asks follow-up questions, picks up on tone. It's weirdly natural.
The catch? It's only on their website. You can't integrate it anywhere. You can't use it in your own app. You just go to pi.ai and that's it.
Which is fine for some people. But if you wanted to use Pi somewhere else, you were out of luck.
Until now.
Pi API is a reverse-engineered gateway to pi.ai
So we have Pi, which only works on their website. And we have this project, which makes Pi work like a normal API.
But underneath that simple interface? The browser is doing a lot of work.
- π¬ Creating conversations
- π€ Sending messages
- π₯ Getting streamed responses back
- π§ Keeping track of where you are
- π Managing authentication
- βοΈ Dealing with Cloudflare
All of that is happening in network requests. Requests that the website has to know about because otherwise it wouldn't work.
This project figured out what those requests look like and built a local API bridge that lets you talk to Pi like it actually had an official interface.
your client (OpenAI SDK, custom app, whatever)
β
Pi API (local)
β
pi.ai (actual AI)
You get all the normal OpenAI-compatible stuff. Pi does the thinking. Done.
Pi is not your typical productivity AI. It's built for conversation. Empathetic, context-aware, emotionally intelligent conversation. That's its whole thing.
So what does this unlock when you have an API?
- π€ Plug Pi into any OpenAI-compatible chat app like Open WebUI, LibreChat, or SillyTavern that would otherwise never support Pi
- π§ Build your own companion chatbot on top of Pi's conversational personality without starting from scratch
- π Personal journaling / reflection app since Pi is genuinely good at asking the right follow-up questions and helping you think through stuff
- πͺ Decision-making assistant that actually pushes back, asks questions, and surfaces blind spots instead of just agreeing with you
- π Creative brainstorming tool for stories, ideas, scripts, or whatever you're making
- π Study buddy app that explains things conversationally instead of dumping bullet points at you
And the best part? Pi is completely free. No paid tier, no rate limits, no credit card. Unlimited conversations. So your API calls are technically unlimited too.
Because the interesting question isn't "can I use Pi?"
The interesting question is "how does the website use Pi?"
There's no documentation for this. There's just... the website working. The browser making requests. The whole thing talking to itself.
So the rabbit hole was:
- π How does Pi create conversations? What's the request?
- π¬ How do you send messages? What payload shape does it expect?
- π How does streaming work? What format are the events?
- πͺ Which cookies actually matter? (spoiler: like 3)
- βοΈ How do you keep Cloudflare happy?
- π§ How does Pi know which conversation you're in?
- πͺͺ What headers does the browser send that actually matter?
The website already had all these answers. They just weren't written down anywhere. So I watched the network tab and figured it out.
Two endpoints matter:
https://pi.ai/api/conversations (create a new conversation)
https://pi.ai/api/v2/chat (send a message, get streamed response)
That's it. Everything else is just:
- Making those requests look like they came from a browser (curl_cffi does this with Chrome impersonation)
- Managing your Pi session cookies
- Keeping Cloudflare happy
- Handling the streaming events
- Wrapping it in an OpenAI-compatible API
Normally with an API, you send your entire conversation history every single time:
user: hey
assistant: hey what's up?
user: what were we talking about?
But Pi already has the conversation. Why rebuild it every request?
So instead: when Pi creates a conversation, I just... save the ID. Stick it on the assistant's response as a hidden marker:
assistant response text here
[pi-conv:ABC123]
Next time the client sends a message, the API looks for that marker in the conversation history, finds the ID, and reuses that conversation on Pi's end.
The client never has to know about it. It just works.
Request 1:
create Pi conversation β get ABC123
send message
response: "..." + [pi-conv:ABC123]
Request 2:
find [pi-conv:ABC123] in history
reuse that conversation
send new message to ABC123
response: "..." + [pi-conv:ABC123]
No database needed. Just one tiny marker. That's it.
| π¬ | OpenAI-compatible chat completions (plug in your existing SDK) |
| π | Streaming responses (get tokens as they arrive) |
| π§ | Conversation continuity (Pi remembers the thread) |
| πͺ | Browser session auth (uses your Pi cookies) |
| βοΈ | Cloudflare babysitting (keeps the session alive) |
| π | CORS support (works from browser too) |
| π | Optional local API key (if you want to lock it down) |
| π₯οΈ | Readable logging (actually tells you what's happening) |
| βΎοΈ | Unlimited calls (Pi is free with no rate limits) |
Seriously, use a separate Pi account for this.
This project is reverse engineering a web service that was not designed to be consumed through this interface. Your cookies.json contains authentication material for your Pi session.
If the account matters to you, don't use it here. Use a burner / alt account instead.
- π Don't use your main Pi account
- πͺ Never share
cookies.json - π« Never commit
cookies.jsonto git - π Don't expose your authenticated server publicly
- π§ͺ Treat the account as disposable
If losing the account would ruin your day, don't use that account here.
You're handing a local program the same session information your browser uses to access Pi. Keep that boundary very clear.
- Python 3.9+
- A Pi.ai account (use a burner)
- Cookie-Editor (or any cookie exporter)
- 30 seconds to export your cookies
git clone https://github.com/shumaqueraza/pi-api.git
cd pi-apipip install -r requirements.txt- Go to pi.ai and log in (on your burner account)
- Install Cookie-Editor
- Click the extension β export β copy the JSON
- Save it as
cookies.jsonin the pi-api folder
Folder should look like:
pi-api/
βββ pi-api.py
βββ cookies.json β here
βββ requirements.txt
βββ README.md
python pi-api.pyYou'll see a fancy startup panel with all the settings. Server runs on http://127.0.0.1:8000
Once it's running, if you know the OpenAI Python SDK you already know how to use this:
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8000/v1",
api_key="anything" # doesn't matter if you didn't set one
)
response = client.chat.completions.create(
model="pi",
messages=[
{"role": "user", "content": "tell me something cool"}
]
)
print(response.choices[0].message.content)stream = client.chat.completions.create(
model="pi",
messages=[
{"role": "user", "content": "tell me a story"}
],
stream=True
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")If some app lets you configure an OpenAI-compatible endpoint:
Base URL: http://127.0.0.1:8000/v1
Model: pi
That's literally all you need.
The main endpoint. Send this:
{
"model": "pi",
"messages": [
{
"role": "user",
"content": "hello"
}
]
}Or with streaming:
{
"model": "pi",
"stream": true,
"messages": [
{
"role": "user",
"content": "tell me something"
}
]
}Response is standard OpenAI format.
{
"object": "list",
"data": [
{
"id": "pi",
"object": "model",
"created": 0,
"owned_by": "inflection"
}
]
}{
"status": "ok",
"cookies": 3
}Top of pi-api.py:
PORT = 8000
HOST = "127.0.0.1"
SHIM_API_KEY = ""
COOKIES_FILE = "cookies.json"
CF_REFRESH_SECS = 25 * 60Default: 127.0.0.1 (keeps it local)
If you change it to 0.0.0.0 it'll be accessible over your network.
Don't do this unless you know what you're doing. This server has your Pi session. Treat it like your browser.
Wherever you want it. Default is 8000.
Empty by default (no auth needed).
If you set it:
SHIM_API_KEY = "super-secret-thing"Clients have to send:
Authorization: Bearer super-secret-thing
Path to your cookies JSON.
How often to poke Cloudflare. Default is fine.
Hardcoded at 4000 chars. This is a Pi.ai limit, not an arbitrary choice. Pi itself rejects anything longer, so there's no point changing it.
It's mostly just one file (pi-api.py) with:
| Part | Job |
|---|---|
CookieManager |
Loads your Pi cookies, keeps them fresh |
PiClient |
Talks to Pi's endpoints |
| Conversation handling | Finds and reuses Pi conversation IDs |
| SSE parser | Reads Pi's stream format |
| FastAPI app | Exposes the OpenAI endpoint |
| Rich logging | Makes terminal output actually readable |
Dependencies are intentionally tiny:
curl_cffi (for browser impersonation)
fastapi (for the API)
uvicorn (for the server)
pydantic (for data validation)
rich (for the pretty terminal)
No database. No frontend. No bloat. Just the bridge.
This isn't a full API. It's a bridge to a website. So there are things you just can't do:
- π« No system prompts - Pi's personality is baked in by Inflection. You can't override it, change it, or steer it. Pi talks how Pi wants to talk.
- π No model switching - there's only one model. It's Pi. That's it.
- π 4000 char prompt cap - hard limit from Pi.ai itself, not the code.
- πͺ Cookies expire - you'll need to re-export
cookies.jsoneventually when your session dies. - π€ Single account - everything runs through one Pi session. No multi-user support.
- π‘οΈ No temperature, top_p, or any generation params - Pi doesn't expose any of that. The field is accepted but silently ignored.
If you need any of those things, Pi probably isn't the right model for your use case anyway.
This is the "you get what you get" part of reverse engineering.
Pi can change:
- Their endpoints
- Their request format
- Their headers
- How Cloudflare works
- Their streaming format
- Literally anything
If that happens, the API breaks. That's just how it is.
If you see 401 / 403 / 502:
- Export fresh cookies
- Try again
If that doesn't work:
Pi probably changed something. Which is when the fun part starts. Open DevTools, watch the network tab, figure out what changed, update the code.
That's the whole game.
The terminal is your UI. So instead of:
INFO: something happened
INFO: something else
ERROR: failure
You get actual information that's readable at a glance:
β conversation created
β reuse conversation (prompt=...)
β prompt too long
β auth rejected
Startup info gets its own panel. Errors get their own panel. Things that work are green. Things that might be problems are yellow. Things that broke are red.
Glance at it and you know what's happening.
This is a reverse engineering project. Made because the website already does the thing but didn't document how.
This is not:
- An official Inflection product
- An official Pi.ai library
- Affiliated with Inflection in any way
- A hosted service
- A replacement for pi.ai
This is:
- A local bridge that talks to Pi the same way your browser does
- Something you run on your own machine
- A way to use Pi if you already have a session
- Fun
Inflection doesn't endorse this. Pi.ai doesn't have an official API because Inflection chose not to make one. This project reverse engineers the web interface.
So like. Keep it local. Use a burner account. Don't be weird with it. Review Inflection's terms if you're doing anything beyond just messing around.
Pi API is unofficial. It's not made by Inflection. It talks to Pi using your browser session (the same cookies your browser uses).
That means:
- It could stop working anytime Pi changes something
- Pi could disable your account if they don't like it
- Your cookies have your session auth in them (treat them like passwords)
- You're responsible for what you do with this
- Use a burner account
MIT
The website was never supposed to be the API documentation. Turns out it was all along.