REST StudioA local-first REST API client that lives in VS Code. No account, no cloud sync, no paid tiers: your requests, collections and environments are plain JSON files on your machine. Getting started
Coming from Postman, Insomnia, Hoppscotch or Thunder Client, or have an OpenAPI spec? Use Import Collection in the Collections view (see Import and export).
Drag the bar between the request editor and the response to give either one more room (or focus it and press Up / Down; Home gives the response almost the whole tab). Double-click the bar to reset it. Each tab remembers its size. Single-clicking a request opens it in a preview tab that the next click reuses until you edit it. Use Open in New Tab to keep several requests open. Environments and variablesWrite EnvironmentsThe Environments view lists:
Click an environment to edit it. Each variable has a name, a value, an on/off checkbox and a secret toggle (the lock icon) that masks the value in the editor, in hovers and in autocomplete, and keeps the value in VS Code's secret storage. Changes save as you type. Environment commands (view toolbar and right-click menu): New Environment, Rename, Duplicate, Delete, Set Active / Deactivate, Link .env File…, Unlink .env File and Import .env File….
|
| Variable | Value |
|---|---|
{{#guid}} |
A random UUID |
{{#name}} |
A random full name |
{{#string}}, {{#string, 5}} |
Random letters and digits (default length 10) |
{{#number}}, {{#number, 1, 10}} |
A random integer, 0–1000 by default (bounds are inclusive) |
{{#email}} |
A random @example.com address |
{{#bool}} |
true or false |
{{#enum, red, green, blue}} |
One of the listed values |
{{#date}} |
Current time in Unix milliseconds |
{{#date, 'YYYY-MM-DD hh:mm:ss:fff'}} |
Current local time, formatted |
{{#date, 'X'}} |
Current time in Unix seconds |
{{#date, 'YYYY-MM-DD', {day: -1}}} |
Yesterday |
{{#dateISO}}, {{#dateISO, {hour: 2}}} |
ISO-8601 time in UTC, optionally offset |
Date format tokens: YYYY, YY, MM, M, DD, D, hh/HH (24-hour), mm, ss, fff
(milliseconds), X (Unix seconds), x (Unix milliseconds). Put literal text in square brackets:
'[on] YYYY-MM-DD'. Offsets take year, mon, day, hour, min and sec, positive or
negative.
Filters
Pipe a value through one or more filters: {{name | filter(args) | filter2}}. A quoted string
can start a chain: {{'user:pass' | btoa}}. Array filters accept variables that hold JSON.
| Group | Filters |
|---|---|
| Array | at(i), first, last, filter(age>=18), map(prop), prop(a.b[0]), unique, join(','), slice(start, end), length |
| Math | abs, add(n), subtract(n), multiply(n) |
| String | lower, upper, format('id-{0}'), replace('a', 'b'), split(','), substring(start, end), removeQuotes, removeSpaces, urlEncode, urlDecode, stringify, parseJSON |
| Encoding | atob, btoa, hash('sha256', 'hex'), hmac('key', 'sha256', 'base64') |
| Validation | isEmail, isUrl |
| Tests | setIfValue, setNull (for setting variables from tests) |
| System | exec, readFile('utf8'): off by default, see below |
filter(...) supports =, !=, >, >=, <, <=, *= (contains), ^= (starts with) and
$= (ends with). hash and hmac support MD5, SHA1, SHA256 and SHA512, in hex or base64.
Examples:
{{users | filter(role=admin) | map(email) | join(';')}}
{{#date, 'X' | add(3600)}}
{{payload | hmac('my-signing-key', 'sha256', 'base64')}}
{{'./fixtures/avatar.png' | readFile('base64')}}
Filter arguments are plain text, not variables. If a filter fails (for example add on text),
the request is not sent, and the error says which {{...}} failed.
Collection and folder settings
Right-click a collection or folder and choose Settings… (or click the gear icon) to set what every request inside it inherits:
- Base URL: a request whose URL has no scheme, such as
/users, is sent to the base URL plus that path. The nearest folder with a base URL wins over the collection. - Environment: attach an environment whose variables override the active environment for requests in this collection or folder.
- Default headers: added to every request. A header set closer to the request (a folder, or the request itself) replaces one with the same name.
- Auth: used by requests (and subfolders) whose auth is Inherit from parent.
- Tests: run after every request inside, before the request's own tests. See Tests.
- Pre-requests: run before every request inside. See Request chaining.
- Scripts: pre- and post-request scripts that run around every request inside. See Scripts.
Tests
Open a request's Tests tab and click Add test. Each row reads like a sentence:
source → property → action → expected value, for example Response Code equal 200
or JSON Query json.items[0].id greater than 0. No scripting needed. Tests run every time
you send the request.
- Untick a row to turn it off without deleting it.
- Drag a row by its handle to reorder it, or focus the handle and press Up / Down.
- Expected values may use variables:
{{expectedId}}.
Sources
| Source | Property | Value tested |
|---|---|---|
| Response Code | The status code, e.g. 200 |
|
| Response Body | The body as text | |
| Content-Type | The Content-Type header |
|
| Content-Length | The body size in bytes | |
| Response Time | Total time in milliseconds | |
| Header | Header name (any case) | The header value; repeated headers are joined with , |
| Cookie | Cookie name | The value set by a Set-Cookie header |
| JSON Query | json.items[0].id, see below |
A value from a JSON body |
| Text Query | text \| substring(0, 5) |
The body text, put through filters |
| XML Query | An XPath such as //user/name |
Text of the matching node(s), or the result of count(...), string(...) |
| Custom | {{token \| length}} |
Any variable or filter expression |
JSON queries start with json (optional) and use dots and brackets: json.user.name,
json.items[0], json.items[-1] (last item), json["first name"]. [*] collects a field
from every item, so json.items[*].id is the array of all ids. A query can end in
filters: json.items | filter(price>10) | length.
XML queries are XPath 1.0. Several matching nodes give an array of their text (use count on it); one node gives its text.
Actions
| Action | Passes when the value… |
|---|---|
| equal / not equal | equals the expected value. Numbers compare as numbers, objects and arrays as JSON |
| contains / not contains | text contains the expected text; an array contains the item; an object has the key |
<, <=, >, >= |
is a number in that relation to the expected number |
| is type | is a string, number, boolean, object, array or null |
| count | has that many items (arrays, or a JSON array body), keys (objects) or characters (text) |
| exists / not exists | is present (a missing header, cookie or JSON field does not exist) |
| matches regex | matches a regular expression: ^v\d+ or /^v\d+$/i |
| matches JSON schema | is valid against a JSON Schema, see below |
| set to variable | always passes; saves the value into a variable |
JSON Schema validation
Choose matches JSON schema and either:
- Inline schema: click the edit icon on the row and write the schema, or
- Schema file: enter a path or click Browse. Files inside the workspace are stored
relative to it (for example
schemas/user.json), so the test works for everyone who clones the repository.
Draft-07 is the default; a $schema of draft 2019-09 or 2020-12 selects that draft. String
formats such as email, date-time and uuid are checked. A failure lists the first few
schema errors, for example value/id must be string.
Results
After a request is sent, the response header shows Tests 3/4: passed out of total. Click it, or open the response's Tests tab, to see every result with the actual value and why it failed. Results from collection and folder tests say where they come from. Copy on that tab copies a text report. Failures are also written to the REST Studio output channel.
Saving response values into variables
The set to variable action saves the source's value into an environment, so later requests
can use it. Enter the variable as {{token}}:
| Value | Saves into |
|---|---|
{{token}} |
The active environment, or the Local environment when none is active |
{{token, global}} |
The Global environment |
{{token, local}} |
The Local environment |
An existing variable keeps its secret flag (and is turned on); a new one is added. Tests further down the list already see the new value.
- End the query with
| setIfValueto save only when there is a value:json.refreshToken | setIfValueleaves{{refreshToken}}alone when the response has none. - End the query with
| setNullto clear the variable. - Without
setIfValue, a value that is not found fails the row and the variable is not changed.
Request chaining
A pre-request is a saved request that runs before another one. Use it to log in before calling an API:
- Create a Login request with a test: JSON Query
json.access_tokenset to variable{{token}}. - In the API request (or in its folder or collection settings), open Pre-requests, click Add pre-request and choose Login.
- Use
{{token}}in the API request, for example as a Bearer token.
Sending the API request now sends Login first, saves the token and then sends the API request with it.
- Conditions: click the filter icon on a pre-request to run it only when a variable matches,
for example
{{token}}=(empty) to log in only while there is no token yet. Operators:=,≠,<,≤,>,≥, contains, starts with, ends with. An undefined variable counts as empty; numbers compare as numbers. - Order: collection pre-requests run first, then each folder's down to the request, then the request's own, each list in the order shown (drag to reorder). A pre-request's own pre-requests run before it.
- Once per send: a request runs at most once while sending, even if several levels list it. A folder may list a request that lives inside it (such as its Login request): when that request is sent itself, it skips itself.
- Cycles: if two requests list each other as pre-requests, sending fails with Pre-request cycle: A → B → A instead of looping.
- Failures: if a pre-request cannot be sent, the request is not sent and the error says which pre-request failed. A pre-request whose tests fail still counts as sent.
The response's Tests tab lists the pre-requests that ran, with their status, time and test counts, and the ones that were skipped and why. Pre-requests are not added to Activity.
Scripts
When scriptless tests are not enough, write JavaScript. Open a request's Scripts tab (or the
Scripts section of a collection's or folder's settings), choose Pre-request or
Post-request, and click Add script for an inline script or Add script file to run a
.js file from the workspace.
- Pre-request scripts run before the request is built and sent. They can change it
(
tc.request), set variables, or skip it. - Post-request scripts run after the response arrives and after the scriptless tests. They
can check the response (
tc.test, Chai'sexpectandassert), save values, retry the request or draw a chart.
// Pre-request: sign the request.
const CryptoJS = require('crypto-js');
const timestamp = String(Date.now());
tc.setVar('timestamp', timestamp);
tc.request.setHeader(
'X-Signature',
CryptoJS.HmacSHA256(timestamp, tc.getVar('apiSecret')).toString(),
);
// Post-request: test the response and keep the token for later requests.
tc.test('status is 200', () => expect(tc.response.status).to.equal(200));
tc.test('returns a token', () => expect(tc.response.json).to.have.property('token'));
tc.setVar('token', tc.response.json.token, 'active');
Order. The collection's scripts run first, then each folder's down to the request, then the request's own, each list in the order shown (drag to reorder). That holds for both phases, so a collection script can prepare what a folder's scripts use. Disabled and empty scripts are skipped. Pre-requests run their own scripts too.
Errors. If a pre-request script throws (or times out), the request is not sent and the response says which script failed and on which line. If a post-request script throws, the error is recorded as a failed test, so the request (and a Collection Runner row) fails.
Output. console.log (and info, warn, error, debug) lines appear in the response's
Console tab and in the log. Script tests are listed in the Tests tab with the
scriptless ones and count towards the pass/fail summary.
The tc API
| Member | What it does |
|---|---|
tc.request |
id, name, url, method, headers, body, bodyType, auth. In pre-request scripts set url, method or body, or call setHeader, removeHeader, setQueryParam, removeQueryParam, setBody(value, type?) (objects become JSON). url still has its {{variables}}. |
tc.response |
Post-request only: status, statusText, headers, getHeader(name), contentType, text, json (parsed, or undefined), time (ms), size (bytes), url, cookies. |
tc.info |
requestId, requestName, collectionName, folderName, iteration (1-based, see the runner), phase (pre or post). |
tc.getVar(name) |
The variable's value as the request sees it, or undefined. |
tc.setVar(name, value, scope?) |
Non-strings are stored as JSON. Scope request (the default) lasts for this send, pre-requests included. active writes to the active environment (Local when none is active), global and local to those environments. |
await tc.replaceVars(text) |
Replaces {{...}} in text, with filters and system variables. |
tc.test(name, result) |
Records a test. result is a boolean, or a function (it may be async) that passes unless it throws. |
tc.skipRequest() |
Pre-request only: do not send this request. It shows as Request skipped; in the runner it counts as skipped. |
tc.retryRequest() |
Post-request only: send the request again (pre-request scripts included) once the scripts finish, up to 5 times. Request-scope variables carry over, so you can count attempts. |
await tc.runRequest(nameOrId) |
Sends a saved request (by ID, name or Collection/Folder/Request path) with its own scripts and tests, and returns its response like tc.response. |
await tc.delay(ms) |
Waits. |
await tc.getCookies(url?), tc.setCookie(url, name, value), tc.clearCookies(url?) |
Read and change the cookie jar. getCookies defaults to this request's URL; setCookie also takes a whole Set-Cookie value; clearCookies without a URL clears every cookie. |
await tc.readFile(path, encoding?), tc.getAbsolutePath(path) |
Read a file (utf8 or base64). Relative paths are relative to the workspace folder. |
await tc.exec(command) |
Runs a shell command in the workspace folder and returns its trimmed output. |
await tc.loadFromPath(path) |
Loads a CommonJS file (module.exports), which can require its neighbours with ./name. Use it to share helpers between scripts. |
await tc.loadModule(name, version?) |
Installs an npm package (with npm, which must be on your PATH) into REST Studio's cache, once, and loads it. Afterwards require(name) works too. |
tc.chartHTML(html, data?) |
Shows HTML in the response's Chart tab. data is available to the page as window.chartData. |
Also available: expect, assert and chai (Chai), console,
btoa, atob, fetch, URL, Buffer, crypto (Web Crypto), timers, and require for these
bundled libraries, with nothing to install: ajv (also ajv/dist/2019 and ajv-formats),
axios, chai, crypto-js, fast-xml-parser, papaparse, tough-cookie, uuid, xpath,
@xmldom/xmldom and yaml, plus the Node modules assert, buffer, crypto, events, fs,
http, https, os, path, querystring, stream, string_decoder, timers, url,
util and zlib. Write require('name') with a quoted name.
Scripts are wrapped in an async function, so await works at the top level. Each script may
run for restStudio.scripting.timeoutMs (10 seconds by default), including what it awaits.
Charts
const rows = tc.response.json.items;
tc.chartHTML(
`<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4"></script>
<canvas id="c"></canvas>
<script>
new Chart(document.getElementById('c'), {
type: 'bar',
data: {
labels: chartData.map((r) => r.name),
datasets: [{ label: 'Price', data: chartData.map((r) => r.price) }],
},
});
</script>`,
rows,
);
The chart page runs in a sandboxed frame: its scripts run, but it cannot reach VS Code or your
data, and it can only load scripts and images from the network (styles must be inline). The last tc.chartHTML call of a
send wins.
Postman scripts
Postman scripts run as they are: pm.environment, pm.globals, pm.collectionVariables,
pm.variables, pm.iterationData, pm.request (URL, headers and body), pm.response
(including pm.response.to.have.status(200), to.be.ok, to.have.header(...) and
to.have.jsonBody(...)), pm.test, pm.expect, pm.sendRequest, pm.cookies, pm.info,
pm.execution.skipRequest() and the older postman.setEnvironmentVariable-style calls.
pm.collectionVariables reads and writes the active environment. postman.setNextRequest is
not supported (order requests in the runner instead), and pm.visualizer templates are shown as
plain HTML rather than Handlebars.
Scripts imported from a Postman collection keep their pm.* code. To make one native, click
Convert pm.* next to it, or run REST Studio: Convert Postman Script to tc.* in a
JavaScript file (on the selection, or the whole file). Calls with no tc equivalent are left as
they are and listed.
Script files and IntelliSense
A script file is a plain .js file in your project, so it is versioned with your code and you
can edit it with VS Code's full JavaScript support. Relative paths are relative to the first
workspace folder. Click Open next to a script file to edit it (REST Studio offers to create
it).
Run REST Studio: Add Script Type Definitions to write rest-studio.d.ts to the workspace
folder, then add /// <reference path="./rest-studio.d.ts" /> (with the right relative path)
at the top of a script file to get completions and documentation for tc, expect and
assert. Inline scripts get completions for tc, pm and console as you type.
Custom filters
Set restStudio.scripting.customFilters to a JavaScript file whose module.exports maps names
to functions. Each one becomes a filter: it gets the piped value and the filter's
arguments (as text) and may return a promise.
// filters.js
module.exports = {
initials: (value) =>
String(value)
.split(' ')
.map((word) => word[0])
.join(''),
pad: (value, width) => String(value).padStart(Number(width), '0'),
};
{{fullName | initials | upper}} {{orderId | pad(8)}}
Built-in filters win over custom ones with the same name. The file is reloaded when it changes, and the editor offers its filters in autocomplete.
Trust
Scripts and custom filters run with the same access as VS Code itself: they can read files, run
commands and reach the network. So they run only in
trusted workspaces. In an untrusted
workspace they are skipped, the request is sent without them, and the Console tab says so.
Turn them off everywhere with restStudio.scripting.enabled. Scripts are kept apart from
REST Studio's own objects (in a separate node:vm context), but that is not a security
boundary: only run scripts you trust. The timeout stops a script that loops without awaiting
anything, but cannot interrupt one that loops forever between awaits; reload the window if
that happens.
Collection Runner
The runner sends every request in a collection or folder and shows which ones passed their tests. Open it with the Run Collection button (▷▷) on a collection or folder, from its context menu, or with REST Studio: Run Collection… (which asks which collection or folder to run). Each collection and folder has its own Run: tab, and remembers its settings.
- Choose the requests to run. They are listed in collection order (folders first, as in the Collections view). Untick a request to skip it, or untick a folder to skip everything in it. Drag a request by its handle to change the order (or focus the handle and press Up / Down); Reset order goes back to the collection order.
- Set the options:
- Mode: Sequential sends one request at a time, in the order shown. Parallel sends up to Concurrency requests at once.
- Iterations: how many times to run the whole list.
- Delay: the pause between requests (sequential) or between iterations (parallel), in milliseconds.
- Stop on first failure: end the run at the first request that fails a test or gets no response.
- Click Run. Cancel stops the run and cancels any requests in flight.
Requests run exactly as they do from their own tab: with the active environment, inherited settings, pre-requests and tests. Set to variable tests save their values as usual, so a Login request early in the run can provide the token for the requests after it. Runs are not added to Activity.
Data-driven runs
Click Choose CSV or JSON… to attach a data file. Each row is one iteration, and its columns
become variables for that iteration, so {{user}} in a URL, header, body or test takes the
value of the user column. Choosing a file sets Iterations to the number of rows; with more
iterations than rows, the rows repeat. {{iteration}} is the iteration number (1, 2, …).
user,password,expectedStatus
ada,secret1,200
bob,wrong,401
[
{ "user": "ada", "password": "secret1", "expectedStatus": 200 },
{ "user": "bob", "password": "wrong", "expectedStatus": 401 }
]
- CSV: the first line names the columns. Quote fields that contain commas, quotes (
"") or line breaks. Tab-separated files work too. - JSON: an array of objects (or one object for a single iteration). Numbers, booleans and
nested values become text (
200,true,{"a":1}).
Data file variables override every environment while the run lasts (see Which value wins); they never change your environments. Files inside the workspace are stored with a relative path, so a runner setup can be shared with the repository.
Results
Results appear as each request finishes: progress, passed, failed (a test failed) and error
(no response) counts, test totals and the run time. A request whose pre-request script calls
tc.skipRequest() counts as skipped. Each row shows the request, HTTP status,
time, size and tests. Failures only hides the requests that passed.
Click a row to see that request's full response (body, headers, cookies and every test result) and the pre-requests that ran. Open request opens it in a tab. The last 1,000 responses of a run are kept for this.
Reports
Pick a format and click Export to save the latest run's report:
| Format | For |
|---|---|
| HTML | A self-contained page to read or share (light and dark) |
| JSON | Every result as data: requests, statuses, times, test results and errors |
| JUnit XML | CI test reporting (GitHub Actions, GitLab, Jenkins, Azure Pipelines) |
| CSV | One row per request and iteration, for spreadsheets |
| NUnit XML | NUnit 3 format, for tools such as Xray |
In the XML reports, each request is a test suite and each of its tests a test case; a request without tests is one case that passes when a response arrives. The same reporters will be used by the command-line runner (v0.9).
Authentication
Open a request's Auth tab and pick a type. Collections and folders have the same editor in
their Settings…; a request (or subfolder) set to Inherit from parent uses the nearest
folder's auth, then the collection's. Every field accepts {{variables}}, so credentials can
live in a secret variable or a .env file.
| Type | What is sent |
|---|---|
| Basic | Authorization: Basic base64(username:password) |
| Bearer token | Authorization: Bearer <token> (the prefix is editable) |
| API key | A header or query parameter with your key name and value |
| Digest | The request; when the server answers 401 with a digest challenge, it is sent again with the answer |
| NTLM | The NTLMv2 handshake on one connection, then the request |
| AWS Signature v4 | A signed request: Authorization, X-Amz-Date and, when needed, X-Amz-Security-Token |
| OAuth 2.0 | Authorization: Bearer <access token>, fetching and refreshing the token for you |
An Authorization header you add yourself on the Headers tab always wins over the auth
type.
Digest
Supports MD5, SHA-256 and SHA-512-256 (and their -sess variants), with qop=auth or
auth-int. Redirects answer a new challenge if the next URL asks for one.
NTLM
Enter the username as user with a separate Domain, or as DOMAIN\user. REST Studio sends
NTLMv2 only (never NTLMv1 or LM). NTLM authenticates a connection, so these requests always use
HTTP/1.1 and a private connection, also through a proxy.
AWS Signature v4
Enter the access key ID, secret access key, region (default us-east-1) and service (for
example execute-api, s3, lambda). Add a session token for temporary credentials. REST
Studio signs host, content-type and every x-amz-* header, and hashes the body. Multipart
and file bodies are signed as UNSIGNED-PAYLOAD. For S3 the path is encoded once and
X-Amz-Content-Sha256 is added, as S3 expects. Each redirect is signed again.
OAuth 2.0
Choose a grant type and fill in what your identity provider gives you:
| Grant | Fields | How the token is fetched |
|---|---|---|
| Authorization Code | Auth URL, Token URL, Client ID, Client secret, Callback URL | Your browser opens to sign in; then the code is exchanged |
| Implicit | Auth URL, Client ID, Callback URL | Your browser opens to sign in; the token comes back directly |
| Client Credentials | Token URL, Client ID, Client secret | Directly, without a browser |
| Password | Token URL, Client ID, Client secret, Username, Password | Directly, without a browser |
All grants take an optional Scope and Audience.
- Callback URL. Leave it empty and VS Code receives the redirect itself, at
vscode://maratib.rest-studio/oauth2/callback(in a remote or web window VS Code turns this into an address your browser can reach). Register that URL with your provider. If your provider only acceptshttpaddresses, enter one such ashttp://localhost:7777/callback: REST Studio listens on that port only while you sign in. Onlylocalhostand127.0.0.1are accepted. - PKCE (Authorization Code) is on by default and uses
S256. Turn it off only for servers that reject it. - Send credentials as a Basic auth header (the default, RFC 6749 §2.3.1) or as
client_id/client_secretin the request body, depending on what the server expects. - Token prefix is what goes before the token in the
Authorizationheader (defaultBearer). - State is checked on every redirect, so a response that REST Studio did not ask for is rejected.
Getting a token. When you send a request without a token, REST Studio gets one first: for Authorization Code and Implicit, your browser opens and a notification waits (cancel it at any time; it gives up after 5 minutes). The section under the fields shows the cached token's first and last characters and when it expires, with these buttons:
- Get New Token: fetch a new token now, replacing the cached one.
- Refresh: use the refresh token (when the server gave one).
- Clear: forget the cached token.
Token cache and refresh. Tokens are cached in VS Code's secret storage, never in your
request files. Requests that use the same auth server, client, scope (and username, for the
password grant) share one token. With Refresh the token automatically on (the default), an
expired token is refreshed with its refresh token before the request is sent; if there is no
refresh token or refreshing fails, a new token is fetched. Turn on
restStudio.oauth2.tokenPerEnvironment to keep a separate token for each active environment,
for example one for Dev and one for Prod. REST Studio: Clear OAuth 2.0 Tokens forgets
every cached token.
Copy as cURL uses the cached token if there is one; it never opens the browser.
Cookies
REST Studio has a cookie jar, like a browser. Cookies that responses set (including on
redirects) are saved, and sent with later requests to matching sites, following the usual
domain, path, Secure and expiry rules. Cookies for public suffixes such as .com are
refused. A Cookie header you set yourself is kept, and stored cookies with other names are
added to it.
- The response's Cookies tab lists the cookies that response set.
- REST Studio: Manage Cookies (also in the Collections and Activity view menus, and the globe button on the Cookies tab) opens the Cookie Manager: cookies grouped by domain, with a filter, add, edit, delete, delete a domain, and Delete all.
- REST Studio: Clear All Cookies empties the jar.
- Turn the jar off with
restStudio.cookies.enabled.
Cookies are kept in cookies.json in REST Studio's storage until they expire or you delete
them, including session cookies, so a login survives restarting VS Code.
Proxy and certificates
Proxy
By default (restStudio.proxy.mode: system) REST Studio uses VS Code's http.proxy setting,
or else the HTTPS_PROXY / HTTP_PROXY environment variables, and skips hosts listed in
http.noProxy or NO_PROXY. Set the mode to custom and fill in restStudio.proxy.url to
use a different proxy, or off to never use one.
- Proxy URLs look like
http://proxy.corp:3128. Add credentials for proxies that need them:http://user:password@proxy.corp:3128(Basic proxy auth). restStudio.proxy.bypasslists hosts that connect directly, in every mode:localhost,api.corp.test(and its subdomains),.corp.test,*.corp.test,host:8443, or*.- HTTPS requests go through the proxy with
CONNECT; plain HTTP requests are forwarded.
Client certificates
For servers that require mutual TLS, add certificates to restStudio.certificates in your
user settings:
"restStudio.certificates": [
{ "host": "api.corp.test", "pfx": "~/certs/me.p12", "passphrase": "…" },
{ "host": "*.internal.test:8443", "cert": "certs/client.pem", "key": "certs/client.key" }
]
hostmatches exactly (*.for subdomains,:portfor one port); the first match wins.- Use either
pfx(PKCS#12) orcertandkey(PEM).caadds CA certificates to trust for that host, for example an internal CA. - Relative paths are relative to the first workspace folder;
~is your home folder.
Turn off Verify SSL certificates (in the request's Settings tab, or
restStudio.http.verifySsl) only for servers with self-signed certificates you trust.
Proxy and certificate settings can only be set in user (machine) settings, not in a workspace's settings, so a repository you open cannot send your requests elsewhere.
Logs
REST Studio: Show Logs opens the REST Studio output channel. It lists every request sent, its status and time, redirects, undefined variables, test failures, token requests and errors. For more detail, choose Debug or Trace from the gear menu on the Output panel (or run Developer: Set Log Level…):
- Debug adds each request's and response's headers, redirect hops, proxy use and auth handshakes.
- Trace also adds the first 2 KB of each response body.
Script output is logged too, as [script] <script>: <message>, along with script errors and
retries.
Authorization, Proxy-Authorization, Cookie, Set-Cookie and X-Amz-Security-Token
values are always shown as ****.
Code snippets
Click Code (</>) at the top of a request tab, or run REST Studio: Generate Code
Snippet, to see the request as code in place of the response. Generate Code… in a
request's context menu in the Collections view opens the request with its code showing. Click
Code again or Close to get the response back.
| Language | Library |
|---|---|
| Shell | cURL |
| HTTP | A raw request, as used by .http files |
| JavaScript | Fetch, Axios |
| Python | requests, http.client |
| C# | HttpClient |
| PHP | Laravel (Http facade) |
| Dart | http |
| Flutter | Dio |
| PowerShell | Invoke-WebRequest |
| Swift | URLSession |
The code follows the request as you edit it, including what it inherits: the base URL, default headers and auth of its folder and collection. The SSL verification and follow redirects settings are carried over where the library supports them. File fields and binary bodies read the file from its path. Digest, NTLM and AWS Signature auth are negotiated while sending, so the snippet notes them instead of including them (cURL includes them).
- Resolve variables on (the default): variables are filled in as they would be sent, like
Copy as cURL. Variables that nothing defines stay as
{{name}}and are listed above the code. The code then contains your real values, including secrets and tokens, so check it before sharing it. - Resolve variables off:
{{variables}}stay as placeholders, so the code is safe to share. An OAuth 2.0 token appears as<access-token>.
Copy puts the code on the clipboard. The language and the Resolve variables choice are remembered per tab.
Import and export
Importing
Click Import Collection in the Collections view's title bar (or run REST Studio: Import Collection…) and choose where the data comes from:
- From File…: one or more
.jsonor.yamlfiles. - From URL…: for example an OpenAPI document published by an API.
- From Clipboard: pasted JSON or YAML. A
curlcommand is passed to Import cURL.
The format is detected from the content:
| Format | What is imported |
|---|---|
| Postman v2.0 / v2.1 | Collections with folders, requests, auth and scripts at every level, and collection variables (as an environment attached to the collection). Postman environments. |
| Insomnia v4 (JSON) | Each workspace as a collection, with folders, folder auth and headers. Each sub-environment becomes an environment, with the base environment's values. |
| Insomnia v5 (YAML) | The same, from Insomnia 11's YAML collections. |
| Hoppscotch | Collections (one or an exported list) with nested folders, auth and headers. Hoppscotch environments. |
| OpenAPI 3.0 / 3.1 | One collection, with a folder per tag. See OpenAPI. |
| Thunder Client | Exported collections and environments, with folder and collection settings and their tests. Also its thunder-tests folder (see below). |
| REST Studio | Files written by Export Collection in REST Studio format. |
Imported items always get new IDs, so importing the same file twice gives two copies and never overwrites your data. Imported environments are standard environments; switch to one with Ctrl/Cmd+E.
Variable syntax is converted: Insomnia's {{ _.name }} and Hoppscotch's <<name>> become
{{name}}. Postman's dynamic variables map to system variables where there
is an equivalent ({{$guid}} → {{#guid}}, {{$timestamp}} → {{#date, 'X'}},
{{$randomInt}} → {{#number}}, and so on).
Anything that cannot be imported exactly is listed in the notification's Show Details (the
REST Studio log): for example Hoppscotch scripts (their pw.* API is not supported),
Thunder Client pre-requests (add them again in Pre-requests), Thunder Client tests without
an equivalent action, and auth types REST Studio does not have.
Moving from Thunder Client. Export each collection and environment from Thunder Client
(… → Export) and import the files. Or import its data directly: pick
thunder-tests/thunderCollection.json (or the same file in Thunder Client's global storage) and
REST Studio also reads thunderclient.json (the requests) and thunderEnvironment.json from
the same folder. Thunder Client's Global and Local environments come across as Thunder Global
and Thunder Local.
OpenAPI
- The first server is the collection's base URL, with server variables set to their defaults.
- Each operation becomes a request named after its summary (or operation ID), in a folder for its first tag. Untagged operations sit at the collection root.
- Path, query and header parameters use their examples (or defaults). Required query parameters are enabled; optional ones are added but switched off.
- Request bodies use the example, or one built from the schema (following
$refs). Form and multipart bodies get a field per property. - Security schemes become auth: HTTP Bearer uses
{{token}}, Basic and Digest use{{username}}and{{password}}, API keys use{{apiKey}}, and OAuth 2.0 flows use{{clientId}}and{{clientSecret}}. Set those variables in an environment. Global security is the collection's auth; operations with their own security override it. - Swagger 2.0 is not supported. Convert it to OpenAPI 3 first (for example in the Swagger Editor).
Exporting
Right-click a collection and choose Export Collection… (or run REST Studio: Export Collection…), pick a format and where to save the file:
| Format | Notes |
|---|---|
| REST Studio (JSON) | Everything: folders, settings, tests, pre-requests and scripts, plus the environments attached to the collection or its folders (secret values left blank). Sorted keys, so the file diffs well. |
| Postman Collection v2.1 | Folders, requests, auth and inline scripts (as prerequest and test events). Postman has no base URLs or default headers, so they are applied to each request. The attached environment's variables become collection variables. |
| OpenAPI 3 (YAML / JSON) | A path per request, with its parameters, an example body and security schemes. {{variables}} in the base URL become server variables. |
Postman and OpenAPI have no scriptless tests or pre-requests, so those are left out, and the
notification says so. Script files are not embedded in a Postman export, and OpenAPI has no
scripts at all. OpenAPI also cannot describe NTLM or AWS Signature auth, custom methods
(such as PROPFIND), or two requests with the same method and path; those are left out with a
note in the log.
Project storage and Git Sync
Every project keeps its REST Studio data in a .rest folder at its root, created the first
time you use REST Studio in that project. Everything about the project lives there, so copying
or moving the project folder takes its collections, environments, Activity and settings with it.
Commit .rest/ to share the collections with your team through Git.
When no folder is open, REST Studio uses VS Code's global storage instead.
What goes where
.rest/
├── .gitignore # keeps local/ out of Git
├── rest-studio.json # layout and schema version
├── collections/ # shared: collections, folders and requests
├── environments/ # shared: Global and your other environments, secret values blanked
└── local/ # this machine only (git-ignored)
├── environments.json # the Local environment
├── settings.json # the active environment
├── history.json # Activity
├── cookies.json # the cookie jar
└── runner.json # Collection Runner settings
The .gitignore inside .rest excludes local/, so teammates share collections and
environments but each keeps their own Local environment, Activity and cookies. local/ still
moves with the folder when you copy the project.
Secret variable values and OAuth 2.0 tokens are kept in your operating system's keychain, not in
.rest. They are matched by environment ID, so they keep working for a copied project on the
same machine. On another machine, enter them again.
Put passwords and tokens in the Local environment or in secret variables, and refer to them
as {{variables}} in shared requests. Teammates set their own values. Values you type directly
into a request (for example a Bearer token) are saved with the request, so they are shared.
REST Studio: Suggest .gitignore Entries offers lines for the project's .gitignore: any
.env file linked to an environment, plus .env, .env.* and !.env.example. Pick the ones
you want and they are appended.
Data from earlier versions
Before v0.6, REST Studio kept everything in global storage. The first time you open a project
whose .rest folder has no collections, REST Studio offers to copy your global collections,
requests and environments into it. You can do this later with REST Studio: Copy Data from
Global Storage. Global storage is not changed, so you can copy the same data into several
projects.
Other locations
restStudio.storage.location |
Where the data goes |
|---|---|
workspace (default) |
.rest in the first workspace folder; restStudio.storage.workspacePath renames it |
custom |
An absolute folder in restStudio.storage.customPath, shared by several workspaces |
global |
VS Code's global storage, as before v0.6 |
restStudio.storage.workspacePath must stay inside the workspace. A custom path is only read
from your user settings.
File layout
.rest/
├── collections/
│ └── <collection-id>/
│ ├── collection.json # name, folders, settings
│ └── requests/
│ └── <request-id>.json # one file per request
└── environments/
└── <environment-id>.json
Files are named by ID, so renaming or moving an item changes one file instead of renaming it. Keys are sorted and only items you change are rewritten, which keeps diffs small.
One file per request (the default) is the friendliest for merges: two people editing
different requests never touch the same file. If you prefer fewer files, run REST Studio:
Storage: Use One File per Collection, which keeps each collection and its requests in
collections/<collection-id>.json. Storage: Use One File per Request converts back. The
choice is saved in rest-studio.json, so the whole team uses the same layout.
Pulling changes
REST Studio watches the folder. After a git pull, a branch switch or any other change on disk,
the Collections and Environments views reload by themselves. Open request tabs show the pulled
version, unless they have unsaved edits, which are kept. A request deleted upstream stays open
as an unsaved request.
If a file cannot be read, for example because it still has Git conflict markers or was written by a newer version of REST Studio, it is skipped with a warning and left untouched. Fix the file and REST Studio reloads it. If two people both created the Global environment, the copies are merged into one when the folder is loaded.
REST Studio: Show Storage Folder reveals the folder in use.
Settings
| Setting | Default | Description |
|---|---|---|
restStudio.http.timeout |
30000 |
Request timeout in milliseconds |
restStudio.http.followRedirects |
true |
Follow redirects |
restStudio.http.maxRedirects |
10 |
Maximum redirects to follow |
restStudio.http.verifySsl |
true |
Verify TLS certificates |
restStudio.http.version |
1.1 |
1.1 or 2 |
restStudio.history.maxEntries |
500 |
Requests kept in Activity; 0 turns history off |
restStudio.response.wordWrap |
false |
Wrap long response lines |
restStudio.response.maxSizeMB |
50 |
Larger responses are truncated |
restStudio.variables.osEnvironment |
true |
Make OS environment variables available as {{NAME}} (trusted workspaces only) |
restStudio.variables.allowSystemFilters |
false |
Allow exec (runs a shell command) and readFile (reads a file) in templates (trusted workspaces only) |
restStudio.scripting.enabled |
true |
Run pre- and post-request scripts and custom filters (trusted workspaces only) |
restStudio.scripting.timeoutMs |
10000 |
How long one script may run, in milliseconds |
restStudio.scripting.customFilters |
"" |
JavaScript file of custom filters (absolute, ~/... or workspace-relative) |
restStudio.cookies.enabled |
true |
Save cookies from responses and send them with later requests |
restStudio.oauth2.tokenPerEnvironment |
false |
Keep a separate OAuth 2.0 token per active environment |
restStudio.proxy.mode |
system |
system (VS Code http.proxy, then HTTPS_PROXY/HTTP_PROXY), custom or off |
restStudio.proxy.url |
"" |
Proxy for custom mode |
restStudio.proxy.bypass |
[] |
Hosts that skip the proxy |
restStudio.certificates |
[] |
Client certificates per host, see Client certificates |
restStudio.storage.location |
workspace |
workspace (a .rest folder in the project), custom or global, see Project storage |
restStudio.storage.workspacePath |
.rest |
Folder for workspace storage, relative to the workspace folder |
restStudio.storage.customPath |
"" |
Absolute folder for custom storage (user settings only) |
The HTTP settings are defaults; each request can override them in its Settings tab.
Data and privacy
REST Studio stores its data in the project's .rest folder (see
Project storage), or in VS Code's global storage when no folder
is open. Nothing is sent anywhere except the
requests you choose to send.
Secret variables are masked in the UI, and their values are kept in VS Code's
secret storage (your
operating system's keychain), not in environments.json, which only records that the variable
exists. Turning secret off moves the value back into environments.json; deleting the
variable or its environment removes the stored value. Secret values written in plain text by
older versions are moved into secret storage the first time REST Studio loads. You can also keep
values in a .env file (git-ignored) and link it.
OAuth 2.0 tokens are kept in secret storage too. Cookies are kept in cookies.json next to
your other data (in .rest/local/ for a project); delete them in the Cookie Manager. Proxy and certificate settings are only
read from your user settings, never from a workspace's settings.
exec and readFile can run commands and read files, so they are off until you turn on
restStudio.variables.allowSystemFilters, and they never run in an untrusted workspace. OS
environment variables are also unavailable in untrusted workspaces. Scripts and custom filters
run only in trusted workspaces (see Trust). Packages installed with tc.loadModule
are downloaded from the npm registry into VS Code's global storage for REST Studio.