Restless tutorial
Restless is an API tester that runs in your browser. Build a request, send it, inspect the response, and add tests that check it automatically. This page walks through every feature, in roughly the order you'll need them.
Everything is stored in this browser until you sign in. Signing in is optional; it adds sync, share links, mock servers and monitors.
1. Your first request
- In the sidebar, open the Examples collection and click Get Lipsum.
- Answer the small sum next to Send (for example
seven + 3 =→ type 10 or ten). - Click Send or press Ctrl/⌘ + Enter.
- The response appears below: the status (200 OK), the time and size, then the body. The Tests tab shows 5/5: the example comes with tests that check the response.
To test your own API, click + in the tab bar for a new request, type a URL such as
https://api.example.com/users, and send. If you leave out https://, it's added for you.
2. The math check
Every Send (and every WebSocket/SSE Connect) needs the sum next to the Send button answered. It keeps bots from using the site's proxy. The numbers are 0–10, with plus or minus, and some numbers are written as words. Answer in digits or words.
- A new question appears after each send. Press ↻ for a different one.
- A correct answer is good for 10 minutes of background requests: the collection runner, OAuth token fetches and GraphQL schema loads. A long run only asks once.
- If you see "Answer the math check next to Send first", fill in the box and try again.
3. Finding your way around
Counters on the editor tabs (for example Headers (2), Tests (3)) show what's filled in at a glance.
4. Request tabs
- New tab: the + at the end of the tab bar, or Alt + T.
- Close: the × on a tab, middle-click it, or Ctrl/⌘ + Shift + W.
- A ● on a tab means it was opened from a collection and has unsaved changes. The note next to the request name says the same.
- Clicking a saved request that's already open switches to its tab instead of opening a copy.
- Tabs and their contents survive reloads. Responses don't; send again after reloading.
5. Method, URL and params
- Pick the method on the left: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, or Realtime WebSocket/SSE.
-
Params and the URL stay in sync. Type
?page=2in the URL and a row appears; add a row and the URL updates. Untick a row to drop it without deleting it. - Paste a cURL command into the URL box and the whole request is filled in: method, URL, headers, body and basic auth.
- Name the request in the title above the URL. That name is used when you save it.
6. Headers
Add a header in the empty row at the bottom of the list; a new empty row appears as you type. Untick to disable,
× to remove. Values can use variables, e.g. X-Request-Id: {{$uuid}}.
You don't need to set Content-Type for JSON, XML, form, multipart or GraphQL bodies; it's added
for you. A header you set yourself always wins.
7. Authorization
Choose a type in the Auth tab. Every field accepts {{variables}}, so keep real secrets in secret variables (see Variables).
| Type | What it sends | Notes |
|---|---|---|
| Bearer token | Authorization: Bearer <token> | Most JWT/OAuth APIs. |
| Basic auth | Authorization: Basic … | Username and password, base64-encoded for you. |
| API key | A header (e.g. X-API-Key) or a query param | Choose Add to: Header or Query param. |
| OAuth 2.0 | Authorization: Bearer <access token> | See below. |
| Digest auth | Answers the server's Digest challenge | Needs Send through the proxy on. |
| AWS Signature v4 | Signed Authorization, X-Amz-Date | Access key, secret, optional session token, region and service (e.g. execute-api, s3). |
OAuth 2.0
- Client credentials: fill in the token URL, client ID, secret and scope. The token is fetched automatically when you send and refreshed when it expires. Get new access token fetches one now.
-
Authorization code (+PKCE): also fill in the authorization URL, then click Get new access
token. A login popup opens. Register this redirect URI with your provider:
https://restlessapp.dev/oauth/callback.html(the exact one is shown in the Auth tab). - The token status shows when it expires and whether there's a refresh token. Clear token forgets it.
8. Request bodies
| Body type | Use it for |
|---|---|
| JSON | Most APIs. Format pretty-prints it. Variables work inside, e.g. {"id": "{{userId}}"}. |
| XML / Raw text | SOAP, XML APIs, plain text. |
| Form (urlencoded) | Classic HTML form posts: key/value rows. |
| Multipart (files) | File uploads. Each row is Text or File; choose a file with Choose file. |
| Binary file | Send one file as the raw body (images, PDFs, zips). |
| GraphQL | Query + variables, with a schema explorer. See GraphQL. |
Files are kept in this browser only. They aren't synced, shared or exported, so after importing on another device you'll need to choose them again.
9. GraphQL
- Set the method to POST, enter the GraphQL endpoint, and choose body type GraphQL.
- Click Load schema. The explorer lists the queries and mutations the API offers.
- Click a field to insert a ready-made query (with its arguments as variables), then adjust it.
- Put variables as JSON in the Variables box, e.g.
{"code": "DE"}.
Try it with https://countries.trevorblades.com/graphql.
10. Variables and environments
Write {{name}} anywhere: URL, params, headers, auth, body, test values. Click
Variables in the top bar to manage them.
-
Environments are sets of values, such as Local, Staging and
Production, each with its own
baseUrland keys. Switch with the Environment dropdown in the top bar. - Globals apply in every environment. The active environment overrides a global with the same name.
- Secret variables 🔒: click the lock on a row. The value is masked, and it is never synced, shared, exported or sent to monitors unless you say so. Put passwords and keys here.
Built-in variables
{{$uuid}} | A random UUID |
{{$timestamp}} | Unix time in seconds |
{{$isoTimestamp}} | e.g. 2026-11-08T03:34:25.716Z |
{{$randomInt}} | A random integer 0–999 |
{{$randomEmail}} | A random test email address |
{{origin}} | This site's address (for its own /api/echo and mocks) |
If a request says Unknown variable {{name}}, it isn't defined in the active environment or Globals.
11. Tests
Tests run after every send, in the collection runner and in monitors. Add them in the Tests tab with + Add test, pick a check and fill in its fields. Results appear in the response's Tests tab (e.g. Tests 4/5).
| Check | Example |
|---|---|
| Status code equals / is one of / is 2xx | 200 · 200,201 |
| Response time below (ms) | 500 |
| Header exists / contains | content-type contains json |
| Body contains text / matches regex | "ok":true · id":\s*\d+ |
| JSON path exists / equals / contains | data.user.email equals [email protected] |
| JSON path is type | items is array (string, number, boolean, object, array, null) |
| JSON array/string length equals | items length 3 |
JSON paths
Use dots and indexes: data.items[0].id. Use [*] to collect every match:
data.items[*].id gives [1, 2, 3], which you can compare with equals
[1,2,3].
A test with a required field left blank fails with "fill in the expected value" rather than passing by accident.
12. Chaining requests
Chaining saves a value from one response as a variable, so later requests can use it. Typical example: log in, keep the token, call the API with it.
Try the built-in chain
- Send Examples → Get a todo. Its Extract tab saves the response's
titleas{{todoTitle}}. - Open Create a post. Its body uses
"title": "{{todoTitle}}". - Send it. The title from step 1 is filled in, and its test confirms it came back.
Make your own
- On the first request, open Extract → + Add extraction.
- Name the variable (e.g.
token), choose a source and where to find it, and a scope. - Send it. The response's Tests tab lists each extracted value.
- Use
{{token}}in the next request: in a header, in Auth → Bearer token, in the URL…
| Source | Find it with |
|---|---|
| JSON path | data.access_token, items[0].id |
| Header | Location, X-Request-Id |
| Set-Cookie | The cookie name, e.g. session |
| Body regex (group 1) | token=(\w+) |
| Status code | (no path needed) |
Scope: Environment saves into the active environment (or Globals if none is active); Globals saves for every environment. To run a whole chain in order, put the requests in a collection and use the runner.
13. Scripts
For logic that the Tests and Extract tabs can't express, use JavaScript in the Scripts tab. The pre-request script runs before sending and can change the request; the test script runs after the response. Use the snippet menus above each editor for ready-made examples.
// Pre-request: compute values, edit the request rl.variables.set("ts", Date.now()); rl.request.headers.upsert({ key: "X-Trace", value: rl.variables.get("ts") }); // Test script: assertions and saving values rl.test("status is 200", () => rl.expect(rl.response.code).toBe(200)); const body = rl.response.json(); rl.test("has users", () => rl.expect(body.users.length).toBeGreaterThan(0)); rl.environment.set("firstUserId", body.users[0].id); console.log("saved", body.users[0].id);
| API | What it does |
|---|---|
rl.environment, rl.globals | .get(k), .set(k, v), .unset(k) on the active environment / globals |
rl.variables | Values for this request or run only |
rl.iterationData | The current row of a runner data file |
rl.request | method, url, headers.get/add/upsert/remove, body.raw |
rl.response | code, status, responseTime, headers.get(), json(), text() |
rl.test(name, fn) | A named test; it fails if fn throws |
rl.expect(x) | Jest-style: toBe, toEqual, toContain, toBeGreaterThan… |
pm.* | Postman-style: pm.test, pm.expect(x).to.equal(…), pm.response.to.have.status(200) |
Scripts run in a locked-down sandbox: no network, no page or cookie access, 5-second limit. Output from
console.log appears in the response's Console tab.
14. Request settings
| Send through the proxy | On (default): the site's server sends the request, so CORS never blocks it and you get full timing and raw details. Off: your browser sends it directly. Needed for localhost and private-network APIs, which the proxy refuses. |
| Follow redirects | Off shows the 3xx response and its Location instead of following it. |
| Use the cookie jar | Send and store cookies automatically (see Cookie jar). |
| Verify TLS certificates | Turn off for self-signed or expired certificates (testing only). |
| Proxy endpoint | Send from another region you've set up (see App settings). |
| Timeout | Milliseconds; 0 uses the default (25 s). |
15. Reading the response
- Body: Pretty (formatted, coloured JSON/XML), Raw, or Preview for HTML pages (scripts disabled), images, PDFs, audio and video.
- JSON filter: type a path such as
data.items[*].nameabove the body to show only that part. - Headers and Cookies: cookies the response set, plus what's in the jar for this URL.
- Tests: pass/fail for each test and script test, and the values that were extracted.
- Console: output from your scripts.
- Raw: the exact HTTP request and response text, with copy buttons. Useful for bug reports.
- The toolbar has Copy body, Save example and HAR (download the exchange for browser dev tools or other tools).
16. Timing and certificates
The Timing tab breaks the request down into phases:
- A long Waiting bar means the server is slow; long DNS/TCP/TLS means connection setup.
- The history sparkline shows how this request's time has changed over your last sends (min, average, p95, max).
- Connection shows the IP address, HTTP version, TLS version and cipher, and the server region that measured it.
- TLS certificate shows who issued it, its expiry and days remaining, the names it covers and the chain.
Through the proxy, phases are measured from the server to the API. In direct mode the browser measures them, and some details are hidden by the browser.
17. Examples and comparing responses
- Save example stores the current response with the saved request (name it, e.g. 200 OK or 404 missing).
- The Compare tab shows a line-by-line diff against the previous response or any saved example. Tick Include status & headers to compare those too. Handy for spotting what changed after a deploy.
- Saved examples also become the responses of a mock server.
18. WebSocket and Server-Sent Events
- Choose WebSocket or SSE in the method menu (Realtime group).
- Enter the URL (e.g.
wss://echo.websocket.org), answer the math check and click Connect. - The log shows messages as they arrive (↓), what you sent (↑) and connection events. JSON is formatted.
- For WebSocket, type in the message box and press Send (or Ctrl/⌘ + Enter). Click Disconnect when done.
Realtime connections go directly from your browser, so the server must allow it. Monitors skip realtime requests.
19. Collections and history
- Save (Ctrl/⌘ + S): the first time, choose a collection or folder, or create a new collection. After that, saving updates it in place.
- + New collection in the sidebar. A collection's ⋯ menu has: new request, new folder, rename, share link, publish as mock server, create monitor, export and delete; ▶ runs it. A request's ⋯ menu has: open in new tab, duplicate, rename, share and delete.
- Folders can be nested. Drag and drop requests and folders to reorder them or move them between folders and collections.
- Search (top of the sidebar) filters collections and history by name, method or URL.
- History keeps your last 100 sends. Click one to open it again.
- Deleting shows an Undo button for a few seconds.
20. Collection runner
Click ▶ on a collection (or Run folder in a folder's menu) to send every request in order and check all the tests.
- Iterations repeats the whole run. Delay waits between requests (for rate-limited APIs).
-
Data file (CSV or JSON): one iteration per row; each column becomes a variable. For example a
CSV with
email,passwordcolumns lets the requests use{{email}}and{{password}}. - Stop on first failure ends the run at the first failed request.
- Values extracted by one request are available to the next, so chains work.
- Afterwards, export a report: JUnit XML (for CI systems), HTML (to share) or HAR.
# users.csv email,role [email protected],admin [email protected],viewer
22. Import, export and code
-
Import (top bar): paste text or choose a file. Restless recognises:
- cURL commands (opened as a new tab)
- Postman collections and environments (v2.0 and v2.1)
- OpenAPI 3 and Swagger 2 specs, JSON or YAML: one request per operation, grouped by tag, with
{{baseUrl}} - HAR files from browser dev tools
- Restless exports
- Export saves everything to a JSON file for backup or moving to another browser. Secret values are left out. Export a single collection from its ⋯ menu.
- Code (next to Save) turns the current request into a snippet: cURL, JavaScript fetch, axios, Node.js, Python requests, Go, PHP, HTTPie or PowerShell. Variables are filled in.
23. Account and sync
- Click Sign in → Create an account (email and a password of 8+ characters).
- Your collections, environments and globals then sync automatically to your account and to any other browser where you sign in. The top bar shows ✓ Synced, ● Unsynced or an error.
- Secret variable values stay on each device; you enter them once per browser.
- If two devices change things at the same time, you're asked which version to keep.
- The account menu has sync on/off, Sync now, change password, sign out and delete account (removes everything stored for it).
25. Mock servers
A mock server answers with your saved examples, so front-end work and demos can go ahead before the real API exists.
- Send requests in a collection and click Save example on the responses you want.
- In the collection's ⋯ menu choose Publish as mock server.
- You get a URL like
https://restlessapp.dev/mock/AbC123xyz. Click Create environment with baseUrl to point your requests at it.
- Routes come from the requests' URLs.
{{baseUrl}}is dropped and{{id}}becomes a wildcard, so/users/42matches/users/{{id}}. - If a route has several examples, add
?status=404to pick one. - Publishing again updates the same URL. Mock servers are public, so don't save secrets in examples.
26. Monitors
A monitor runs a collection on a schedule from the server, even when your browser is closed, and alerts you when it starts failing and when it recovers.
- In a collection's ⋯ menu, choose Create monitor.
- Choose a schedule (every 5, 15 or 30 min, 1, 6 or 12 hours, or daily) and the environment to use.
- Add a webhook URL (Slack or Discord incoming webhook, or any URL) and/or an email for alerts.
- Open it from the Cloud tab to see run history (green/red dots), details of each run, Run now, edit and delete.
Monitors use a snapshot of the collection. Choose Edit → Update the snapshot after changing the requests. They run tests and extractions, not scripts, and can't reach private or localhost addresses.
27. App settings and regions
- Theme: the ◐ button switches light and dark; Settings → Theme also offers System.
- Proxy endpoints: to measure from another part of the world, deploy a second copy of this site in another Netlify region and add it under Settings → Proxy endpoints. Then pick it per request in Settings → Proxy endpoint. The Timing tab shows which region answered.
- Echo endpoint:
{{origin}}/api/echoreturns what it received. Add?status=404or?delay=2000to test error and timeout handling.
28. Keyboard shortcuts
| Ctrl/⌘ + Enter | Send (press again to cancel) |
| Enter in the URL or math box | Send |
| Ctrl/⌘ + S | Save |
| Alt + T | New tab |
| Ctrl/⌘ + Shift + W | Close tab |
| Ctrl/⌘ + L | Select the URL |
| Tab in body and script editors | Indent |
29. Troubleshooting
- "Answer the math check next to Send first"
- Type the answer to the sum next to Send. In the runner, answer the copy shown in the runner window.
- "Requests to private or reserved addresses are blocked"
- The proxy won't call
localhostor internal networks. Turn off Send through the proxy in Settings to call them from your browser. - "The browser blocked or failed the request" (direct mode)
- Usually CORS: the API doesn't allow browser calls. Turn Send through the proxy back on.
- "Unknown variable {{name}}"
- Define it under Variables, check the right environment is selected, or send the request that extracts it first.
- Digest auth doesn't work
- Digest needs the proxy. Turn on Send through the proxy.
- "Upstream request timed out"
- The API took longer than the timeout. Raise it in Settings (up to 25 s).
- A file says "re-select: file not stored"
- Files live in one browser only. Choose the file again.
- The app looks broken after an update
- Your browser may be using old files. Reload with Ctrl/⌘ + Shift + R.