Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>REST StudioNew to Visual Studio Code? Get it now.
REST Studio

REST Studio

Maratib Ali Khan

| (0) | Free
Local-first REST API client
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

REST Studio

A 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

  1. Open the REST Studio view in the activity bar.
  2. Click New Request (or run REST Studio: New Request), enter a URL and press Enter or Ctrl/Cmd+Enter.
  3. Press Ctrl/Cmd+S to save it into a collection.

Coming from Postman, Insomnia, Hoppscotch or Thunder Client, or have an OpenAPI spec? Use Import Collection in the Collections view (see Import and export).

Shortcut Action
Ctrl/Cmd+Enter Send (in a request tab)
Ctrl/Cmd+S Save
Ctrl/Cmd+E Switch environment
Ctrl/Cmd+U Import cURL
Shift+Alt+F Format body
Alt+Z Toggle response word wrap
Esc Exit response full screen
Ctrl/Cmd+Enter (tree) Open the selected request in a new tab
F2 / Delete (tree) Rename / delete the selected item

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 variables

Write {{name}} anywhere in a request: the URL, query parameters, path variables, headers, the body, form fields and auth. REST Studio replaces it with the variable's value when the request is sent. The request itself keeps the {{name}} text, so Activity and saved requests stay reusable.

Environments

The Environments view lists:

  • Global: variables every request can use.
  • Local: machine-only values, such as secrets. Local is never synced to a workspace folder.
  • Your environments (for example Dev, Staging, Prod): switch between them with Ctrl/Cmd+E, the globe item in the status bar, the environment button in a request tab, or Set Active in the view. One environment is active at a time, or none.

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….

.env files

  • Link a .env file to any environment (including Global and Local). Its values are read every time you send, so edits to the file apply immediately, and the file stays the single source of truth. Paths inside the workspace are stored relative to it.
  • Import a .env file to copy its values into a new or existing environment once.

.env files support KEY=value, export KEY=value, # comments, and single-, double- or back-quoted values. Double-quoted values may span lines and understand \n.

Which value wins

When several places define the same name, the one lowest in this list wins:

  1. Built-in variables: {{request_name}}, {{collection_name}}, {{folder_name}}
  2. OS environment variables, for example {{HOME}} (see restStudio.variables.osEnvironment)
  3. Global environment
  4. Local environment
  5. The active environment
  6. The environment attached to the collection, then to each folder down to the request
  7. Collection Runner data: the current data file row and {{iteration}} (only during a run)
  8. Request-scope variables, set by scripts with tc.setVar(name, value) (see Scripts)

A .env file linked to an environment sits just below that environment's own variables. Disabled variables are ignored. A value may refer to other variables, for example baseUrl = https://{{host}}/v1.

Editor help

  • {{variables}} are highlighted in the URL bar, tables, auth fields and the body editor.
  • Hover a variable to see its resolved value and where it comes from. Secret values are masked, and exec/readFile are never run by a hover.
  • Type {{ to autocomplete variable names and system variables, and | to autocomplete filters.
  • A variable that nothing defines gets a wavy underline, and a warning under the URL bar lists every undefined variable in the request. Undefined variables are sent as written, for example {{token}}.

System variables

These produce a new value for every reference, every time you send:

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 | setIfValue to save only when there is a value: json.refreshToken | setIfValue leaves {{refreshToken}} alone when the response has none.
  • End the query with | setNull to 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:

  1. Create a Login request with a test: JSON Query json.access_token set to variable {{token}}.
  2. In the API request (or in its folder or collection settings), open Pre-requests, click Add pre-request and choose Login.
  3. 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's expect and assert), 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.

  1. 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.
  2. 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.
  3. 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 accepts http addresses, enter one such as http://localhost:7777/callback: REST Studio listens on that port only while you sign in. Only localhost and 127.0.0.1 are 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_secret in the request body, depending on what the server expects.
  • Token prefix is what goes before the token in the Authorization header (default Bearer).
  • 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.bypass lists 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" }
]
  • host matches exactly (*. for subdomains, :port for one port); the first match wins.
  • Use either pfx (PKCS#12) or cert and key (PEM). ca adds 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 .json or .yaml files.
  • From URL…: for example an OpenAPI document published by an API.
  • From Clipboard: pasted JSON or YAML. A curl command 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.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft