1 Getting Started
CALLSTACK is a native desktop REST API client built with Tauri 2 and React. It uses a Rust HTTP engine under the hood — so there are no CORS restrictions, no browser Origin headers injected automatically, and no need for a proxy server. All data is stored locally in a SQLite database. No account is required.
System Requirements
| Platform | Minimum Version | Notes |
|---|---|---|
| macOS | 10.13 (High Sierra) | Apple Silicon (M1/M2/M3) and Intel fully supported |
| Linux | glibc 2.29+ | AppImage works on most modern distros (Ubuntu 20.04+, Fedora 32+) |
| Windows | Windows 10 (1803+) | x64 only; ARM64 Windows not supported |
Installation
macOS — Homebrew (recommended):
brew tap kinarix/callstack
brew install --cask callstack
All platforms — direct download: Visit GitHub Releases and download the package for your platform:
- macOS:
.dmg— open the disk image and drag CALLSTACK to your Applications folder. - Linux:
.AppImage— make executable (chmod +x CALLSTACK*.AppImage) then run it directly. No installation needed. - Windows:
.msi— run the installer. CALLSTACK will be added to your Start menu.
First Launch
- Open CALLSTACK from your Applications folder (macOS), launcher (Linux), or Start menu (Windows).
- On first launch the app creates a local SQLite database:
- macOS:
~/Library/Application Support/callstack/callstack.db - Linux:
~/.local/share/callstack/callstack.db - Windows:
%APPDATA%\callstack\callstack.db
- macOS:
- Optionally sign in with Google via Settings → Account. This scopes your data to your email address — useful if multiple developers share one machine. It is not required for any feature.
- Click + Project in the sidebar to create your first project and start making requests.
Your First Request
A quick walkthrough to make your first API call:
- Click + Project and name it (e.g. "My API").
- Inside the project, click + Request. A new request appears in the sidebar and opens in the editor.
- Set the method to GET and enter a URL:
https://httpbin.org/get. - Click Send (or press ⌘X on Mac / CtrlX on Windows/Linux).
- The response appears in the right panel: status 200, the JSON body, headers, and timing information.
2 Core Concepts
Data Hierarchy
Everything in CALLSTACK is organised in a tree structure:
Project
├── Environment (multiple per project)
├── Folder (optional, nestable)
│ └── Request
├── Request (directly in project root)
├── Automation
└── Data File (CSV)
- Projects are top-level containers — think of each as a separate API or service (e.g. "Payment API", "Auth Service", "Internal Admin").
- Folders group related requests within a project. They can be nested to arbitrary depth. Folders are purely organisational — they don't affect how requests are sent.
- Requests are individual API calls with their own URL, method, headers, body, and scripts. Each request is independent.
- Environments are named sets of variables attached to a project (e.g. "Development", "Staging", "Production"). Only one environment can be active at a time per request.
- Automations are visual multi-step workflows that chain requests together with loops, conditionals, and parallel branches.
- Data Files are CSV tables used to drive data-iteration in automations.
Deleting a project cascades — all its folders, requests, environments, automations, and data files are removed permanently.
Persistence
All data is persisted to SQLite immediately. There is no "save" button — every change (renaming a request, editing a URL, toggling a header) is written to disk automatically. Closing the app without explicitly saving is safe.
Environments
Each project can have multiple environments. An environment holds key-value pairs that are substituted into your requests using {{variableName}} syntax. This lets you keep a single set of requests that work across dev, staging, and production by simply switching the active environment.
Variables can be marked as Secret to mask them in the UI. Environment variables can also be read and written by scripts at runtime.
Request Lifecycle
When you click Send, CALLSTACK executes the following sequence:
- Resolve — All
{{variable}}and{{$token}}references in the URL, params, headers, and body are resolved against the active environment. - Pre-script — If a pre-request script is attached, it runs in a JavaScript sandbox. It can read and mutate
envvariables, modifyrequestheaders/body, and callfakerto generate data. - Send — The Rust HTTP client sends the fully-resolved request. No CORS, no injected browser headers, no proxy needed.
- Post-script — If a post-response script is attached, it runs with access to the
responseobject. Use it to extract tokens, run assertions withtest(), or emit values for downstream automation steps. - Persist — The response is saved to the database if you have history saving enabled, and logged to the Request Log footer.
- Display — The response appears in the viewer: body, headers, timing, and test results.
Why a Native HTTP Client?
Most web-based API tools (Insomnia web app, Bruno web, etc.) are constrained by the browser's networking stack. That means:
- CORS restrictions block requests to APIs that don't send permissive
Access-Control-Allow-Originheaders. - Browsers automatically inject headers like
Origin,Referer, andUser-Agentthat you can't remove. - Certain headers (
Host,Cookie,Authorizationwith some values) are restricted.
CALLSTACK uses a Rust HTTP client (reqwest) that operates outside the browser entirely. You get full control over every header and byte sent on the wire.
3 Request Builder
Method & URL
Select the HTTP method from the dropdown on the left of the URL bar. The badge changes colour to match the method type:
Available methods: GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS. Type or paste the full URL in the bar. Use {{variable}} syntax to substitute environment values:
{{BASE_URL}}/api/v1/users/{{USER_ID}}/profile
The URL bar accepts both http:// and https://. If you omit the scheme, CALLSTACK prepends https:// automatically.
Query Parameters
The Params tab lets you manage URL query parameters as key-value rows — without manually constructing ?key=value&key2=value2 strings.
- Each row has a toggle checkbox — unchecked rows are excluded from the request entirely.
- Keys and values support
{{variable}}substitution. - Params you add are merged with any query string already in the URL bar.
- URL-encoding is handled automatically.
Key: page Value: {{PAGE_NUM}}
Key: per_page Value: 25
Key: sort Value: created_at
→ https://api.example.com/users?page=1&per_page=25&sort=created_at
Headers
The Headers tab lets you add arbitrary HTTP request headers. Each row has an enable/disable toggle. Start typing in the key field and CALLSTACK suggests common header names (Authorization, Content-Type, Accept, Cache-Control, Cookie, Origin, Referer, X-Api-Key, X-Request-ID…). Once a recognised header is selected, a Presets button appears next to the value with sensible defaults for that header — Bearer/Basic/Digest/AWS4 for Authorization, JSON/XML/Form/Multipart for Content-Type, gzip/identity for Accept-Encoding, and so on.
Common patterns:
Authorization: Bearer {{ACCESS_TOKEN}}
Authorization: Basic {{BASIC_AUTH_B64}}
X-API-Key: {{API_KEY}}
Host, Origin, Referer, Cookie, and arbitrary custom headers. This makes it ideal for testing APIs with strict header requirements.
Request Body
The Body tab has several modes depending on what your API expects:
| Mode | Content-Type set | Use when |
|---|---|---|
| None | — | GET, HEAD, DELETE requests with no body |
| JSON | application/json | Sending JSON payloads (most REST APIs) |
| XML | application/xml | SOAP or XML-based APIs |
| Text | text/plain | Arbitrary plain-text payloads |
| Form | application/x-www-form-urlencoded | HTML form submissions, OAuth token endpoints |
| Multipart | multipart/form-data | File uploads, mixed text+file bodies |
The CodeMirror editor in JSON and XML modes provides syntax highlighting, real-time validation, and error indicators. JSON errors appear as red underlines — hover to see the message. The Format button (or the format button in the toolbar) pretty-prints your JSON with correct indentation.
Body content supports {{variable}} substitution — values are resolved at send time, so your saved body can contain placeholders:
{
"email": "{{TEST_EMAIL}}",
"org_id": {{ORG_ID}},
"request_id": "{{$randomUUID}}"
}
Form Body
When you set Content-Type to application/x-www-form-urlencoded, the Body tab switches from a CodeMirror text editor to a key/value editor — just like Params. Rows have enable/disable toggles, template tokens are preserved through encoding, and CALLSTACK serialises everything to key=value&key=value on send. No manual URL-encoding required. This is the correct format for OAuth 2.0 token endpoints:
grant_type: client_credentials
client_id: {{CLIENT_ID}}
client_secret: {{CLIENT_SECRET}}
scope: read:users write:users
File Attachments (Multipart)
Switch to Multipart mode to send files alongside form fields. Click Add file to open the native file picker. Each attachment row shows its field name, filename, size, and MIME type. Files are copied into CALLSTACK's app data directory and referenced by path — so the original file can be moved or deleted without affecting the saved request.
You can mix text fields and file fields in the same multipart body:
- Text field: key-value pair sent as a form field
- File field: the file contents sent with
Content-Disposition: form-data; name="..."; filename="..."
Sending & Cancelling
Click Send or use the keyboard shortcut (⌘X on Mac, CtrlX on Windows/Linux) to execute the request. A spinner appears in the Send button while the request is in flight. To abort a running request, click Cancel. CALLSTACK enforces a 30-second default timeout — if no response is received within 30 seconds, the request is automatically cancelled with a timeout error.
Redirects
Use the Follow redirects checkbox in the URL bar to control whether CALLSTACK follows HTTP redirects (up to 10 hops) or returns the first redirect response verbatim. The setting is per request — it's saved with the request and honoured everywhere it runs: interactive Send, Pre-request Chain steps, and Automation steps.
Cookies
CALLSTACK has a built-in cookie jar. The Use cookie jar checkbox in the URL bar controls whether the request participates in the jar — also per request and persisted with it. When enabled, cookies set by Set-Cookie response headers are stored and automatically sent on subsequent requests to the same domain. You can view, edit, and delete stored cookies in Settings → Cookies.
Setup tab (Pre-request Chain)
The Setup tab lets each request declare an ordered list of sibling requests that fire silently before the main request whenever you click Send from the Request Builder. This is ideal for auth-style flows: configure a login request that captures a token via a post-script (env.set('token', …)), reference {{token}} in the main request's Authorization header, then put login as a pre-step on the main request.
- Pre-step responses do not replace the main response viewer — only one-line summaries (
[pre-chain] GET /login → 200 (84ms)) are appended to the Scripting console. - Each pre-step entry also appears in the footer Logs panel with a copyable curl command, alongside the main request.
- Each step has its own continue on error checkbox — toggle it on for best-effort warm-ups, leave it off when the next step depends on the previous step succeeding.
- Environment mutations from a pre-step's pre/post scripts propagate to subsequent steps and to the main request, so captured tokens are visible immediately.
- Nested pre-chains are executed recursively with cycle detection — a request listing itself, or two requests pointing at each other, are silently skipped.
- The pre-chain is ignored when the request runs as a step inside an Automation; automations remain self-contained.
Renaming requests
Double-click a request in the sidebar to rename it inline. CALLSTACK rejects duplicate names within the same scope (the project root or a single folder) — the input turns red as you type and reverts on blur or Enter if a sibling already has that name.
4 Response Viewer
Metadata Bar
The coloured bar at the top of the response panel shows three key metrics at a glance:
- Status code — Colour-coded: green (2xx success), amber (3xx redirect / 4xx client error), red (5xx server error). The status text (e.g. "OK", "Not Found") is shown alongside.
- Response time — Total round-trip time in milliseconds, measured from the moment the request is sent to the moment the full response body is received.
- Body size — Size of the response body in bytes (or KB/MB for large responses).
Body Tab
The Body tab displays the response body with automatic pretty-printing based on the Content-Type header:
- JSON — Syntax-highlighted with collapsible objects and arrays, line numbers, and fold indicators. Large JSON responses are virtualised for performance.
- XML / HTML — Indented and syntax-highlighted.
- Plain text — Monospace font, shown verbatim.
- Binary — Hex representation with byte count.
Copy copies the formatted (pretty-printed) body to your clipboard — not the raw wire bytes. This is useful for pasting readable JSON into a chat or document. Save to file opens the native file dialog and writes the raw response bytes to disk.
Headers Tab
Lists all response headers as key-value pairs. Common headers to look for:
Content-Type— What format the body is inAuthorization/WWW-Authenticate— Auth challengesX-RateLimit-*— Rate limit informationLocation— Redirect destinationSet-Cookie— Cookies being set
Preview Tab
The Preview tab renders the response visually rather than as raw text:
- HTML — Rendered in a sandboxed iframe. Useful for testing server-rendered pages or email templates.
- Images — Displays PNG, JPEG, GIF, SVG, WebP inline. The image is shown at natural size with a scroll container for large images.
- Video/Audio — Plays back base64-encoded media returned directly in the response body.
- PDF — Renders the PDF inline if supported by your OS WebView.
Test Results Tab
If your post-response script contains test() assertions, results appear here after every request. Each test row shows:
- The test description string (first argument to
test()) - Status: PASS (green) or FAIL (red)
- For failures: the error message thrown inside the test function
The tab label shows a summary count (e.g. "Tests 3/4") so you can see pass rate at a glance without opening the tab.
Large Responses
Responses over ~1 MB are truncated in the viewer to keep the UI fast. A warning banner shows the actual full size. Use Save to file to write the complete response to disk, then open it in a dedicated JSON viewer or text editor.
5 Response History
CALLSTACK can save a rolling history of responses for each request. This lets you compare the current response against previous runs without any external tooling — useful for tracking API behaviour changes over time, debugging regressions, or just reviewing what a request returned an hour ago.
Saving a Response
After a successful request, click Save Response in the response toolbar (or press ⌘S / CtrlS). A dialog asks for an optional name (e.g. "Before migration", "User with admin role"). If you leave the name blank, CALLSTACK uses a timestamp as the name.
Saved responses are stored in the SQLite database against the specific request. They persist across app restarts and are included when you export the project.
Viewing History
Click the History button in the response toolbar to open the history panel. Each saved response is shown as a row with:
- Name (or timestamp if unnamed)
- Status code
- Response time
- Body size
- Date and time saved
Click any row to load that saved response into the viewer. The current live response is not affected — switching history items is read-only.
Diff View
Select two history entries (or one history entry and the current response) and click Diff to see a side-by-side or inline diff of the response bodies. Additions are highlighted green, removals red. This makes it easy to spot exactly what changed between two API responses — for example, after a server deployment or schema migration.
History Limit
CALLSTACK keeps the most recent N responses per request. The default limit is 10. You can change this in Settings → Response History Limit. When the limit is reached, the oldest saved response is deleted automatically to make room for the new one. Set the limit to 0 to disable automatic saving entirely.
Deleting History
To delete individual saved responses, open the History panel, hover over an entry, and click the delete (×) icon. To wipe all history for a request, click Clear History in the panel header. This cannot be undone.
6 Environments & Variables
Creating an Environment
- Open a project and click the Environments tab at the top of the main panel.
- Click + Environment and give it a name (e.g. "Development", "Staging", "Production").
- Add key-value pairs. Use descriptive key names — they appear in autocomplete when you type
{{in any field. - Mark sensitive values (passwords, API keys, tokens) as Secret to mask them in the UI.
- Select the environment from the dropdown in the request builder to make it active for that request.
Multi-Environment Strategy
A common pattern is to have three environments per project with the same variable keys but different values:
| Variable | Development | Staging | Production |
|---|---|---|---|
BASE_URL | http://localhost:3000 | https://api-staging.example.com | https://api.example.com |
ACCESS_TOKEN | dev-token-123 | stg-token-456 | (Secret) |
DB_SEED_USER_ID | 1 | 42 | — |
Switch environments in the dropdown without changing the request itself. This makes it safe to run the same request against any environment without risk of editing the wrong URL.
Variable Substitution
Use {{variableName}} anywhere in your request — URL, query params, headers, or body. Variable names are case-sensitive. Substitution happens at send time; what you see saved in the editor is the template with placeholders.
{{BASE_URL}}/api/v2/users/{{USER_ID}}/profile
Authorization: Bearer {{ACCESS_TOKEN}}
{
"email": "{{TEST_EMAIL}}",
"org_id": {{ORG_ID}},
"role": "{{DEFAULT_ROLE}}"
}
Secrets
Variables marked as Secret are shown as ●●●●●● in the environment editor. Hold the eye icon to temporarily reveal the value. Secrets substitute into requests exactly like regular variables — the masking is only in the UI, not in the actual request.
Runtime Variable Changes
Pre/post scripts can read and write environment variables at runtime using env.get() and env.set(). Runtime changes persist for the duration of the current request execution but are not written back to the saved environment definition. This is intentional — scripts can safely modify values mid-flight without permanently altering your environment.
In automations, runtime changes propagate forward through subsequent steps within the same run.
Recursive Variables
Environment variables can reference other variables in the same environment. CALLSTACK resolves them recursively at send time:
BASE_URL = https://api.example.com
API_V2 = {{BASE_URL}}/v2
USERS_URL = {{API_V2}}/users → https://api.example.com/v2/users
USER_DETAIL = {{USERS_URL}}/{{USER_ID}} → https://api.example.com/v2/users/42
Circular references (A → B → A) are detected and left unresolved — the raw {{variableName}} string is sent as-is. Resolved values are cached per send, so each unique key resolves only once regardless of how many times it is referenced.
Dynamic Tokens
Use built-in $-prefixed tokens anywhere you'd use {{…}} to inject randomly-generated or time-based values without writing scripts. They are regenerated fresh on every request send:
| Token | Output | Example value |
|---|---|---|
{{$randomUUID}} | UUID v4 | 550e8400-e29b-41d4-a716-446655440000 |
{{$guid}} | Alias for $randomUUID | 6ba7b810-9dad-11d1-80b4-00c04fd430c8 |
{{$timestamp}} | Unix timestamp (seconds) | 1713177600 |
{{$isoTimestamp}} | ISO 8601 UTC timestamp | 2024-04-15T12:00:00.000Z |
{{$randomInt}} | Random integer 0–1000 | 543 |
{{$randomFloat}} | Random float 0.0–1.0 | 0.7823 |
{{$randomBoolean}} | true or false | true |
{{$randomEmail}} | Fake email address | alice.walker@example.com |
{{$randomFullName}} | Fake full name | Alice Walker |
{{$randomFirstName}} | Fake first name | Alice |
{{$randomLastName}} | Fake last name | Walker |
{{$randomPhoneNumber}} | Fake phone number | +1-555-867-5309 |
{{$randomIP}} | Random IPv4 address | 203.0.113.42 |
{{$randomIPv6}} | Random IPv6 address | 2001:db8::1 |
{{$randomUrl}} | Fake HTTPS URL | https://example.com/path |
{{$randomDomainName}} | Fake domain | example-corp.io |
{{$randomStreetAddress}} | Fake street address | 123 Main St |
{{$randomCity}} | Fake city name | Springfield |
{{$randomCountry}} | Country name | Germany |
{{$randomCountryCode}} | ISO 3166-1 alpha-2 code | DE |
{{$randomHexColor}} | Hex color string | #a3f0c2 |
{{$randomAlphaNumeric}} | 8-char alphanumeric string | k3mP9xQr |
{{$randomLoremWord}} | Single lorem ipsum word | lorem |
{{$randomLoremSentence}} | Lorem ipsum sentence | Lorem ipsum dolor sit amet. |
{{$randomLoremParagraph}} | Lorem ipsum paragraph | (multi-sentence) |
Autocomplete triggers when you type {{$ in any input field — press Tab or click to insert.
7 Pre/Post Scripts
Scripts are JavaScript that run in a sandboxed QuickJS environment before and after each request. There is no filesystem access, no network calls (fetch is not available), and no access to Node.js or browser APIs. The sandbox is intentionally minimal — it gives you just enough to manipulate request data, run assertions, and pass values between steps.
Two hooks are available per request:
- Pre-request script — Runs before the request is sent. Use it to generate or inject data, compute headers, or set environment variables that the request body/headers reference.
- Post-response script — Runs after the response is received. Use it to extract tokens, run assertions, or pass values to downstream automation steps.
Scripting API Reference documents every global, namespace, and method exposed to your scripts — request, response, env, secrets, console, test, emit, plus the supported built-ins (JSON, Math, Date, Promise, Array, Object, Number).
Scripting Examples catalogues every snippet shipped with the app: tests, env capture, secrets, auth (Basic/Bearer/Digest/AWS4), request mutation, chaining, emitter signals, Base64 encode/decode, JWT decode, response-header lookup & capture, URL param parsing.
Available APIs
request.method — HTTP method string. request.url — fully resolved URL. request.headers — array of {key, value} objects. request.params — array of {key, value} query params. request.body — raw body string. request.json() — parsed body as object (throws if not valid JSON).response.status — HTTP status code (number). response.statusText — status text ("OK", "Not Found"). response.headers — array of {key, value} objects. response.body — raw body string. response.time — response time in ms. response.json() — body parsed as JSON (only in post-script).env.get('KEY') — read a variable from the active environment. Returns undefined if not found. env.set('KEY', value) — write a variable for this execution (not persisted). Works in both pre and post scripts.secrets.get('KEY') — read a secret variable. secrets.set('KEY', value) — write a secret variable for this execution. Identical to env but for secret-marked variables.emitter.emit('key', value) — publish a value for downstream automation steps. The value is then available as {{emitter.key}} in subsequent step URLs, headers, and body. emitter.get('key') — read a previously emitted value in the same run.faker.name(), faker.email(), faker.phone(), faker.address(), faker.uuid(), faker.number(min, max), faker.word(), faker.sentence(), faker.paragraph() — generate realistic test data. New values on every call.test('name', () => { if (condition) throw new Error('message'); }) — results appear in the Test Results tab. Multiple test() calls are allowed; each is independent.console.log(...args) — output appears in the Request Log footer next to the request entry. Useful for debugging script logic. console.error() and console.warn() also work.Examples
Extract a JWT from a login response and save it for subsequent requests:
const body = response.json();
if (body && body.access_token) {
env.set('ACCESS_TOKEN', body.access_token);
env.set('REFRESH_TOKEN', body.refresh_token);
console.log('Tokens saved. Expires in:', body.expires_in, 'seconds');
} else {
console.error('Login failed:', response.status, body?.message);
}
Assert multiple conditions on a response:
test('Status is 200', () => {
if (response.status !== 200)
throw new Error(`Expected 200, got ${response.status}`);
});
test('Response time under 500ms', () => {
if (response.time > 500)
throw new Error(`Too slow: ${response.time}ms`);
});
test('Response has users array', () => {
const body = response.json();
if (!Array.isArray(body.users))
throw new Error('Expected body.users to be an array');
});
test('At least one user returned', () => {
const body = response.json();
if (body.users.length === 0)
throw new Error('Empty users array');
});
Generate fake user data and inject it into the request body via env variables (pre-request):
// Generate a unique test user every time
env.set('RANDOM_EMAIL', faker.email());
env.set('RANDOM_NAME', faker.name());
env.set('RANDOM_PHONE', faker.phone());
// Then reference {{RANDOM_EMAIL}}, {{RANDOM_NAME}} in the body template
Refresh an expired access token automatically before sending:
// Check if token looks expired by inspecting a custom header you set
// (This is illustrative — scripts can't make HTTP calls directly)
const token = env.get('ACCESS_TOKEN');
if (!token) {
// Signal that the token is missing so the request will fail with
// a meaningful error rather than a cryptic 401
throw new Error('ACCESS_TOKEN is not set. Run the Login request first.');
}
Pass a created resource ID to the next automation step:
const body = response.json();
emitter.emit('newUserId', body.id);
emitter.emit('newUserEmail', body.email);
console.log('Created user', body.id, '—', body.email);
{{BASE_URL}}/api/users/{{emitter.newUserId}}
Compute an HMAC signature in the pre-request script:
// Build a timestamp-based nonce and store it for the header
const ts = Math.floor(Date.now() / 1000).toString();
env.set('REQUEST_TS', ts);
// Note: crypto APIs are not available in the sandbox.
// Use {{$timestamp}} in the header directly for timestamp-only auth.
Debugging Scripts
- Use
console.log()liberally — output appears in the Request Log footer next to the request entry. - The script editor highlights syntax errors inline. Hover a red underline to see the error message.
- If a script throws an uncaught error (outside a
test()block), the error is shown in the log footer and the request is still sent — scripts don't block execution unless explicitly throwing in a pre-request hook. - In post-scripts, only call
response.json()— it is undefined in pre-request scripts.
fetch, XMLHttpRequest, and similar APIs are not available in the sandbox. All data must come from the response or environment variables. This is by design to keep scripts predictable and fast.
8 Automations
Automations let you build multi-step request workflows visually — think of them as a flowchart of API calls with loops, branching, parallel lanes, and CSV-driven data iteration. They are useful for end-to-end scenarios, data seeding, smoke tests, and load testing.
Step Types
{{loop.index}} (0-based).{{#columnName}} in nested step templates.Building a Workflow
- Open a project, click the Automations tab, then click + Automation and give it a name.
- Add the first step by clicking + in the visual builder. Choose a step type and configure it. For Request steps, select any saved request from the project dropdown.
- Add more steps below. Each step has its own configuration panel on the right when selected.
- For Request steps, you can pin a specific environment — it overrides the automation's global environment for that step only. This lets you create, for example, in dev and verify in staging.
- Select a global environment in the automation settings for all steps that don't have a pinned environment.
- Click Run to execute. A progress indicator advances through each step. You can watch results in real-time.
Branch Conditions
A Branch step evaluates a condition and takes the "if" or "else" path. Available conditions:
- Last request passed all tests / failed at least one test
- Last status code is exactly N
- Last status code is 2xx / is not 2xx
- Emitted variable
emitter.keyequals a value, is truthy, or exists
Example: after a "Create Order" request, branch on whether emitter.orderId exists. If yes, proceed to "Confirm Order"; if no, log an error and Stop.
CSV Iterator Example
A complete example of seeding multiple users from a data file:
- Create a data file named "test-users" with columns:
email,name,role. - Add rows for each test user.
- In the automation, add a CSV Iterator step and select "test-users".
- Inside the CSV Iterator, add a Request step that calls your "Create User" endpoint. In the request's body template, use
{{#email}},{{#name}},{{#role}}— data file columns use the{{#columnName}}prefix to distinguish them from environment variables. - Run the automation — CALLSTACK executes the nested request once per row.
Passing Data Between Steps
emitter.emit('userId', response.json().id);
emitter.emit('userEmail', response.json().email);
{{BASE_URL}}/api/users/{{emitter.userId}}/activate
{
"to": "{{emitter.userEmail}}",
"template": "welcome",
"data": { "userId": "{{emitter.userId}}" }
}
Fanout (Parallel Lanes)
A Fanout step executes multiple independent lanes concurrently. Each lane is a sequence of steps that runs in parallel. All lanes must complete before execution continues past the Fanout. Use fanout for tasks that don't depend on each other — for example, seeding a user, product, and order simultaneously before running integration tests.
Run History
Every automation run is saved with its start time, duration, overall status (PASS / FAIL / PARTIAL / ERROR), and a per-step breakdown of results, emitted values, and error messages. Access run history from the History tab in the automation editor. Click any run to see step-level detail. Old runs are retained indefinitely — use Clear Runs to remove them.
Aborting a Running Automation
Click Stop in the run progress bar to immediately halt execution. Any steps already running (including in-flight HTTP requests) are cancelled. The run is saved with status ERROR and notes which step was active when stopped.
9 Data Files
Data Files are CSV tables stored per-project. They power the CSV Iterator automation step, letting you run the same request against many rows of data without duplicating requests manually.
Creating a Data File
- Open a project and click the Data tab.
- Click + Data File and give it a name (e.g. "test-users", "product-catalog").
- Add columns by clicking + Column. Column names become variable names in automation steps — use
camelCaseorsnake_casenames without spaces. - Add rows by clicking + Row. Fill in values in the grid.
- To import an existing CSV file, click Import CSV and select the file. The first row is treated as the header and becomes column names.
CSV Format Rules
- The first row must be column headers.
- Column names should not contain spaces — use
userIdnotuser id. Spaces are replaced with underscores on import. - Values can contain commas if the field is quoted:
"Smith, John". - Empty cells are treated as empty strings in variable substitution.
Using in Automations
Add a CSV Iterator step to your automation and select the data file. Each row's column values are injected as template variables using the {{#columnName}} syntax — the # prefix distinguishes data file columns from environment variables:
userId,email,role
1,alice@example.com,admin
2,bob@example.com,viewer
3,carol@example.com,editor
{{BASE_URL}}/api/users/{{#userId}}
{
"email": "{{#email}}",
"role": "{{#role}}"
}
The nested steps execute once per row: first with {{#userId}}=1, then {{#userId}}=2, then {{#userId}}=3. Environment variables like {{BASE_URL}} and data file columns like {{#userId}} can be mixed freely in the same template.
CSV Editor Features
- Inline editing — Click any cell to edit it directly.
- Add/remove rows — Buttons in the toolbar or keyboard shortcuts.
- Rename columns — Double-click a column header to rename it.
- Reorder columns — Drag column headers to rearrange.
- Delete column — Right-click a header and choose Delete Column.
- Filter rows — Type in the filter box to show only rows matching a value.
- Sort — Click a column header to sort ascending/descending.
- Export CSV — Click Export to download the table as a standard CSV file.
- Import CSV — Click Import to replace or merge with a CSV file from disk.
10 Organisation & Navigation
Sidebar Layout
The left sidebar lists all your projects. Each project is an expandable section — click the ▼ chevron to expand and see its folders and requests. Click again to collapse. Expansion state persists across restarts.
At the top of the sidebar there are icon buttons for: theme toggle, accent toggle, new request, import, and settings. At the bottom: help/docs.
Folders
Folders are purely organisational — they group requests but don't change how requests are sent. You can nest folders to any depth. Typical organisation strategies:
- By resource:
/Users,/Products,/Orders - By feature area:
Auth,Search,Checkout - By test type:
Happy Path,Edge Cases,Error Scenarios
To create a folder, right-click a project or existing folder and choose New Folder, or use the + button that appears on hover.
Drag-and-Drop Reordering
Grab any request, folder, or project by its drag handle (visible on hover, left side of the row) and drop it to a new position. You can:
- Reorder requests within the same folder
- Move a request into a different folder
- Move a request to the project root
- Reorder folders within a project
- Reorder projects
Reordering is persisted immediately to the database.
Collapsing & Resizing the Sidebar
Click the collapse arrow at the bottom of the sidebar to hide it entirely, giving the request/response panels maximum space. Click again to restore. You can also drag the border between the sidebar and the main content area to resize it to your preferred width.
Renaming
- Double-click any request or folder name in the sidebar to rename it inline.
- With a request focused, press ⌘R (Mac) or CtrlR (Windows/Linux) to start renaming.
- Press Enter to confirm or Escape to cancel.
Cloning Requests
- Right-click a request and choose Duplicate.
- Or press ⌘D / CtrlD with the request focused.
The clone is placed directly below the original and named with a " (copy)" suffix. Everything is duplicated: method, URL, params, headers, body, and scripts.
Deleting
Hover over a request, folder, or project in the sidebar to reveal the × delete button. Click it and confirm to delete. Deleting a folder deletes all requests inside it. Deleting a project deletes all its contents. Deletions are permanent — there is no undo.
11 Import & Export
Import from Postman
CALLSTACK imports Postman Collection v2.1 JSON files.
- In Postman, open your collection, click the three-dot menu, and choose Export. Select Collection v2.1 (recommended).
- In CALLSTACK, click the Import button (arrow icon) in the sidebar header.
- Select your
.jsonfile from the native file picker. - A preview dialog shows the requests and folders to be imported. Check or uncheck items.
- Click Import Selected. A new project is created with the collection name.
What is preserved from Postman:
- Folder structure and request order
- HTTP method, URL, query parameters
- Request headers and body (JSON, raw text, form data)
- Request names and descriptions
What is not imported: Postman pre-request and test scripts (they use a different API), Postman environments (import them separately if needed), and collection-level auth (set headers manually).
Import Callstack Format
To import a .callstack.json file exported from CALLSTACK (from a colleague or a previous backup):
- Click the Import button in the sidebar header.
- Select the
.callstack.jsonfile. - A preview shows all projects, folders, requests, and environments in the file.
- Click Import All or select specific items.
Callstack-format imports fully restore scripts, environments, automations, and data files — everything the export contained.
Export to Callstack Format
The native Callstack format (.callstack.json) is the most complete export — it preserves everything. Use it for backups and sharing with teammates who also use CALLSTACK.
- Hover over a project in the sidebar, click the three-dot menu, and choose Export.
- Choose what to include: response history, environment variable values, file attachment contents (inline as base64).
- Click Export. The native file save dialog opens.
- Save the
.callstack.jsonfile to your desired location.
Export to Postman Format
Export your project as a Postman v2.1 collection for sharing with teammates who use Postman. Use Export → Postman from the project menu. Note that CALLSTACK scripts and dynamic tokens are not converted — only the request structure (URL, headers, body) is exported in Postman format.
Curl Export
Any request in the Request Log can be exported as a curl command — useful for reproducing issues in CI/CD, sharing with a backend developer, or testing from a remote server.
curl -X POST https://api.example.com/v1/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer eyJhbGci..." \
-d '{"email":"user@example.com","role":"admin"}'
Backup Strategy
For a simple backup, export each project to .callstack.json format periodically. Alternatively, copy the entire database file:
- macOS:
~/Library/Application Support/callstack/callstack.db - Linux:
~/.local/share/callstack/callstack.db - Windows:
%APPDATA%\callstack\callstack.db
A file copy works while the app is closed. While the app is running, use the export function instead to get a consistent snapshot.
12 Request Log
The Request Log is a collapsible footer panel that records every request sent in the current session. It is session-scoped — the log resets when you quit and reopen CALLSTACK. It's useful for debugging, auditing, reviewing script console output, and generating curl commands.
Log Columns
| Column | Description |
|---|---|
| Timestamp | Date and time the request was sent (YYYY-MM-DD HH:MM:SS) |
| Method | HTTP method with colour-coded badge |
| URL | Full resolved URL (all variables substituted, not the template) |
| Status | HTTP status code and text |
| Time | Response time in ms |
| Size | Response body size in bytes |
Expanding a Log Entry
Click any log row to expand it and see:
- Request headers — All headers sent, including auto-added
Content-Type - Request body — The fully-resolved body sent on the wire
- Console output — Any
console.log()messages from your scripts - Script errors — Uncaught errors from pre/post scripts
- Copy curl — Button to copy the equivalent curl command
Curl Export
Expand any log entry and click Copy curl. The curl command includes all resolved headers and body. This is the actual request that went over the wire — all variables are substituted, so the curl command is self-contained and can be shared directly.
Clear Log
Click Clear in the log panel header to wipe all entries from the current session. This does not affect saved responses in the database or response history.
13 Keyboard Shortcuts
Default Shortcuts
| Mac | Windows / Linux | Action |
|---|---|---|
| ⌘X | CtrlX | Send / execute the current request |
| ⌘N | CtrlN | New request in the current project |
| ⌘R | CtrlR | Rename the selected request |
| ⌘D | CtrlD | Duplicate (clone) the selected request |
| ⌘S | CtrlS | Save the current response to history |
| ⌘C | CtrlC | Copy the response body (formatted) — works globally, even inside editors |
| ⌘= | Ctrl= | Zoom in (increase UI scale) |
| ⌘- | Ctrl- | Zoom out (decrease UI scale) |
| F1–F12 | F1–F12 | Quick-launch an assigned request |
F-Key Quick Launch
Assign any request to an F1–F12 key for one-key instant access — no clicking through the sidebar required:
- Right-click any request in the sidebar.
- Choose Assign shortcut from the context menu.
- Press the F-key you want to assign (F1–F12).
- From anywhere in the app, press that F-key to jump to that request and immediately send it.
F-key assignments are shown next to the request name in the sidebar as a small badge. Reassign at any time by repeating the process. Remove an assignment by choosing Remove shortcut from the context menu.
Customising Shortcuts
- Open Settings (gear icon in the sidebar).
- Click the Shortcuts tab.
- Click the key combination displayed next to the action you want to change.
- Press your desired new key combination. CALLSTACK records it immediately.
- If there's a conflict with another action, a warning is shown.
- Click Reset to defaults to restore all shortcuts to their original values.
Custom shortcuts are saved in localStorage and persist across sessions. They are not included in project exports — shortcuts are per-installation, not per-project.
14 Theming
Appearance Modes
CALLSTACK supports four appearance modes, toggled via the moon/sun icon in the sidebar icon bar:
| Mode | Description |
|---|---|
| Dark | Deep navy background with teal accents. Default. Easy on the eyes for long coding sessions. |
| Light | Clean white background with darker accents. Great for bright environments or printed screenshots. |
| Dim | Softer dark theme with reduced contrast — a middle ground between dark and light. |
| System | Automatically follows your OS dark/light preference and updates in real-time when you change it. |
Accent Colour Modes
In addition to the appearance mode, CALLSTACK has three accent colour schemes. Cycle through them using the accent toggle button (palette icon) in the sidebar icon bar:
| Mode | Description |
|---|---|
| Color | Default. HTTP methods each have a distinct colour: GET teal, POST blue, PUT amber, DELETE red, PATCH purple. The sidebar and active elements use these colours. |
| Bright | More vivid, high-saturation version of Color mode. Higher contrast between elements. Good for large monitors or accessibility. |
| Mono | Monochrome — removes all method-based colour coding. All accents use a single neutral tone. Good for minimal, distraction-free workflows or when projecting on a screen. |
Appearance mode and accent mode are independent — you can mix Dark appearance with Mono accents, or Light appearance with Bright accents.
Both preferences are saved in localStorage and persist across app restarts.
15 Settings
Open Settings via the gear icon in the sidebar icon bar. Settings are organised into tabs.
General
| Setting | Description | Default |
|---|---|---|
| Zoom Level | Scale the entire UI from 50% to 200%. Use ⌘= / ⌘- as quick shortcuts. Useful on high-DPI displays or small laptop screens. | 100% |
| Theme | Dark / Light / Dim / System appearance mode (same as the sidebar toggle). | Dark |
| Accent | Color / Bright / Mono accent scheme (same as the sidebar accent toggle). | Color |
| Response History Limit | How many saved responses to keep per request. When the limit is reached, the oldest is deleted. Set to 0 to disable automatic deletion (responses accumulate until manually cleared). | 10 |
Shortcuts
The Shortcuts tab shows all configurable key bindings. Click any binding to change it. The current binding is shown as a clickable chip — press your new key combination to replace it. See Keyboard Shortcuts for the full list and instructions.
Account
Sign in or out of Google to scope your data to an email address. When signed in, your email is shown and all projects/requests created are associated with your email. Signing out returns to "anonymous" mode where all data is shared on the local machine.
About
Shows the current CALLSTACK version number, build date, and links to the GitHub repository and issue tracker. Include this version information when reporting bugs.
Danger Zone
| Action | What it does |
|---|---|
| Reset All Settings | Restores all settings to defaults: zoom 100%, Dark theme, Color accent, default shortcuts, response history limit 10. Does not touch project data. |
| Clear All Data | Destructive and irreversible. Deletes all projects, folders, requests, responses, environments, automations, and data files from the database. The app restarts to a clean slate. |
16 Advanced Features
JWT Decoder
Inspect any JSON Web Token without leaving CALLSTACK. Click the JWT button in the toolbar or paste a token into the JWT decoder panel. The decoded header and payload are displayed as formatted JSON. The decoder also shows:
- The algorithm (
alg) and key ID (kid) from the header - Standard claims:
iss(issuer),sub(subject),aud(audience),exp(expiry),iat(issued at) - A human-readable expiry time and whether the token is currently expired
The decoder does not verify the token signature — it only decodes the payload. To verify signatures, use your server's auth library.
Template Variable Inspector
Click the { } icon near the URL bar to open the Variable Inspector. It lists every variable in scope at the moment, including:
- All key-value pairs from the active environment (secrets shown as ●●●●)
- Emitter values from the current automation run (if applicable)
- Built-in dynamic tokens (shown with their next-resolved value if previewable)
Use this to debug substitution failures — if a variable is missing here, it won't be substituted in your request.
Faker API in Scripts
The faker object is available in all pre and post scripts. Full method list:
faker.name() // "Alice Johnson"
faker.firstName() // "Alice"
faker.lastName() // "Johnson"
faker.email() // "alice.johnson@example.com"
faker.phone() // "+1-555-123-4567"
faker.address() // "123 Main St, Springfield"
faker.city() // "Springfield"
faker.country() // "Germany"
faker.countryCode() // "DE"
faker.uuid() // "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
faker.number(1, 100) // 42 (inclusive range)
faker.float(0, 1) // 0.7823
faker.boolean() // true
faker.word() // "lorem"
faker.sentence() // "Lorem ipsum dolor sit amet."
faker.paragraph() // "Lorem ipsum dolor sit amet..."
faker.hexColor() // "#a3f0c2"
faker.url() // "https://example.com/path"
faker.domain() // "example-corp.io"
faker.ip() // "203.0.113.42"
faker.alphanumeric(8) // "k3mP9xQr" (length parameter)
Cookies
CALLSTACK maintains a persistent cookie jar. Cookies are stored per-domain and sent automatically on subsequent requests. To manage cookies:
- View cookies: Settings → Cookies. Shows all stored cookies with domain, name, value, and expiry.
- Delete a cookie: Click × next to any cookie row.
- Clear all cookies: Settings → Cookies → Clear All.
Database Inspector
Settings → About → Database Stats shows the size of each table in your database and the total database file size. Use Compact Database to run VACUUM and reclaim space after deleting large amounts of data (e.g. after clearing response history).
Multi-User Support
If multiple developers share one machine, each can sign in with their Google account. All data — projects, requests, responses — is scoped to the signed-in user's email address. Sign out (Settings → Account → Sign out) to switch users. All data lives in the same local SQLite file but is namespaced by email, so each user sees only their own projects.
Database Location
| Platform | Path |
|---|---|
| macOS | ~/Library/Application Support/callstack/callstack.db |
| Linux | ~/.local/share/callstack/callstack.db |
| Windows | %APPDATA%\callstack\callstack.db |
You can back up this file at any time with a plain file copy. While the app is running, use Settings → Export instead to get a consistent snapshot. The database uses WAL mode — both the .db and .db-wal files should be copied together for a complete backup.
17 Troubleshooting
App won't open on macOS (Gatekeeper)
If you see "CALLSTACK cannot be opened because it is from an unidentified developer":
- Right-click (or Control-click) the CALLSTACK icon in Finder.
- Choose Open from the context menu.
- Click Open in the dialog that appears.
You only need to do this once. Subsequent launches will open normally. Alternatively, go to System Preferences → Security & Privacy → General and click "Open Anyway" after the first failed launch attempt.
Request times out
CALLSTACK enforces a 30-second timeout. If your server is slow or unreachable:
- Verify the URL is reachable from your machine:
curl -v https://your-api.com/endpointin a terminal. - Check OS-level firewall rules or VPN settings that may block outbound connections on specific ports.
- If you're hitting a localhost server, confirm it's running and listening on the expected port.
- For long-running operations (file uploads, heavy computation), consider increasing the payload or using async APIs that return immediately with a job ID.
SSL/TLS errors
If you see a TLS or certificate error (e.g. "certificate verify failed"):
- For self-signed certificates in development: CALLSTACK uses the system certificate store. Add your self-signed CA to your OS trust store — on macOS, open Keychain Access and import the certificate, marking it as "Always Trust".
- For expired certificates: the server's certificate has expired and needs to be renewed. This is a server configuration issue.
- For mismatched hostnames: the certificate's CN or SAN doesn't match the hostname in your URL. Check that you're using the correct URL.
Variables not substituting
- Ensure an environment is selected in the dropdown near the Send button (it can show "No environment" if none is active).
- Variable names are case-sensitive:
{{BASE_URL}}and{{base_url}}are different. - Open the Variable Inspector (the
{ }icon) to see all variables in scope and their current values. - Check for extra spaces inside the braces:
{{ BASE_URL }}does not matchBASE_URL— there should be no spaces. - For recursive variables, check for circular references (A references B which references A).
Scripts not running or errors
- Open the Request Log footer, expand the request entry, and look at the console output and script error sections.
- Ensure JavaScript syntax is valid — the script editor highlights syntax errors in red. Hover the underline for details.
- Do not call
response.json()in a pre-request script — the response object is only available in post-response scripts. - Do not use
fetch,XMLHttpRequest, Node.js APIs (require,fs), or browser APIs (window,document) — they are not available in the sandbox. - If a pre-request script throws an uncaught error, the request is still sent. To block sending on a script error, use
throw new Error('reason')explicitly.
Automation stopped early
- Open the run history and expand the failed run. The step that failed is shown with a red indicator and an error message.
- Check Branch conditions — a condition evaluating unexpectedly may route to a Stop step.
- For CSV Iterator, verify the data file has rows and columns named correctly (no spaces).
- If an individual Request step fails (non-2xx status), it's not automatically a run failure — only uncaught script errors or Stop steps abort the run. Check your Branch conditions for unintended Stop routing.
Postman import fails or is incomplete
- CALLSTACK only supports Postman Collection v2.1. Export from Postman using "Collection v2.1 (recommended)" — not v1.
- If requests are missing, check if they were in a collection folder that wasn't selected in the import preview.
- GraphQL requests from Postman may not import body correctly — paste the body manually after import.
- Very large collections (>1,000 requests) may take several seconds to parse.
App is slow or using excessive memory
- Large response histories accumulate in the database. Go to Settings → About → Database Stats to see the database size. Clear history for large-response requests.
- Run Compact Database (Settings → About) after deleting data to reclaim disk space and improve performance.
- Very large response bodies (>10 MB) in the viewer can cause slowness in the editor — use Save to file and open externally instead.
Data recovery
If the app crashes or the database becomes corrupted:
- Locate the database file at the platform-specific path (see Database Location).
- Open it with DB Browser for SQLite (free, cross-platform) to inspect and export tables manually.
- If you have a backup copy of the
.dbfile, close CALLSTACK and replace the current file with the backup. - If the WAL file (
.db-wal) exists, the database may be in an incomplete state. Delete the.db-waland.db-shmfiles, then reopen CALLSTACK — SQLite will recover what it can.
Reporting Bugs
Open an issue on GitHub Issues and include:
- Steps to reproduce (exact clicks and inputs)
- Expected vs. actual behaviour
- Your platform (macOS 14.4 / Ubuntu 22.04 / Windows 11) and CALLSTACK version (Settings → About)
- Any error messages from the Request Log or console
- A screenshot or screen recording if the issue is visual