v2.1.0 New: a WebSocket client, Git sync for teams, response views, example responses and secrets in the system keychain →

The API client that stays on your machine.

pyhttp is a local API client — an alternative to Postman that imports Postman collections and runs Postman scripts — with a built-in collection runner, in its own desktop window. Requests live in a SQLite file, every run writes to plain files, and the backend only listens on 127.0.0.1.

Free for everyone, including at work Windows · macOS · Linux

Have pyhttp on a Mac and the in-app update fails? Homebrew 7 must trust the pyhttp tap once: run brew trust pyhttp2/pyhttp in Terminal, then update again. Copy the commands → · Why

The pyhttp desktop app in its dark theme: an Orders API folder in the sidebar with a WebSocket request among the HTTP ones, the request Create order with the named bodies valid order and missing email, a response chip for each body and iteration of the smoke run with a Compare button, the badge Get token: sent first, and the completed run on the right with 8 of 8 sent and 16 of 16 tests passed.
—
Downloads so far
0
Accounts, syncs or telemetry
127.0.0.1
The only address it binds to
pm.*
Postman scripts run unchanged
Imports what you already have
  • Postman v2.1
  • OpenAPI 3
  • Swagger 2
  • HAR
  • cURL
  • CSV & JSON data
Features

Everything you use in Postman. Nothing phoning home.

Build, organise, run and test requests with the tools you already know.

Folders & collections

Nest requests in folders like QA / Users. Drag to reorder, duplicate, move, and run a whole folder at once.

Environments & variables

Dev, QA, Staging and Prod, each with its own {{base_url}}. Prod is flagged red so you never hit it by accident.

Secret variables

Mark a key or password secret and it's masked on screen and saved as •••••• in history and run files.

OAuth 2.0 & every auth type

OAuth 2.0 tokens are fetched, cached and refreshed for you. Also Basic, Bearer, API key and Digest, inherited from folders.

Import & export

Postman collections and environments, OpenAPI 3 / Swagger 2 in JSON or YAML, browser HAR files, or a pasted curl.

History & compare

Every request you send is kept. Filter by status, send again with one click, or diff two responses, also straight from the response bar after sending several bodies.

Named bodies

Keep valid user, missing email and more on one request. Tick the ones to send, every response shows as its own chip, and tests and scripts can target one body or all of them.

Smart body editor

CodeMirror with folding and bracket matching. Ctrl+/ comments a line, Ctrl+B beautifies, and broken JSON is pointed out live.

Response viewer

A collapsible JSON tree. Click a key to copy its path, filter with data.items[*].id, or preview HTML, images and PDF.

Dynamic variables

{{$guid}}, {{$timestamp}}, {{$randomEmail}} and path params like /users/:id. Unresolved ones are flagged before you send.

Cookie jar

Cookies are stored per domain like a browser, sent automatically, and editable in a manager.

Split panes, your layout

Request and response are two panes that scroll on their own, so the URL and the status never leave view. Put the response below or to the right, drag the divider, hide the sidebar with Ctrl+\.

Updates itself

pyhttp checks for new releases at start. The Windows installer, the portable .exe and the Homebrew install download, verify and install the update, then restart.

Windows, macOS, Ubuntu

A per-user Windows installer with no admin rights, a portable .exe, a Homebrew cask or .dmg on macOS, and a .deb for Ubuntu 22.04+ and Debian 12+.

Dependent requests

Point a request at the one that fetches its token. The token request runs first, is reused until the expiry you set, and is refreshed with one retry when the API answers 401.

Parallel sends

Send fifteen bodies or a whole run at once, bounded by a limit you choose, or keep them in order when one request feeds the next. Pick the mode right on the Send button.

Run responses beside the request

While a run goes, every request it sends shows its responses next to it: one chip per body and iteration, so you compare them without opening the report.

Variables file, with a sample

Drive iterations from a CSV or JSON file of variable values, one row per iteration. The run dialog writes a sample with a column for every variable your requests use.

Monitors

Pick requests and an interval; pyhttp re-sends them while the app is open and flags any error, 4xx/5xx or failed test — in the status bar and, if you set one, a webhook. Sparklines show the trend.

AI-ready: a built-in MCP server

Claude Code, Claude Desktop or Cursor can manage your collection over MCP: create and edit requests, send them, run folders, read history. Setup is one copy-paste from Settings → AI (MCP).

Mock servers, on the same port

Answer /mock/payments/users/42 from pyhttp itself — no second process, no second port. Routes take :id params, wildcards and a templated body that can read the incoming request.

Streamed responses

Server-Sent Events, chunked and AI-style streaming endpoints show their body as it arrives. Cancel stops the stream and keeps what came. One tick in the request's Settings tab.

Undo, and a keyboard-first sidebar

Deleted a request or body? Undo is right on the toast. Arrows move through the collection, Enter opens, Space ticks for a run, Delete removes — hands on the keyboard.

New

WebSocket client

Give a request a ws:// or wss:// address, written out or as {{events_url}}/orders, and Send becomes Connect. A live log of messages in both directions, a filter, Ping, and a box to send from. The request's headers, auth and {{variables}} are used for the connection.

New

Git sync for teams

Every request, folder, environment, mock and monitor as a JSON file in your own Git repository, committed and pushed a few seconds after you change it, and pulled back in when a teammate pushes. Share a collection without a cloud account.

New

Response views

Attach your own HTML page to a request or a whole folder and see the response as a chart, a table or a dashboard, in the View tab or in the browser. A starter view is one click away.

New

Example responses

Pin a response to its request. Examples appear in the documentation export, travel with exports and Git sync, and each one can become the answer of a mock route.

New

Projects

Separate workspaces, each with its own requests, environments, history, cookies, mocks and monitors, in its own SQLite file. Switch from the top bar; pyhttp run --project picks one in CI.

New

Secrets in the system keychain

One switch moves the values of secret variables out of the project file and into Windows Credential Manager, the macOS Keychain or the Secret Service. The database then holds no secrets.

New

API documentation, generated

Export a project or a single folder as Markdown or as one self-contained HTML page: requests, parameters, auth, example bodies, pinned example responses and tests, with credentials masked.

New

Large responses, handled

Load the whole body of a multi-megabyte response on demand. A big JSON document is drawn as a window of rows, so 100 000 items scroll, expand, filter by path and search as smoothly as ten.

New

Compare any two responses

Sent several bodies, or ran a few iterations? Compare in the response bar diffs any two: body, headers and the request as sent, with JSON keys sorted so only real changes show.

Free for everyone

Use it at home or at any company, for any purpose, with no fees, no seats and no registration. The app stays free; paid extras, if ever, are optional add-ons.

Collection runner

Run a whole suite. Keep every response.

Select requests, reorder them and run them with live progress. Each response lands in the run's output folder, so you always have a record of exactly what happened.

  • ChainingRead the last response with {{prev.json.token}} or {{prev.status}}.
  • In order or in parallelOne after another with {{prev}}, or everything at once with a limit you set.
  • Variables fileCSV or JSON, one iteration per row; download a sample made from your own variables.
  • Responses beside the requestEach request in the run shows a chip per body and iteration while it runs.
  • ControlsIterations, delays, stop-on-failure, cancel mid-run, or skip the dialog with ⚡ Run now.
  • Run historyHTML or JSON reports, and every past run one click away.
outputs/2026-10-07_18-30-00_smoke/
summary.json run status
01_login.json request + timing
01_login.body.json raw body
02_create-user__valid-user.json
02_create-user__valid-user.body.json
03_list-users.json
 
✓ POST login 200 · 132 ms
✓ POST create user 201 · 88 ms
✓ GET list users 200 · 141 ms
3 passed0 failed361 ms
Mock servers

Need the API before it exists? Serve it yourself.

A mock server is a name and a list of routes, answered by pyhttp on the port it already runs on. Point your frontend at it and build against endpoints nobody has written yet.

  • No second processEverything under /mock/<name>/… is answered by pyhttp itself. Open /mock to see every server you have.
  • Real routing/users/:id or /users/{id}, * for one segment, ** for the rest. Match on the query string when two routes overlap.
  • Bodies that answer backThe body is a template: {{req.params.id}}, {{req.query.plan}}, {{req.json.user.name}}, plus {{$guid}} and friends.
  • Status, headers, delayReturn a 402 or a 500 on purpose, set your own headers and content type, and add a delay per route to see your spinners work.
  • Seed it from real responsesTurn saved requests into routes that reply with the response they last gave, then edit from there.
  • Call logEvery request a mock answers is logged with its method, path, query and status, so you can see exactly what your app sent.
Payments API/mock/payments
route GET /users/:id → 200 · application/json
 
{
"id": "{{req.params.id}}",
"plan": "{{req.query.plan}}",
"trace": "{{$guid}}"
}
 
$ curl localhost:8765/mock/payments/users/42?plan=pro
{
"id": "42",
"plan": "pro",
"trace": "ecbd182b-7d4d-4825-870c-d4a60b4256"
}
answered by pyhttpno second process3 calls logged
Response views

JSON is for machines. Give your response a page.

A response view is an HTML page of your own that pyhttp opens with the response in it. Twenty lines of HTML turn an order list into a revenue dashboard, a metrics endpoint into a chart, a search result into the table your support team asked for.

  • Any HTML file, or any pagePoint a request at a file on your computer or at an address such as http://localhost:5173/chart.html. Your own scripts, styles and chart library load next to it.
  • One line to get the dataThe page reads api from its address and fetches it: the body exactly as the API sent it, however large. meta adds status, timing and headers.
  • Side by sideAttach several views and look at them one at a time or all together, in the View tab of the response, or open them in your browser.
  • Every past call tooOpen any entry of the request history in its views. A view you add today also draws the calls you made last week.
  • On a folderGive a folder a view and every request inside shows it, next to its own.
  • Start in one click“Create a starter view” writes a working page with a chart, a table and the raw data. Edit it into the one you want.
  • Kept apart from the appInside pyhttp a view runs in a sandboxed frame: it gets the response it was opened with and nothing else.
orders-dashboard.htmlyour file
<!-- pyhttp opens this page with ?api=…&meta=…&theme=dark -->
<h1>Orders</h1>
<div id="total"></div>
<script>
const q = new URLSearchParams(location.search);
 
// the response body, exactly as the API sent it
const orders = await fetch(q.get('api'))
.then((r) => r.json());
 
const sum = orders.reduce((n, o) => n + o.total, 0);
total.textContent = `${orders.length} orders, $${sum}`;
</script>
runs in a sandboxed framedark and lightno build step
The View tab of the response of List orders showing two views side by side: Overview, a bar chart of items by customer, and Dashboard, with 12 orders, 1022.75 dollars of revenue, an average order of 85.23 dollars, a bar of orders by status and a table of the largest orders.

One response, two pages, side by side

On the left the starter view pyhttp writes for you; on the right a dashboard written for this API in sixty lines of HTML. Both draw the same response, and both redraw when you send again.

The Response views dialog of the request List orders: two rows, Overview and Dashboard, each with the path of an HTML file, the buttons Add a view and Create a starter view, and the switch Show the view first.

Attaching a view is a name and a path

Pick the file, or let pyhttp create a starter. Views are saved with the request, so they travel with exports and Git sync. Turn on Show the view first and a response opens straight in its page.

See it in action

What's new, in the app itself.

Real screenshots of pyhttp, taken against a local test API.

The request Order events with the address ws://127.0.0.1:8790/events and a red Disconnect button. Below, the WebSocket console: Connected, a welcome message, a subscribe message sent, and order.created and order.paid events received, one of them pretty-printed, with a box to send the next message.

WebSockets, in the same window

A ws:// address turns Send into Connect and the response into a live console. Messages in both directions with their time and size, click one to pretty-print its JSON, filter the log, and send the next one with Ctrl+Enter.

The request List orders with its View tab open: a page titled List orders, 200, drawing a bar chart of items by customer from the JSON response, with Reload, Open in browser and Edit views.

Response views: your data, drawn your way

Point a request at an HTML page of your own and the response opens in it: a chart, a table, a dashboard. It works for every past call in the history too, and a folder can give its views to every request inside.

The Examples tab of Create order with two pinned responses, 422 missing email and 201 created, each with View, Mock, Rename and Delete. Below, the response chips valid order 201 and missing email 422 with a Compare button.

Example responses, and a diff between any two

Pin the responses worth keeping: they document the request, go out with exports and Git sync, and turn into mock routes. Sent two bodies? Compare shows what differs, line by line.

The Settings tab of Create user: Depends on another request set to POST Users API › Get token, send it again after 30 minutes, retry on 401 or 403 ticked, and the status line Last sent 1 s ago (200), runs again in 30 min.

Dependent requests: tokens on autopilot

Pick the request that fetches your token and how long it stays valid. It runs first, its extraction sets {{token}}, and it is reused until it expires. A 401 refreshes it and retries once.

A request with fifteen named bodies sent in parallel: the Send mode menu open with Send in parallel, at most 8 at once ticked, fifteen response chips all 200, and the run panel listing 15 of 15 sent with 0 failed.

Fifteen bodies, sent in parallel

Send ×15 goes out at once, at most eight at a time, with the token fetched only once. Every response is a chip with its status and time; click one to read it. The ▾ next to Send switches between in order and in parallel.

The Run requests dialog: How to send set to In parallel, at most 8 at once, three requests in order, and the variables file sample-variables.csv with three rows and the variables user_name and page.

Runs: in order or in parallel, driven by a variables file

Choose how to send, then load a CSV or JSON of variable values, or let pyhttp write a sample from the variables your requests use. Three rows, three iterations.

The Mock servers dialog showing the routes of Payments API: GET /users/:id answering 200 with 4 hits, POST /charge answering 402, and a GET /** catch-all answering 404, with the base URL http://127.0.0.1:8765/mock/payments-api and a Copy button.

Mock servers: endpoints before the API exists

Add routes with :id parameters, a status code and a templated body, and pyhttp answers them on the port it already runs on. Hits are counted per route, and every call is logged.

The Monitors dialog: Production health passing with a green duration sparkline and a webhook set, Checkout flow failing with a red FAIL badge, a count badge on the Monitors button, and 1 monitor failing in the status bar.

Monitors: your API checked on a schedule

Every few minutes, the requests you picked are sent again. Pass and fail at a glance, duration sparklines, a results log per monitor — and a webhook ping when something breaks.

After a run named smoke: the Create user request shows response chips for valid user and missing email in iterations 1 to 3, and the Current run panel lists twelve sends, all 200, with 36 of 36 tests passed.

Every run response, beside its request

Open any request after a run and its responses are right there: one chip per body and iteration, with the run's name and totals above them, next to the full run report.

Command line & CI

The same collection, headless in your pipeline.

pyhttp run runs folders, requests or a collection file without the window, prints each result and exits non-zero when anything fails. It works like newman, but for pyhttp.

  • Pick what to runFolders by path, requests by name, --tag smoke, or a Postman, OpenAPI or HAR file.
  • Environments--env QA, an exported environment file, or --var key=value.
  • ReportsJUnit XML for your CI, plus HTML and JSON reports.
  • Exit codes0 all passed, 1 something failed, 2 usage error.
terminal.github/workflows/api.yml
$ python -m pyhttp run "API/Users" --env QA \
--junit out/junit.xml --html out/report.html
 
pyhttp 0.1.7 · API/Users · 4 request(s) · environment QA
✓ POST login 200 · 96 ms
✓ GET list users 200 · 121 ms
✓ POST create user 201 · 84 ms
✗ DELETE delete user 403 · 41 ms
✗ status is 204 — expected 204, got 403
3/4 requests passed, 5/6 tests passed · 0.34s
Report: out/junit.xml
Report: out/report.html
$ echo $?
1
JUnitHTMLJSONexit 1
Scripts tab

Assertions without code. Scripts when you need them.

The Scripts tab has a Pre-request and a Post-response section, like Postman's. Build checks on status, headers, body, JSON path and response time from simple rows; results show in the response's Tests tab and in run reports.

  • No-code testsStatus, header, body, JSON path and timing assertions.
  • Per bodyEach test, and each pre-request or test script, runs for every named body or for one you pick: “missing email” can expect a 400 while “valid user” expects a 201.
  • Tokens that expireMake a request depend on your token request: it runs first, is reused until it expires, and is refreshed when the API says 401.
  • Extract & chainPull a value into a variable, then use {{token}} next.
  • Postman scripts, unchangedThe full pm API in JavaScript.
  • Python when you want itA py object for anything the builder can't express.
The Scripts tab: four no-code tests, two of them scoped to the bodies valid user and missing email, the extract-variables row that saves user_id, and a JavaScript test script using the pm API.
History & compare

What changed since the last call? See it line by line.

Request history sits next to Run history in the right-hand panel. Pick any two entries and pyhttp diffs the status, timing, headers and body. JSON keys are sorted first, so only real changes show.

  • Filter fastSearch by text, or show only 2xx, 4xx, 5xx or failed requests.
  • Send againReplay any past request with today's variables in one click.
  • Open as newTurn a history entry back into an editable request.
CompareGET /users/42 · 10:02 → 10:14
status 200 → 200 time 88 ms → 131 ms
 
{
"email": "ada@example.com",
"id": 42,
- "plan": "free",
+ "plan": "pro",
+ "seats": 5,
"status": "active"
}
+2−1body · headers · request
Local-first

The only place your traffic goes is your API.

No account to create, nothing synced to a cloud, no telemetry. The only calls pyhttp makes on its own are the update check against GitHub (switch it off in Settings) and a monitor's webhook, if you set one.

Bound to 127.0.0.1

The backend listens only on localhost. Window mode picks a free port and prints it on start. Mock servers answer on that same local port — nothing is exposed to the network.

Plain, portable data

Requests, folders, environments and history live in one SQLite file. Move it with --data-dir.

No Electron, no build step

A native OS webview (WebView2, WKWebView or WebKitGTK) with native file dialogs. Python 3.10+ is all you need.

Drive it from scripts

Use pyhttp run in CI, or start with --server for a REST API documented at /api/docs.

Built for the keyboard

Fast by keyboard. Flexible by setting.

Every common action has a shortcut. On macOS, use Cmd in place of Ctrl.

  • AutosaveEvery edit is saved half a second after you stop typing.
  • Code snippetscURL, Python requests and httpx, fetch, axios, PowerShell, raw HTTP and Go, with variables and auth resolved.
  • Body typesJSON, text, XML, HTML, JavaScript, form, form-data with files, binary and GraphQL.
  • SettingsRedirects, proxy with bypass list, per-host client certificates, timeouts, User-Agent, response below or to the right, and update checks.
  • Send request Ctrl↵
  • Save now CtrlS
  • New request CtrlN
  • Duplicate request CtrlD
  • Search the collection CtrlK
  • Move through the collection ↑↓
  • Open / tick the focused request ↵Space
  • Show or hide the sidebar Ctrl\
  • Beautify the body CtrlB
  • Search the response CtrlF
  • Code snippet Ctrl⇧C
  • Close the current tab CtrlW
  • Toggle the console CtrlAltC
  • Shortcut help ?
Download

Get pyhttp

Packaged builds bundle Python and every dependency. Just double-click.

Latest versionv2.1.0
Released—
Total downloads—

Windows 10 / 11 Your system

Installer: per user, no admin rights, updates itself. Or the portable .exe, one file, no install. WebView2 ships with Windows.

Download installer

macOS 11+ Your system

Instead of downloading, paste this in Terminal (Apple Silicon). It installs pyhttp through Homebrew, and from then on updates install themselves from inside the app. The last line clears macOS's quarantine flag, which otherwise shows “Apple could not verify pyhttp” on first launch (the build isn't notarized yet):

Install
brew trust pyhttp2/pyhttp
brew install --cask pyhttp2/pyhttp/pyhttp
xattr -dr com.apple.quarantine /Applications/pyhttp.app

Already have pyhttp and “Update with Homebrew” fails? Paste this instead; your requests and settings are kept. The last line stops the “Apple could not verify” popup after a Terminal upgrade. Why this happens

Update
brew trust pyhttp2/pyhttp
brew update && brew upgrade --cask pyhttp2/pyhttp/pyhttp
xattr -dr com.apple.quarantine /Applications/pyhttp.app

No Homebrew? Install it first, or use the .dmg: drag pyhttp to Applications (a .dmg install does not update itself).

Download .dmg

Linux Your system

Ubuntu 22.04+ / Debian 12+ .deb: sudo apt install ./pyhttp-*.deb. Uses your distro's GTK and WebKitGTK. Other distros run from source.

Download .deb

Prefer pip or Homebrew? pip install pyhttp-app (or pipx install pyhttp-app) installs the same app from PyPI on any OS with Python 3.10+, and brew install pyhttp2/pyhttp/pyhttp does it through Homebrew on macOS (Intel and Apple Silicon) and Linux — or tap once with brew tap pyhttp2/pyhttp and it's just brew install pyhttp from then on. The command is pyhttp either way.

The builds aren't code-signed yet, so the first launch shows a warning you can allow once (see the FAQ). pyhttp checks for new versions at start; on Windows and with Homebrew it installs them itself. Previous versions and checksums (SHA256SUMS.txt) are on the releases page.

FAQ

Questions, answered.

Something missing? Open an issue on GitHub.

Is pyhttp free? Can I use it at work?

Yes, for everyone. pyhttp is free under the pyhttp Free License: use it at home or at work, at a company of any size, for any purpose, with no fees, no seats and no registration. You can share the unmodified installers inside your company or through package managers. The source code is published on PyPI as pyhttp-app, so it can be inspected and installed with pip; modified versions may not be distributed. The app stays free; if paid extras ever appear they will be optional add-ons. pyhttp is provided “as is”, with no warranty of any kind and no liability for any damage or loss from its use; see the license for the full terms.

Do I need an account, or does it send my data anywhere?

No. There's no sign-up, no sync and no telemetry. Your collection is a SQLite file on your disk, and the only network traffic is the requests you send. The app checks GitHub for a newer release, and that's all.

Can I bring my Postman collections?

Yes. Import Postman v2.1 collections and environments, including folders, auth, variables and scripts. pm.* scripts run unchanged. You can also export back to Postman format.

Can I import an OpenAPI or Swagger spec?

Yes, OpenAPI 3 and Swagger 2 in JSON or YAML. Each tag becomes a folder, the server URL becomes {{baseUrl}}, and request bodies are filled from the spec's examples and schemas. HAR files from browser devtools work too.

My API needs a token that expires. Do I fetch it by hand?

No. Save the request that fetches the token and let it store the value with an extraction or pm.environment.set. In any other request's Settings tab, pick it under Depends on another request and set how long a token stays valid. pyhttp sends it first, reuses the token until then, fetches it only once even for parallel sends, and refreshes it with one retry when the API answers 401 or 403.

Can AI tools drive pyhttp?

Yes. pyhttp mcp serves an MCP server on stdio, so Claude Code, Claude Desktop or Cursor can list, create and edit requests, send them, run a folder or a tag, read history, switch environments and set variables. Settings → AI (MCP) shows the exact command and JSON for your machine, already filled in. It uses the same data folder as the app, so both can be open at once.

Can I test a WebSocket?

Yes. Type a ws:// or wss:// address into a request (or a variable that holds one, such as {{events_url}}/orders) and Send becomes Connect. You get a live log of everything sent and received, Ping, a filter, and a box to send messages from; the request's query parameters, headers, auth and {{variables}} are used for the connection, and a Sec-WebSocket-Protocol header names the subprotocols to offer. Compression (permessage-deflate) and proxies are not supported yet.

Can my team share a collection without a cloud account?

Yes, through Git. Git sync (top bar → Git) writes the project's requests, folders, environments, mocks and monitors as JSON files into a repository of yours, commits and pushes a few seconds after every change, and reads your teammates' pushes back in, also on a timer while the app is open. Values of secret variables stay out of the files unless you ask for them. When two people change the same request, pyhttp asks which version to keep.

Can I fake an API that does not exist yet?

Yes — add a mock server and point your frontend at http://127.0.0.1:<port>/mock/<name>/…. It is answered by pyhttp itself, so there is no second process or port. Routes take :id parameters and wildcards, the body is a template that can read the incoming request, and you can set the status, headers and a delay per route. You can also seed routes from saved requests so they reply with the response they last gave.

How do I run my tests in CI?

Run python -m pyhttp run with a folder name or an exported collection file. Add --junit out/junit.xml for your CI's test report. The exit code is 1 when any request or test fails.

How do updates work?

pyhttp looks up the latest release at start (and on Check now in Settings) and shows an Update button when a newer version exists. The Windows installer, the portable .exe and a Homebrew install download the new version, verify its checksum and install it, then pyhttp restarts. A .dmg or .deb install gets the download and the exact command to run. Your requests and settings are never touched. From 2.0.8 on, pyhttp trusts its own Homebrew tap before upgrading (Homebrew 7 asks this once per third-party tap) and points taps made before the move at the current repository. On 2.0.7 and older the update can fail with an “untrusted tap” error; see the next question.

Which Linux distributions are supported?

The .deb installs on Ubuntu 22.04 and later and Debian 12 and later, and uses the distribution's GTK and WebKitGTK packages (they are pulled in as dependencies). Other distributions can run pyhttp from source with Python 3.10+ and the GTK webview bindings.

“Update with Homebrew” fails on my Mac (exit code 1, “untrusted tap”). What do I do?

Homebrew 7 loads nothing from a third-party tap until you have trusted it once. Macs that installed pyhttp before the project moved to github.com/pyhttp2/pyhttp trusted the tap under its old address; when pyhttp points the tap at its new home, that trust no longer applies and brew upgrade stops with Refusing to load cask pyhttp2/pyhttp/pyhttp from untrusted tap. pyhttp 2.0.7 and older report this only as “brew upgrade --cask failed (exit code 1)”. Trust the tap once in a terminal and update again, from the app or with the second line:

Update
brew trust pyhttp2/pyhttp
brew update && brew upgrade --cask pyhttp2/pyhttp/pyhttp
xattr -dr com.apple.quarantine /Applications/pyhttp.app

The last line matters too: an upgrade run from Terminal leaves macOS's quarantine flag on the new app, so the next launch shows “Apple could not verify pyhttp is free of malware” (the build isn't notarized yet). xattr -dr com.apple.quarantine /Applications/pyhttp.app clears it; the in-app updater does this by itself. Your requests, environments and history are kept: they live in ~/Library/Application Support/pyhttp, which no update touches. Not installed with Homebrew? Download the .dmg again and drag pyhttp over the old app. From 2.0.8 on, pyhttp does the trust step itself and shows Homebrew's own error line with the fix when something else goes wrong.

Windows or macOS warns me when I open it. Is that safe?

The builds aren't code-signed yet, so the system warns on first launch. On Windows (installer or portable), click More info → Run anyway. On macOS the popup says “Apple could not verify pyhttp is free of malware”: click Done, open System Settings → Privacy & Security, scroll down and click Open Anyway next to pyhttp (right-click → Open no longer works on macOS 15). Or clear the quarantine flag once in Terminal, which also fixes “the app is damaged”:

Allow
xattr -dr com.apple.quarantine /Applications/pyhttp.app

The in-app updater clears the flag itself after a Homebrew update; an upgrade run from Terminal does not, hence the third line in the command boxes above.

Does the Mac build run on Intel Macs?

The .dmg is built for Apple Silicon. On an Intel Mac, run pyhttp from source with Python 3.10+.

Where is my data stored?

In %APPDATA%\pyhttp on Windows, ~/Library/Application Support/pyhttp on macOS and ~/.local/share/pyhttp on Linux. Use --data-dir to keep it somewhere else, such as a synced folder. Updates and uninstalls leave it alone.

Send your first request.

Free for everyone, at home or at any company. No sign-up, ever.