用 Go 安装需 Go 1.25 或更新版本;macOS 二进制要求 macOS 13 Ventura 以上、Linux 要求 glibc 2.32 以上;Homebrew 安装用 brew upgrade resterm 更新,脚本或二进制安装用 resterm --update

resterm
unkn0wn-rootApache-2.0活跃维护终端里的 API 客户端,用可版本化的 .http 文件管理 HTTP、GraphQL、gRPC、WebSocket 和 SSE 请求,支持脚本化测试、工作流、Mock、录制和 CI 命令行运行。
Terminal API client for HTTP, GraphQL, gRPC, WebSocket and SSE. Plain .http files you can diff and version, with scripted tests, workflows, mocks, recorder, and a CLI runner.
brew install resterm
# 或
curl -fsSL https://raw.githubusercontent.com/unkn0wn-root/resterm/main/install.sh | bashREADME 未说明硬件要求;提供 macOS、Linux、Windows 预编译二进制,macOS 需 13 Ventura 及以上,Linux 需 glibc 2.32 及以上
终端里的 API 即代码工作台,用可 diff 的 .http 文件管理请求、工作流和 mock
终端里的 API 客户端,用可版本化的 .http 文件管理 HTTP、GraphQL、gRPC、WebSocket 和 SSE 请求,支持脚本化测试、工作流、Mock、录制和 CI 命令行运行。
核心能力
- 用纯文本 .http/.rest 文件定义请求,可读可 diff 可入 git
- 支持 HTTP、GraphQL、gRPC(含反射与流式)、WebSocket 和 SSE
- 用 @capture/@assert/@poll/@retry 做响应捕获与断言检查
- 工作流把请求串成步骤,支持 @if 与 @for-each 条件循环
- resterm run 在 CI 中跑同一批文件,输出 text/JSON/JUnit 与退出码
容易踩的坑
- v1.18.0最新正式版2026-11-07 · 2026-11-07 20:01 UTC
v1.18.0
This release fixes a few security issues, makes
collection --forcesafer, and cleans up some smaller bugs and quirks.The biggest change is how Resterm handles secrets.
resterm runnow masks them throughout its output, including values that weren't referenced through templates. History also masks secrets captured later in a session.There's also a new
--responseflag for including responses in JSON reports, a skill for coding agents, and a change to OAuth handling so headless runs can no longer open a browser.I added the skill because I realized that most folks now use local LLMs or frontier models like Claude or Codex, so it should help your LLM work with Resterm.
Secret masking in
resterm runIn previous versions, Resterm masked secrets referenced through templates like
{{api.key}}. But if you put the same value directly in a URL, it could still appear in the report:# @file-secret api.key s3cr3t-token-value-123 ### Search # @name search GET http://localhost:3000/search?key=s3cr3t-token-value-123The output looked like this:
PASS GET search [200 OK in 550.625µs] Source Target: http://localhost:3000/search?key=s3cr3t-token-value-123 Effective Target: http://localhost:3000/search?key=•••In 1.18.0:
PASS GET search [200 OK in 447.458µs]The target lines are only printed when they differ. Once both values are masked, there's no difference to show.
- Secrets are masked with
•••in text, JSON, and JUnit reports, stream transcripts, trace files, and errors. This also covers JSONtargetfields, JUnit test names, and the request list printed when a file contains multiple requests. - Masking uses all secrets known by the end of the run. If a later request captures a token as a secret, that value is also masked in earlier results.
- Secrets declared with fewer than 4 characters produce a warning, since masking a short value might also replace unrelated text:
Warnings: secret "pin" is shorter than 4 characters, so reports may also mask the same word in unrelated textJSON reports include this under
warnings. The warning doesn't change the exit code.Include responses in JSON reports
You can now use
--responseto include response data in the JSON report. It adds aresponseobject to request results, including individual workflow, for-each, and compare steps.resterm run --all --format json --response api.httpExample output:
"response": { "headers": { "Content-Type": ["application/json"], "Set-Cookie": ["•••"] }, "body": "{\"token\":\"•••\",\"user\":\"ada\"}", "bodySize": 47, "captures": [ {"scope": "file", "name": "auth.token", "value": "•••", "secret": true} ] }Responses are masked before being written to the report.
- Secret values,
-secretcaptures,Set-Cookie, and sensitive headers such asAuthorizationandX-API-Keyare replaced with•••. - Gzip bodies are decoded before masking. If Resterm can't decode a body, such as with
bror invalid gzip data, thebodyfield is omitted. Empty bodies are kept as"body": "", along with their headers. - Bodies that aren't valid UTF-8 are base64-encoded and include
"bodyEncoding": "base64". - Masked bodies larger than 1 MiB are truncated to the first 1 MiB, with
"bodyTruncated": true. - Skipped requests, profile runs, and requests without a response don't include a
responsefield.
The flag only works with JSON output. Using it with another format exits with code 2:
error[unknown]: run: --response can only be combined with --format jsonIf you're using Resterm as a Go package, the equivalent option is
Response.Enabled. - Secrets are masked with
- v1.17.22026-10-30 · 2026-10-30 22:55 UTC
查看更新说明
v1.17.2
Clipboard over SSH (OSC 52)
- With no clipboard tool on the host (usual over SSH), yank failed:
Clipboard unavailable; saved in editor register- Now the copied text goes to your terminal with OSC 52:
Copied selection. Sent via OSC 52- Editor yank, delete, change and response pane copy.
- Only when the clipboard tool fails.
- Works by default in Kitty, Ghostty, WezTerm, Alacritty, foot, Windows Terminal.
- iTerm2: Settings > General > Selection > "Applications in terminal may access clipboard".
- Not supported: macOS Terminal, GNOME Terminal.
- tmux:
set -g set-clipboard on - Paste unchanged.
puses the editor register, terminal paste in insert mode uses your clipboard.
osc52_limit- Default 1 MiB, about 768 KB of text. Bigger copies stay in the editor register:
Too large for OSC 52 (limit 1 MiB). Saved in editor register[clipboard] osc52_limit = "8MiB"- JSON:
{"clipboard":{"osc52_limit":"8MiB"}} - Raise it for Kitty.
16KiBfor mosh. KB,KiB,MB,MiB,GB,GiB
Broken settings file no longer overwritten
- A file that failed to load, for example
diagnostics = 'yes', was replaced with defaults on the next theme change or layout save (g L). - With
settings.json, a newsettings.tomlwas written and the JSON file was ignored after that. - Now the save is refused and the status line shows why:
layout save error: settings not saved: parse settings "/home/you/.config/resterm/settings.toml": toml: cannot decode TOML string into struct field config.EditorSettings.Diagnostics of type *bool- Fix the file and restart.
Mock server, recorder and OAuth
:mock restart,:mock stop, the toggle key and quitting hung 3s:
Mock server stop failed: context deadline exceeded- Stopping the recorder: same 3s,
Recorder stopped: context deadline exceeded. - OAuth auth code login: about 5s extra after "Authentication complete" in the browser.
- Cause: a connection that never sent a request (Go 1.27 clients, browsers). Resterm waited about 5s for it.
- Requests in progress still finish.
- v1.17.12026-10-30 · 2026-10-30 11:47 UTC
查看更新说明
v1.17.1
Failed tests in the text report
The text report only gave a count for a failing request or step. 1.17.1 lists each failed test with its message, and workflow step details start under the step text instead of one or two columns to its left. A step with a failed JavaScript test in 1.17.0:
FAIL WORKFLOW check-user [0 passed, 1 failed, 0 skipped in 1.739666ms] 1. FAIL Get [1 test(s) failed] Source Target: {{base.url}}/users/1 Effective Target: http://localhost:3000/users/1And in 1.17.1:
FAIL WORKFLOW check-user [0 passed, 1 failed, 0 skipped in 1.435875ms] 1. FAIL Get [1 test(s) failed] Source Target: {{base.url}}/users/1 Effective Target: http://localhost:3000/users/1 Failed Tests: user has an email- Passing tests are left out. A message over several lines stays lined up under its first line.
- With 10 or more steps, the step numbers are right-aligned so the details of every step start in the same column.
- This applies to
-f text, to the defaultautoformat whenever it prints the text report, and to the headlessTextformat.
JavaScript asserts show their text once
tests.assertandtests.failused their text as both the test name and its message, so you saw it twice:Tests: [PASS] status is 200 [FAIL] user has an email - user has an emailNow it's
[FAIL] user has an email. In JSON,failure.messageisuser has an emailinstead ofuser has an email: user has an email, and the failed test has nomessagefield.client.testhasn't changed and still shows what failed it, as inuser has an email - Error: no email for user 1.--timeoutstops JavaScript tests@assertand RTS test blocks already stopped at the request timeout. JavaScript tests didn't, so a test stuck in a loop ran for 30 seconds whatever the timeout was. This one never moves to the next page:### Orders # @name Orders # @script test > {% let page = response.json(); let count = 0; while (page.next) { count += page.items.length; } tests.assert(count > 0, "has orders"); %} GET {{base.url}}/ordersWith
--timeout 5s, 1.17.0 still waited 30 seconds and exited with 24:Errors: Script error: error[timeout]: script exceeded the 30s time limit test script 11.17.1 stops it after 5 seconds and exits with 20:
Errors: Script error: error[timeout]: response checks timed out after 5s test script 1 ╰─> context deadline exceeded- All checks share one timeout that starts after captures. They run in order:
@assert, RTS test blocks, then JavaScript tests. A check that hasn't started when the time runs out is skipped. @timeouton a request works the same way.- A timeout in the checks exits with 20, like any other timeout. 1.17.0 exited with 24, the code for script errors, even when the error said timeout. A script that throws still exits with 24.
- A script block still stops after 30 seconds at most. Pre-request scripts don't use
--timeout.
See exit codes.
Loop variables in step conditions
A step's
@whenor@skip-ifis checked once, before the@for-eachof the request it runs, so it can't read that loop's variable. For most names this fails with a hint. If the variable had the name of a built-in, such aslist,jsonorlen, the condition read the built-in instead and carried on without an error.# @workflow sync-lists # @skip-if list.archived # @step Sync using=SyncList ### Sync list # @name SyncList # @for-each json.file("lists.json") as list POST {{base.url}}/lists/{{= list.id }}/syncold-promosis archived, and 1.17.0 synced it anyway:PASS WORKFLOW sync-lists [3 passed, 0 failed, 0 skipped in 2.967291ms] 1. PASS Sync (1/3) [200 OK in 1.218709ms] Source Target: {{base.url}}/lists/{ - v1.17.02026-10-27 · 2026-10-27 11:27 UTC
查看更新说明
v1.17.0
gRPC requests can load their schema straight from
.protofiles, without server reflection or a compiled descriptor set. GraphQL subscriptions work over SSE and WebSocket.gRPC with
.protofilesPoint
@grpc-protoat your.protofiles and Resterm compiles them when it sends the request, so there's noprotocstep.### Say Hello # @grpc acme.v1.Greeter/SayHello # @grpc-proto ./proto/acme/v1/greeter.proto # @grpc-import-path ./proto GRPC {{grpc.host}} {"name": "Ada"}Here
greeter.protoimportsacme/v1/types.protofromproto/. If a file doesn't compile, the error tells you where:error[protocol]: compile proto files ╰─> acme/v1/types.proto:6:3 ╰─> field acme.v1.Greeting.text: unknown type strng- Without
@grpc-import-path, the folder of each listed file is its import path. Add import paths when imports start from a root folder, as withprotoc -I. Both directives can repeat, and paths can use{{variables}}. - The standard
google/protobuf/*.protofiles are built in. Others, such asgoogle/api/annotations.proto, have to be on an import path. - Resterm reads the files on every send, so an edit to a
.protofile applies to the next request without a restart. - proto2, proto3 and edition 2023 files work. Edition 2024 isn't supported yet.
- A request can use
@grpc-protoor@grpc-descriptor, but not both. - The navigator lists the
.protofiles your requests use, and completion after@grpc-protosuggests only.protofiles. Explain shows each proto file and import path. resterm collection exportbundles the proto files together with every file they import.
See the proto files docs.
GraphQL subscriptions
@graphqlworks with@sseand@websocket.@operation,@queryand@variablesbehave the same as over HTTP.Over SSE
### Order events # @graphql # @operation OrderEvents # @sse duration=30s idle=10s # @assert stream.summary().reason == "complete" POST {{graphql.endpoint}} subscription OrderEvents($id: ID!) { orderEvents(id: $id) { status } } # @variables { "id": "{{order.id}}" }The query is sent like any other GraphQL request, in the body for POST or as query parameters for GET. The stream ends at the first
completeevent and the summary reason iscomplete. If the server closes the stream before that, the reason iseof. This is the distinct connections mode of GraphQL over SSE.Over WebSocket
### Order events over WebSocket # @graphql # @operation OrderEvents # @query < ./queries/order-events.graphql # @variables < ./queries/order-events.json # @websocket GET {{graphql.wsEndpoint}} Authorization: Bearer {{token}}Resterm uses the
graphql-transport-wssubprotocol. It sendsconnection_init, waits forconnection_ack, subscribes and answers eachping. When the operation ends withcompleteorerror, Resterm closes the socket with code 1000.@wssteps run after the subscription starts, so# @ws wait 20skeeps listening for up to 20 seconds.A GraphQL error doesn't fail the request, the same as over HTTP. Check for it with
@assert. A message that breaks the protocol does fail it, with exit code 26. Here is the stream summary after a server sent anextmessage whose payload wasn't an object:closeCode: 4400, closeReason: "server sent next without an object payload", closedBy: "error", errorClass: "protocol",- The request also fails if the server closes the socket before
connection_ack(exit code 26), or ifidleruns out before it arrives (exit code 20). After the ack, a close with any code, such as 4403, is a normal ending. - Only
graphql-transport-wsis supported, not the oldersubscriptions-transport-ws. On a@graphqlrequest, any othersubprotocolsvalue is a parse error. connection_inithas no payload, so send crede
- Without
- v1.16.12026-10-22 · 2026-10-22 08:36 UTC
查看更新说明
v1.16.1
This release moves resterm to Go 1.27.3.
Important: Please update. This release blocks certificates from a compromised certificate authority (eMudhra).
Compromised eMudhra certificates
The private keys of eight intermediate certificates from the certificate authority eMudhra were so weak that anyone could recover them from the public certificates. With those keys, anyone can issue a valid HTTPS certificate for any website. eMudhra has revoked the certificates, and browsers and operating systems are blocking them. (CVE-2026-111943, Mozilla's write-up)
resterm 1.16.1 rejects any certificate chain through these intermediates on every platform. resterm 1.16.0 can still accept them on systems whose certificate store doesn't block them yet.
Before you update
macOS 12 Monterey is no longer supported. The macOS binaries need macOS 13 Ventura or newer. On macOS 12, do not run
resterm --update, because it installs a binary your system does not support. Instead, build 1.16.1 with Go 1.26.10 or a newer 1.26 release, which has the same certificate fix:go install github.com/unkn0wn-root/resterm/cmd/[email protected]Some TLS-inspecting proxies may drop HTTPS connections. resterm now offers post-quantum (ML-DSA) signature algorithms when it opens a TLS connection. Some proxies and load balancers reset these connections. Reports so far name Palo Alto Prisma Access and F5 BIG-IP (golang/go#81199). If HTTPS requests fail with EOF or connection reset by peer after you update, build with Go 1.26.10.
Fixes
- An SSE stream that stops at its size or event limit now closes its connection right away instead of reading on in the background.
- Go security fixes for net/http, crypto/tls, net/textproto and html/template: CVE-2026-97031, CVE-2026-94440, CVE-2026-97032, CVE-2026-94439, CVE-2026-78663, CVE-2026-78669, CVE-2026-78660, CVE-2026-78667, CVE-2026-56866, CVE-2026-78659, CVE-2026-97030, CVE-2026-94448.
Other changes
- GODEBUG=tlsrsakex=1 and GODEBUG=tls3des=1 no longer turn RSA key exchange or 3DES back on for old servers.
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
Resterm is a code-first API client built around plain .http/.rest files. A request is a few lines of text you can read, diff and keep in git, and the same goes for workflows and local mocks. You send them from the terminal UI while you work, and resterm run runs the same files in CI. If your API sits behind an SSH bastion or inside a Kubernetes cluster, Resterm opens the tunnel or port-forward for you.
Features
- HTTP, GraphQL, gRPC (with reflection and streaming), WebSocket and SSE
- environment files, grouped environments, dotenv files and OS variables
- bearer tokens, OAuth 2.0 (client credentials, password, and authorization code with PKCE), and tokens from CLIs you already use, like
gh auth token - TLS client certificates, custom root CAs, proxies, HTTP/2 and a cookie jar per environment
- captures and checks with
@captureand@assert, plus@polland@retry - workflows that chain requests into steps, with
@ifand@for-each - RestermScript for logic and
@rts testblocks - editor with Vim motions, syntax highlighting, completion for directives, headers, file paths, and inline diagnostics
@compareto send one request to several environments and diff the results- timeline tracing with per-phase budgets, and export to OpenTelemetry
@profilewith latency stats, a histogram, P95 and P99- SSH tunnels and Kubernetes port-forwards that Resterm opens for the request
- local mock servers from your
.httpfiles, and a recording proxy that turns real traffic into requests or mocks resterm runfor CI, with text, JSON or JUnit output and exit codes that tell a failed check from a TLS or tunnel error- headless Go API to run the same files from your own Go code
- import from curl and OpenAPI, and share collections as bundles or zip files
- custom key bindings and themes
- open a file in
$EDITOR, or a response body in your default app
No account and no telemetry.
[!NOTE] Resterm has no MCP server or built-in AI features. To help your LLM use Resterm, install the skill with
resterm skill --agent claudeorresterm skill --agent codex. See the skill docs for other agents. You can also point your agent atdocs/or resterm.app/docs.
Screenshots
Install
macOS and Linux:
brew install resterm
# or
curl -fsSL https://raw.githubusercontent.com/unkn0wn-root/resterm/main/install.sh | bash
Windows:
iwr -useb https://raw.githubusercontent.com/unkn0wn-root/resterm/main/install.ps1 | iex
With Go 1.25 or newer:
go install github.com/unkn0wn-root/resterm/cmd/resterm@latest
Prebuilt binaries for macOS, Linux and Windows are on the releases page. The macOS binaries need macOS 13 Ventura or newer, and the Linux binaries need glibc 2.32 or newer.
To update a Homebrew installation, run brew upgrade resterm. For release binaries or installations made with the scripts, run resterm --update. If you installed with Go, rerun the go install command above. More in the install guide.
Quick start
mkdir my-api && cd my-api
resterm init
resterm
resterm init creates a requests.http file with a few sample requests and local mocks to answer them, so you don't need a real API to try it. Press g Shift+M to start the mock server, move the cursor to a request and press Ctrl+Enter to send it. Press ? for help at any time.
You don't need init either. Run resterm, type a URL or paste a curl command, and press Ctrl+Enter.
The same file also runs without the UI. Start the mocks in one terminal and run a request in another:
resterm mock requests.http # terminal 1
resterm run --request CreateUser requests.http # terminal 2
Request files
Resterm reads the standard .http format and adds # @ directives on top for settings and automation:
# @setting base-url https://api.example.com/v1/
### Create users
// Send this request once for each name in the list.
# @for-each ["david", "tom"] as name
# @when env.meta.name == "dev"
# @assert response.statusCode == 201
# @rts test "user has an id"
> if response.json().id == null {
> t.error("no id for", name)
> }
POST users
Content-Type: application/json
{"name":"{{= name }}"}
A @setting before the first request applies to the whole file. ### starts a new request. Directives above a request can repeat it, skip it or check the response, either with @assert or with an @rts test block of RestermScript code.
There are more examples in _examples/, and every directive is listed in the request file reference.
Documentation
The full docs are at resterm.app/docs. Good places to start are workflows, mock servers, recording, the CLI and RestermScript.
Inside the TUI, :help <topic> opens the built-in manual and :docs <topic> opens the same page on the website.
To run request files from your own Go code, see the headless package.
Contributing
To suggest a feature, start a discussion in the Ideas category. To report a bug, open an issue. To contribute code, open a pull request.
Reporting bugs and suggesting features helps just as much as code.
Sponsor
Resterm is and always will be free and open source. If you like my work, you can sponsor it on GitHub.