The path is stored alone.
Each request’s configuration — method, URL, headers, params, body, and auth — is one JSON blob in SQLite. The url field holds the path. A missing host stops the send and asks you to set one.
Every create, update, or delete on a collection, request, or environment increments that entity’s version, starting at 1, and writes a changelog entry with a summary and a JSON diff. Open the history from the Changelog button in the top bar.
- Nested collections, with drag-to-move. Right-click a request to move it between collections or to the project root. Moving the last request out of a collection prompts you to delete the empty collection.
- Auth is bearer token, Basic, or API key.
- The response viewer shows status, headers, an auto-formatted JSON body, and timing.
{
"method": "POST",
"url": "/users/{{userId}}",
"headers": [{ "key": "Authorization", "value": "Bearer {{token}}", "enabled": true }],
"params": [],
"body": { "type": "json", "content": "{\"name\": \"test\"}" },
"auth": { "type": "bearer", "token": "{{token}}" }
}
Request config, as stored. From the PIU README.
Variables land on a matching path.
Create environments — Dev, Staging, Prod — and key=value pairs. At request time, {{variable}} is filled in anywhere it appears.
A variable can also carry match_paths (globs such as api/auth/*), a target_location, a priority, and an optional expires_at. PIU injects it only when the path matches. If two variables aim at the same place, the higher priority wins. A stale value raises a variable-expired warning.
- header
- url-param
- url-path
- body
- auth-bearer
- auth-basic-user
- auth-basic-pass
- auth-apikey-name
- auth-apikey-value
target_location
A hook writes the next variable.
Hooks run after a request finishes and copy a value into environment variables — a token or a session id, without a manual paste. They also run when an MCP tool sends the request. Refresh on a hook sends the source request now and reports what it captured.
- Source request
- The request whose response to watch.
- Response location
- Body, read with JSONPath, or a response header, read by name.
- Selector
- A JSONPath such as
$.data.access_token, or a header name. - Target variables
- One or more environment variables to update.
- Value template
- Transforms the extracted value. The default is
{{value}}. - TTL
- Optional. Sets
expires_aton the captured variable. - Array strategy
first,last, orpick. Pick opens the array picker.
The project is a graph.
Data models belong to a project: named fields, descriptions, required flags, and example values. Link a model to a request to generate a sample JSON body or to check a response shape.
Inheritance is one parent plus mixins. Fields resolve in a fixed order: parent chain, then mixins, then the model’s own fields. Cycle detection refuses a loop. A diff modal compares a parent with a child, or two versions.
The in-app graph is Sigma.js v3 on WebGL: collections, requests, and models, seven edge types, ForceAtlas2 layout, and Louvain clusters grouped by URL prefix. Shift+click traces a path. You can also inspect a node’s blast radius. The graph has a dark theme and a light theme, plus keyboard shortcuts.
Search uses SQLite FTS5 with BM25 ranking. Backlinks are precomputed, so an MCP query can ask what uses a model and get the endpoints, collections, and inheritance chain. JSON bodies edit in Monaco. Form mode edits the same body field by field from the linked model.
What it is built with
- Backend
- Rust, Tauri 2.0, reqwest
- Frontend
- React 19, Next.js 16, TypeScript
- Database
- Turso SQLite, embedded and local
- State
- Zustand 5
- UI
- Ant Design 6, Tailwind CSS 4
- Editor
- Monaco
- Graph
- Sigma.js 3, Graphology, ForceAtlas2
- Packages
- bun
Port 3333 starts with the app.
The MCP server comes up on port 3333 at launch, so Claude or another MCP client can connect without a manual start. It exposes 57 tools: projects, collections, requests, environments, data models, execution, response hooks, search, sync status, and OpenAPI generation, including a Mermaid class diagram of model relationships.
Hook tools can build a token flow in code: create the login request, point a hook at the token, send the login, and the variable is filled in. execute_request fires hooks, same as the desktop UI.
Skills
Four Claude Code skills live in the repository: piu-mcp, piu-backend-sync, piu-frontend-sync, and piu-hyperf-sync. Install them from skills.sh:
npx skills add dickwu/piu
Backend sync detects the framework, reads routes, and creates the PIU project, environment, collections, and requests through MCP, tagged with the source commit. It also builds data models from request and response DTOs. Frontend sync scans fetch, axios, ky, ofetch, @tanstack/query, and SWR, then reports calls that match, calls missing in PIU, endpoints missing in the frontend, and type mismatches. Hyperf sync reads php bin/hyperf.php describe:routes, validation rules, Eloquent models, and middleware.
- Express
- FastAPI
- Django
- Gin
- Rails
- Axum
- Spring
- NestJS
- Hono
- Echo
- Fiber
- Actix
- Hyperf
- Laravel
Fourteen frameworks named in the README.
Two apps, or a git commit
PIU can sync a project to another PIU on the LAN. One side hosts. The other connects with an IP address, a port, and a shared join key. Conflicts resolve last-writer-wins on the version field. Clone from Host creates the local project on the connecting side, so nothing has to exist there first.
Projects, collections, and requests can store a source_commit_id. A project can also store source_repo_url and backend_type (for example express, fastapi, or axum). The MCP tool get_sync_status compares each entity’s commit with the project commit and flags anything behind.
Install PIU.
Homebrew on macOS
Tap the repository, then install the cask. The formula name in Casks/piu.rb is piu. Version 0.1.45 installs Piu.app. Checksums for both DMGs are pinned in that cask.
brew tap dickwu/tap
brew install --cask piu
The installer removes the macOS quarantine flag. If you still see a damaged app or quarantine warning, run:
sudo xattr -d com.apple.quarantine /Applications/Piu.app
Download a release
Signed builds for 0.1.45, from the GitHub release. The tag also publishes updater JSON for the built-in Tauri updater. Older files stay on the releases page.
Build from source
You need Rust stable, Bun 1.0 or newer, and the Tauri prerequisites for your platform. Docs for the app are the README in the source repository, which is also the homepage listed on the cask.
git clone https://github.com/dickwu/piu.git
cd piu
bun install
bun tauri dev
Questions
What is PIU?
PIU is a native desktop API client and a Postman alternative built with Tauri 2.0, React 19, and Ant Design 6. It is offline-first: projects, collections, requests, environments, and data models stay in a local SQLite database. Requests run via reqwest on the Tauri backend, so there are no browser CORS limits. The license is MIT, and the source and cask homepage are https://github.com/dickwu/piu.
How do I install PIU on macOS?
Run brew tap dickwu/tap, then brew install --cask piu. The cask installs version 0.1.45 as Piu.app. The installer removes the macOS quarantine flag. If macOS still shows a damaged app or quarantine warning, run sudo xattr -d com.apple.quarantine /Applications/Piu.app.
Which operating systems does PIU run on?
macOS, Windows, and Linux. Version 0.1.45 ships an Apple Silicon DMG, an Intel DMG, a Windows EXE installer, a Windows MSI, a Debian DEB package, and a Fedora RPM package. Releases are signed and include updater JSON for the built-in Tauri updater.
How does PIU build a request URL?
Each request stores only the path. At execution time the full URL is the environment host, plus the collection path prefix, plus that path. In the project README, https://api.example.com plus /v1 plus /users/123 becomes https://api.example.com/v1/users/123. If the environment has no host, PIU shows a config prompt before sending.
How do environment variables get applied?
Variables are written as {{variable}} and resolved when the request runs. Each variable can set match_paths globs, a target_location, a priority, and an optional expires_at TTL. target_location is one of header, url-param, url-path, body, auth-bearer, auth-basic-user, auth-basic-pass, auth-apikey-name, or auth-apikey-value. PIU injects the variable only when the request path matches, and the higher priority wins when two variables target the same place. A stale variable raises a variable-expired warning.
What does a response hook do?
After the source request completes, a hook reads a JSONPath from the body (for example $.data.access_token) or reads a response header, transforms the value with a template (the default is {{value}}), and writes one or more environment variables. If the selector returns an array, the strategy is first, last, or pick. Hooks run from the desktop UI and from MCP tools. Refresh sends the source request and reports the captured value.
Can two copies of PIU sync a project?
Yes, over the LAN. One instance hosts and the other connects with an IP address, a port, and a shared join key. Conflicts resolve last-writer-wins using each entity's version field. Clone from Host creates a new local project on the connecting instance, so you do not need a project there already.
Does PIU expose an MCP server?
Yes. The MCP server starts automatically on port 3333 when the app launches. It includes 57 tools for projects, collections, requests, environments, data models, execution, response hooks, search, sync status, and OpenAPI generation, including a Mermaid class diagram of model relationships. Claude Code skills in the repository are installed with npx skills add dickwu/piu.