terapi
Terminal + API — a keyboard-driven TUI for exploring, testing, and automating REST and GraphQL APIs, without leaving your terminal.
┌─────────────────────────── terapi ────────────────────────────┐
│ Collections | Request | Env | History │
├────────────────────────────────────────────────────────────────┤
│ ┌─ URL ──────────────────────────────────────────────────────┐ │
│ │ GET https://api.example.com/users │ │
│ └────────────────────────────────────────────────────────────┘ │
│ Description | Headers | URL Params | Body | Auth | Options │
│ ┌─ JSON · Raw r: toggle -/=: resize ──────────────────────┐ │
│ │ ▼ (root) Object │ │
│ │ status String "success" │ │
│ │ code Number 200 │ │
│ │ ▼ data Object │ │
│ │ ▶ user Object { id: 42, username: "tsodev" …} │ │
│ └────────────────────────────────────────────────────────────┘ │
├────────────────────────────────────────────────────────────────┤
│ Request › Body › JSON ● env: Production │
│ Tab: panels ←/→: section ↑/↓: cursor Enter: fold q: quit │
└────────────────────────────────────────────────────────────────┘
Why terapi?
| Tool | Problem |
|---|---|
| Postman / Insomnia | Electron, cloud account required, heavy |
| ATAC | Great REST TUI, but no GraphQL, no scripting |
| hurl | Excellent for scripting, no interactive TUI |
| HTTPie | Terminal, but not TUI |
terapi aims to be all of the above in one tool:
- GraphQL native — schema introspection, query autocompletion, variable editing
- Pipeline automation — chain requests, extract variables, run campaigns headlessly
- Local-first — collections stored as TOML, git-friendly, no account, no cloud
- Single binary —
cargo install terapi, instant startup, zero Electron
Installation
Or build from source:
Requirements: Rust 1.75+ (edition 2021), any modern terminal with 256-color support.
Usage
TUI keybindings
Request panel
| Key | Action |
|---|---|
Tab |
Switch panel |
e |
Edit URL (enter URL mode) |
m |
Cycle HTTP method (outside URL mode) |
↑ / ↓ |
Cycle HTTP method (in URL mode) / move response cursor / scroll |
n |
New request — clear all fields |
s |
Send request |
S |
Save current request to a collection |
i |
Edit description (Description sub-tab — enter editor) / Edit body (Body sub-tab — enter editor) |
a / d |
URL Params sub-tab — add / delete param |
t |
Toggle body mode: Text ↔ JSON (Body sub-tab, outside editor) |
← / → |
Navigate sub-tabs (also exits URL mode) |
Enter |
Send request (URL mode) / fold-unfold JSON node / edit body field (JSON mode) |
Esc |
Finish URL edit / exit body editor |
{{ |
Open variable picker (any editable field) — insert {{VAR}} from active env |
↑ / ↓ |
Auth sub-tab — navigate fields |
Space / Enter |
Auth sub-tab (Type row) — cycle auth type (No Auth → Bearer → Basic → API Key) |
Enter |
Auth sub-tab (field row) — open edit modal for token / username / password / key |
↑ / ↓ |
Options sub-tab — navigate between options |
Space / Enter |
Options sub-tab — toggle (Skip TLS / Follow redirects / Cookie jar) or cycle timeout |
r |
Cycle response view: JSON → Raw → HTTP exchange |
- / = |
Resize Key column |
q / Esc |
Quit |
Collections panel
| Key | Action |
|---|---|
Tab |
Switch panel |
↑ / ↓ |
Move cursor |
Enter |
Expand / collapse folder — or load request into Request tab |
n |
New collection |
f |
New folder in selected collection |
a |
Add request to selected collection / folder |
e |
Edit selected request — loads into Request tab with all fields editable (URL, headers, body, auth, description); S opens Update Request modal (pre-filled name/location; change location to save as new) |
d |
Delete selected item |
q / Esc |
Quit |
Env panel
| Key | Action |
|---|---|
Tab |
Switch panel |
← / → |
Switch focus: Environments ↔ Variables |
↑ / ↓ |
Navigate within focused panel |
Enter |
Activate selected environment |
n |
New environment |
a |
Add variable to selected environment |
d |
Delete selected environment or variable |
q / Esc |
Quit |
History panel
| Key | Action |
|---|---|
Tab |
Switch panel |
↑ / ↓ |
Navigate entries |
Enter |
Load request into Request tab |
d |
Delete entry |
q / Esc |
Quit |
Collections
Collections are stored as TOML files — one file per collection. Terapi resolves the storage directory in priority order:
| Priority | Location | Use case |
|---|---|---|
| 1 | $TERAPI_DIR |
CI, cron, custom path |
| 2 | ./.terapi/collections/ |
Per-project, versionable in Git |
| 3 | ~/.config/terapi/collections/ |
Global, cross-project (default) |
Collection TOML format
[]
= "My API"
= "Optional description"
[[]]
= "Auth"
[[]]
= "Login"
= "POST"
= "https://api.example.com/auth/login"
= '{"email": "{{EMAIL}}", "password": "{{PASSWORD}}"}'
[]
= "application/json"
[[]]
= "List users"
= "GET"
= "https://api.example.com/users"
[]
= "Bearer {{TOKEN}}"
See examples/collection.toml for a fully annotated template.
Example collections
Ready-to-use collections in examples/collections/ — copy them to your terapi directory to get started immediately:
| File | Contenu | Auth |
|---|---|---|
public-rest.toml |
JSONPlaceholder, ReqRes, httpbin, PokeAPI, CoinGecko | Aucune |
graphql.toml |
Countries API, Rick & Morty API (POST GraphQL) | Aucune |
sncf.toml |
API SNCF — gares, horaires, itinéraires, perturbations | Basic {{SNCF_TOKEN}} |
france-geo.toml |
API Géo + API Adresse IGN — communes, départements, régions, géocodage | Aucune |
france-eau.toml |
Hub'Eau — hydrométrie, qualité rivières et nappes | Aucune |
france-meteo.toml |
Météo-France — prévisions, observations, vigilance | Bearer {{METEO_TOKEN}} |
# Copier une collection dans le répertoire global
# Ou dans un projet local
Campaign runner
Terapi includes a headless campaign runner for API automation.
Campaign TOML format
[]
= "Users API — smoke tests"
= "Login then run CRUD operations"
# Optional: load a named terapi environment as base vars
= "production" # <terapi_dir>/envs/production.toml
[]
= "https://api.example.com" # overrides env_file if same key
[[]]
= "Login"
= "POST"
= "{{BASE_URL}}/auth/login"
= '{"email": "admin@example.com", "password": "secret"}'
[]
= "application/json"
[]
= "token" # extracted from response JSON
= "user.id"
[[]]
= "Get profile"
= "GET"
= "{{BASE_URL}}/users/{{USER_ID}}"
[]
= "Bearer {{JWT}}"
[[]]
= "Health check (staging)"
= "staging" # uses staging terapi env for this step only
= "GET"
= "{{BASE_URL}}/health"
Variable priority (lowest → highest): env_file → [env] → connector row → step env → extracted vars.
Data-driven campaigns (CSV connector)
[[]]
= "csv"
= "contacts.csv" # columns become {{variables}}
# Campaign runs once per CSV row
[[]]
= "Invite contact"
= "POST"
= "{{BASE_URL}}/invitations"
= '{"email": "{{contact_email}}", "name": "{{contact_name}}"}'
Variable extraction
Extracted values use dot-path notation over the JSON response:
| Path | Extracts |
|---|---|
token |
response["token"] |
user.id |
response["user"]["id"] |
data.items.0.name |
response["data"]["items"][0]["name"] |
Campaign report
Campaign : Users API — smoke tests
✓ Login POST 200 142 ms
↳ JWT = eyJhbGciOiJIUzI1NiIs…
↳ USER_ID = 42
✓ Get profile GET 200 89 ms
✗ Delete user DELETE 404 34 ms HTTP 404
╔════════════════════════════════════════════════════════════════════╗
║ Campaign Report — Users API — smoke tests ║
╠════════════════════════════════════════════════════════════════════╣
║ Steps : 2 ok / 1 failed (3 total) ║
║ Duration : 265 ms ║
╠════════════════════════════════════════════════════════════════════╣
║ ✗ SOME STEPS FAILED ║
╚════════════════════════════════════════════════════════════════════╝
Coming next
Native GraphQL support is in active development — schema introspection, query editor with field autocompletion, variables panel, and mutations.
Stack
| Role | Crate |
|---|---|
| TUI rendering | ratatui + crossterm |
| HTTP client | reqwest (async) |
| Async runtime | tokio |
| Serialization | serde + serde_json |
| Config / campaigns | toml |
| CSV connector | csv |
| CLI | clap |
| Config dir resolution | dirs |
| Error handling | anyhow |
| Body editor | tui-textarea |
License
MIT — © TSODev