docs Request files
Authentication
Static tokens, captured tokens, OAuth 2.0 and tokens from CLIs you already use.
Static tokens
Use @auth bearer {{token}} or an Authorization: Bearer {{token}} header. Combine it with @global or environment values to reuse the token.
To use the same authentication for every request in a file, add a file-scoped definition:
# @auth file bearer {{auth.token}}
### Inherits file auth
GET {{base.url}}/profile
### Opt out for one request
# @auth none
GET {{base.url}}/publicCaptured tokens
Capture a value at runtime and reuse it in later requests:
### Login
# @capture global-secret auth.token = response.json.token
POST {{base.url}}/login
{
"user": "{{user.email}}",
"password": "{{user.password}}"
}
### Authorized request
# @auth bearer {{auth.token}}
GET {{base.url}}/profileOAuth 2.0 directive
Resterm fetches the token for you, caches it per environment, refreshes it when it expires, and adds the Authorization: Bearer ... header to the request. Three grant types are supported.
Client credentials grant
Use this for machine-to-machine calls where no user is involved.
### Service-to-service call
# @auth oauth2 token_url=https://auth.example.com/oauth/token client_id={{svc.clientId}} client_secret={{svc.clientSecret}} scope="api:read api:write"
GET https://api.example.com/internal/statusBy default, the client ID and secret are sent to the token endpoint with HTTP Basic authentication. Some providers want them as form fields instead. Use client_auth=body for that:
# @auth oauth2 token_url={{oauth.tokenUrl}} client_id={{oauth.clientId}} client_secret={{oauth.clientSecret}} scope="{{oauth.scope}}" client_auth=body
GET {{base.url}}/resourceResterm puts the client ID and secret into the Basic header as written. The OAuth 2.0 specification asks clients to URL-encode both values first, and some providers expect this. With those providers, a secret containing + or %, or a client ID containing :, is rejected. Wrap the values in url.encode:
# @const oauth.id {{= url.encode(env.get("oauth.clientId")) }}
# @const oauth.secret {{= url.encode(env.get("oauth.clientSecret")) }}
### Resource
# @auth oauth2 token_url={{oauth.tokenUrl}} client_id={{oauth.id}} client_secret={{oauth.secret}}
GET {{base.url}}/resourceIf your provider accepts form fields, client_auth=body works as well. Do not combine it with url.encode. The form body is already encoded, so the secret would be encoded twice.
Password grant
For older systems that log in with a username and password.
### Resource owner password
# @auth oauth2 token_url=https://auth.example.com/oauth/token client_id={{app.clientId}} client_secret={{app.clientSecret}} grant=password username={{user.email}} password={{user.password}} scope="profile"
GET https://api.example.com/meAuthorization code + PKCE
With grant=authorization_code, Resterm runs the whole flow for you:
- Opens your system browser at
auth_url, which must be a fullhttp://orhttps://URL, with the authorization request. - Starts a temporary HTTP server on localhost to catch the redirect.
- Exchanges the authorization code for tokens at
token_urland sends the PKCE verifier with it. - Caches the token and adds it to your request.
resterm run and the headless package never open a browser. They use a cached token or refresh it, and otherwise fail the request with the reason.
Redirect URI behavior
The redirect_uri controls where the authorization server sends the user after login:
| Configuration | Result |
|---|---|
Omit redirect_uri |
http://127.0.0.1:<random-port>/oauth/callback |
redirect_uri=http://127.0.0.1:8080/callback |
Uses port 8080 with path /callback |
redirect_uri=http://localhost:0/auth |
Random port, custom path /auth |
The redirect URI has a few rules:
- It must use
http://, nothttps://. See RFC 8252. - The host must be
127.0.0.1orlocalhost. Other hosts are rejected. - Register the redirect URI pattern with your OAuth provider. Most allow
http://127.0.0.1:*or similar.
PKCE details
PKCE (Proof Key for Code Exchange) helps protect your login if someone intercepts the authorization code. Resterm generates the values needed for the exchange:
- code_verifier - 64 random bytes, base64url-encoded (about 86 characters). You can set your own. It must be 43 to 128 characters long (RFC 7636).
- code_challenge - SHA-256 hash of the verifier, base64url-encoded.
- state - 24 random bytes for CSRF protection.
Example: Public client with PKCE
### GitHub OAuth (public client, no secret)
# @auth oauth2 auth_url=https://github.com/login/oauth/authorize token_url=https://github.com/login/oauth/access_token client_id={{github.clientId}} scope="repo read:user" grant=authorization_code
GET https://api.github.com/user
Accept: application/jsonExample: Confidential client
### Auth0 with client secret
# @auth oauth2 auth_url=https://{{auth0.domain}}/authorize token_url=https://{{auth0.domain}}/oauth/token client_id={{auth0.clientId}} client_secret={{auth0.clientSecret}} scope="openid profile" audience={{auth0.audience}} grant=authorization_code
GET {{api.url}}/userinfoTimeout behavior
The browser login has a 2-minute timeout by default, which gives you time to log in. If you need longer, set @timeout on the request. Resterm uses it when it is longer than 2 minutes.
Custom token header
Some APIs expect tokens in a non-standard header. Use the header parameter to change where the token goes:
### API expecting X-Access-Token header
# @auth oauth2 token_url={{oauth.tokenUrl}} client_id={{oauth.clientId}} client_secret={{oauth.clientSecret}} header=X-Access-Token
GET https://api.example.com/dataWhen header is something other than Authorization, Resterm sends only the raw token, without the Bearer prefix. With the default Authorization header, the value is Bearer <token>.
Digest auth
Use # @auth digest <username> <password> for HTTP Digest authentication (RFC 7616).
### Digest-protected endpoint
# @auth digest {{api.user}} {{api.password}}
GET {{base.url}}/reportsResterm sends the request without a Digest header. If the server returns a supported 401 challenge in WWW-Authenticate, Resterm retries once with an Authorization: Digest header and returns that response.
- Supported algorithms are
MD5,SHA-256andSHA-512-256, including their-sessvariants. Resterm uses the first supported challenge in the server's list. - Resterm prefers
qop=auth. Withqop=auth-int, it also hashes the request body. Withoutqop, it uses the older RFC 2069 format. Session algorithms (-sess) requireqop. - Cookies set with the challenge are saved in the cookie jar and sent on the retry.
- Each redirect can get its own Digest header under the same credential rules as Basic auth. Sending credentials to another origin requires
forward-credentials-on-redirect. Credentials are never sent after an HTTPS-to-HTTP redirect, even if a later redirect returns to HTTPS. - An explicit
Authorizationheader takes precedence over@authand is sent unchanged. Otherwise,@auth digestoverrides any username and password in the URL.
Digest auth works with HTTP and SSE requests. Resterm does not support Digest auth for WebSocket or gRPC requests. An explicit Authorization header still takes precedence over @auth for these requests.
If you used # @auth Digest <value> to send a custom header, replace it with Digest: <value> in the request. digest is now a reserved auth type: one value is rejected, and additional values are read as a username and password.
Command-backed auth
If the token is already in an environment variable, you do not need a command. Read it once per file:
# @file token env:GH_TOKEN
# @auth file bearer {{token}}
### User
GET https://api.github.com/userUse @auth command when a CLI you already have can print the token, for example gh auth token. To apply it to every request in a file, define it with file scope:
# @auth file command cmd="gh auth token"
### User
GET https://api.github.com/user
### Repos
GET https://api.github.com/user/repos
### Public endpoint without inherited auth
# @auth none
GET https://api.github.com/rate_limitThis runs the command for every request. To run it once and reuse the token, give the definition a name and pick it per request with use=. A global definition works from every file in the workspace:
# @auth global command name=gh cmd="gh auth token"
### User
# @auth use=gh
GET https://api.github.com/user
### Same token in a custom header
# @auth use=gh header=X-GitHub-Token
GET https://api.github.com/rate_limitAny request can run first. The token stays cached for the session in each environment, and ttl refreshes tokens that expire:
# @auth file command name=gcloud cmd="gcloud auth print-access-token" ttl=50mStructured output works too:
### Internal CLI with JSON output
# @auth command cmd="mycli auth print --json" format=json token_path=access_token type_path=token_type expires_in_path=expires_in cache_key=myapi
GET https://api.example.com/projectsOlder files share a token through cache_key. The first request runs the command and fills the cache; later requests reuse it by naming the key. This still works, but the first request must run before the others:
### Seed a reusable command-auth slot
# @auth command argv=["gh","auth","token"] cache_key=github-cli
GET https://api.github.com/user
### Reuse it later with cache_key only
# @auth command cache_key=github-cli
GET https://api.github.com/user/reposAuthentication directives
| Type | Syntax | Notes |
|---|---|---|
| Basic | # @auth basic user pass |
Adds Authorization: Basic …. Templates expand inside parameters. |
| Bearer | # @auth bearer {{token}} |
Adds Authorization: Bearer …. |
| Digest | # @auth digest user pass |
Retries once after the server's 401 Digest challenge. See Digest auth. |
| API key | # @auth apikey header X-API-Key {{key}} |
api-key works too. Write the placement, the name, and the value. placement can be header or query. An auth dict in @apply or @patch may leave out name, which then defaults to the X-API-Key header. |
| Custom header | # @auth Authorization CustomValue |
Any header and value. For a header named after an auth type, such as Digest, use a normal request header. |
| Command | # @auth command cmd="gh auth token" |
Runs a non-interactive command without a shell, reads its stdout output, and adds an auth header before sending the request. |
| Named command | # @auth use=gh |
Uses a command auth defined once with @auth file or @auth global and a name. |
| OAuth 2.0 | # @auth oauth2 token_url=... client_id=... |
Fetches and caches tokens. Supports client_credentials, password, and authorization_code with PKCE. |
Scopes:
- Bare
@auth ...is request-scoped. @auth request ...is an explicit request-scoped form.@auth file ...defines inherited auth for later requests in the same document.@auth global ...defines inherited auth for the whole workspace. File-scoped auth wins when both exist.@auth file command name=<name> ...and@auth global command name=<name> ...define a named command auth. A named definition needscmdorargvand is not inherited. Requests pick it with@auth use=<name>.@auth nonedisables inherited auth for the current request.- An
@authline with an error is not skipped. A request that would take its auth from that line (directly, through inheritance, or throughuse=) fails instead of going out with other auth or none.
OAuth 2.0 parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
token_url |
Yes | - | Token endpoint URL. Must be provided at least once per cache_key. |
auth_url |
For auth code | - | Authorization endpoint. Required when grant=authorization_code. |
client_id |
Yes | - | Your application's client ID. |
client_secret |
No | - | Client secret (omit for public clients using PKCE). |
grant |
No | client_credentials |
Grant type: client_credentials, password, or authorization_code. |
scope |
No | - | Space-separated scopes to request. |
audience |
No | - | Target API audience (for example, Auth0). |
resource |
No | - | Resource indicator (for example, Azure AD). |
client_auth |
No | basic |
How to send credentials: basic (Authorization header) or body (form fields). Falls back to body automatically for public clients. |
header |
No | Authorization |
Which header receives the token. Use this when an API expects tokens in a custom header like X-Access-Token. |
username |
For password | - | Resource owner username (only for grant=password). |
password |
For password | - | Resource owner password (only for grant=password). |
cache_key |
No | auto | Sets the cache key yourself. Useful when several requests should share one token even if their parameters differ a little. When omitted, Resterm builds the key from the token URL, client ID, scope, and other fields. |
redirect_uri |
No | auto | Callback URL for authorization code flow. See Redirect URI behavior. |
code_verifier |
No | auto | PKCE verifier (43-128 characters per RFC 7636). Auto-generated when omitted. |
code_challenge_method |
No | s256 |
PKCE method: s256 (recommended) or plain. |
state |
No | auto | CSRF protection token. Auto-generated when omitted. |
Any other key=value pairs are sent as extra form parameters to both the authorization and token endpoints.
How token caching works
Resterm caches tokens per environment and cache_key. When a request needs a token:
- If a valid cached token exists (not expired, with a 30-second safety margin), Resterm reuses it.
- If the cached token is expired and has a
refresh_token, Resterm tries to refresh it and keeps thatrefresh_tokenunless the server sends a new one. - If the refresh fails or there is no token, Resterm fetches a new one from the token endpoint. For
grant=authorization_codethat takes a browser login, which only the TUI does, soresterm runand the headless package fail the request with the reason instead.
For grouped environments, "per environment" means the full group selection. Changing just the credentials profile gives you a separate OAuth and command-auth cache. An explicit cache_key is also tied to that selection.
Older tokens saved under an explicit key do not identify which selection they belong to. Resterm ignores those tokens and fetches new ones for the current selection.
You can define the full OAuth parameters once, then use only cache_key in later requests:
### First request - seeds the cache
# @auth oauth2 token_url={{oauth.tokenUrl}} client_id={{oauth.clientId}} client_secret={{oauth.clientSecret}} scope="read write" cache_key=myapi
GET {{base.url}}/users
### Later request - reuses cached token
# @auth oauth2 cache_key=myapi
GET {{base.url}}/projectsIf you leave out token_url on a later directive and the cache has not been seeded yet, Resterm fails with @auth oauth2 requires token_url (include it once per cache_key to seed the cache).
Command auth parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
name |
No | - | Names a file or global definition so requests can pick it with use=. A named definition is not inherited. A request line cannot take a name. |
cmd |
One of cmd or argv |
- | Command line, split into arguments like a shell would split it, but no shell runs. See Command lines. |
argv |
One of cmd or argv |
- | JSON array of command arguments. Bare JSON works, for example argv=["gh","auth","token"]. Outer single quotes also work and are useful when you want to keep whitespace exactly as written, for example argv='["gh", "auth", "token"]'. |
format |
No | text |
Parse stdout as text or json. |
header |
No | Authorization |
Target header name. |
scheme |
No | auto | Prefix for the final header value. When omitted, Authorization gets Bearer and custom headers get the raw token. |
token_path |
For format=json |
- | JSON path to the token value. Uses the same JSON path behavior as RTS captures and helpers. |
type_path |
No | - | JSON path to the token type, used for Authorization headers when scheme is omitted. |
expiry_path |
No | - | JSON path to an absolute expiry value (RFC3339, RFC3339Nano, Unix seconds, or Unix milliseconds). |
expires_in_path |
No | - | JSON path to a relative lifetime in seconds. |
ttl |
No | - | How long a cached token stays valid when the command output has no expiry fields. Needs a named definition or cache_key. |
cache_key |
No | - | Names a command-auth slot shared by every directive that uses the same key. Seed it once with the full command, then reuse it with cache_key only. Named definitions are simpler for new files. |
timeout |
No | request timeout | Per-command timeout, bounded by the request timeout. |
@auth command accepts only the options in this table, and any other option is an error. The same rule applies to {auth: {type: "command", ...}} in @apply and @patch. @auth use=<name> accepts only header, scheme, and timeout. Everything else belongs to the definition.
Command lines
cmd splits its value into arguments and runs the first one directly. No shell runs, so pipes, redirects, wildcard patterns (globbing), $VAR, and $(...) are passed to the command as plain text.
- Spaces and tabs separate arguments.
- Single quotes keep everything inside them as written.
- Double quotes group words and accept
\"and\\. - Outside quotes, a backslash escapes a space, a quote, or another backslash. Any other backslash stays, so
C:\tools\gh.exeworks unquoted. - A
{{...}}template stays in one argument even when it contains spaces. Templates expand after the split, so a value with spaces never becomes two arguments.
Wrap cmd in the kind of quote the command line does not use: cmd="gcloud auth print-access-token --account '[email protected]'" or cmd='mycli --name "Ada Lovelace"'. Without quotes, cmd=gh auth token is an error. It does not run gh alone. cmd=mycli --role=admin is an error too, because --role=admin reads as an unknown option. So is a quote left open.
Command auth behavior
- Commands run during auth preparation, before the request is sent.
- Resterm runs
@auth commandwithout a shell. Shell front ends such assh,bash,zsh,cmd, andpwshare rejected. - The command inherits Resterm's environment and runs from the directory of the file that defines it, so a global definition behaves the same from every request file.
- Interactive login flows are not supported. Authenticate the CLI outside Resterm first, then use its non-interactive token-printing command.
- A named definition caches its token for the session, per environment and workspace.
ttl,expiry_path, orexpires_in_pathmake it expire sooner. Editing the definition runs the command again.resterm run --persist-authkeeps the cache between runs. - A
use=name that no file or global definition has fails the request with the line that named it. - An unnamed definition without
cache_keyruns the command for every request. - With
cache_key, the first full directive seeds a reusable command-auth config for that environment. Later directives can use onlycache_key. Empty fields take their values from the seeded config. If the slot has not been seeded yet, Resterm fails with@auth command requires cmd or argv (include it once per cache_key to seed the cache). - Reusing a
cache_keywith different settings for obtaining the token fails right away. These settings includecmdorargv,format, JSON paths,ttl, and related source fields. You can still changeheader,scheme, andtimeoutper request. - Explain preview never runs commands. It only injects a header when a valid cached result already exists, so name the definition or add
cache_keyto preview it. - Text mode accepts exactly one non-empty line from
stdout. Multi-line output fails. Useformat=jsonfor that. - Successful command output is treated as secret. Resterm redacts the raw token and the final injected header value in the explain and history views.
Some APIs take the token with another scheme. The Amazon ECR registry API takes the get-authorization-token value as Basic auth, valid for 12 hours:
# @auth file command name=ecr cmd="aws ecr get-authorization-token --region {{aws.region}} --output text --query authorizationData[].authorizationToken" scheme=Basic ttl=11h
### Image tags
# @auth use=ecr
GET https://{{aws.account}}.dkr.ecr.{{aws.region}}.amazonaws.com/v2/{{repo}}/tags/listaws ecr get-login-password prints the decoded registry password for docker login --username AWS. It is not a header value, so use get-authorization-token for HTTP requests.