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

PlatformMinimum VersionNotes
macOS10.13 (High Sierra)Apple Silicon (M1/M2/M3) and Intel fully supported
Linuxglibc 2.29+AppImage works on most modern distros (Ubuntu 20.04+, Fedora 32+)
WindowsWindows 10 (1803+)x64 only; ARM64 Windows not supported

Installation

macOS — Homebrew (recommended):

Terminal
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.
⚠️
macOS Gatekeeper If macOS says "CALLSTACK cannot be opened because it is from an unidentified developer", right-click the app in Finder and choose Open, then click Open again in the dialog. You only need to do this once.

First Launch

  1. Open CALLSTACK from your Applications folder (macOS), launcher (Linux), or Start menu (Windows).
  2. 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
  3. 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.
  4. 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:

  1. Click + Project and name it (e.g. "My API").
  2. Inside the project, click + Request. A new request appears in the sidebar and opens in the editor.
  3. Set the method to GET and enter a URL: https://httpbin.org/get.
  4. Click Send (or press X on Mac / CtrlX on Windows/Linux).
  5. The response appears in the right panel: status 200, the JSON body, headers, and timing information.
💡
No account needed CALLSTACK works entirely offline. Everything — requests, responses, environments, scripts — is stored locally. Google Sign-In is entirely optional.

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:

  1. Resolve — All {{variable}} and {{$token}} references in the URL, params, headers, and body are resolved against the active environment.
  2. Pre-script — If a pre-request script is attached, it runs in a JavaScript sandbox. It can read and mutate env variables, modify request headers/body, and call faker to generate data.
  3. Send — The Rust HTTP client sends the fully-resolved request. No CORS, no injected browser headers, no proxy needed.
  4. Post-script — If a post-response script is attached, it runs with access to the response object. Use it to extract tokens, run assertions with test(), or emit values for downstream automation steps.
  5. Persist — The response is saved to the database if you have history saving enabled, and logged to the Request Log footer.
  6. 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-Origin headers.
  • Browsers automatically inject headers like Origin, Referer, and User-Agent that you can't remove.
  • Certain headers (Host, Cookie, Authorization with 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:

GET POST PUT DELETE PATCH

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.
Example params → resulting URL
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:

Bearer token auth
Authorization: Bearer {{ACCESS_TOKEN}}
Basic auth (base64 encoded)
Authorization: Basic {{BASIC_AUTH_B64}}
API key header
X-API-Key: {{API_KEY}}
ℹ️
Full header control Because CALLSTACK uses a Rust HTTP client, you can set headers that browsers normally forbid — including 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:

ModeContent-Type setUse when
NoneGET, HEAD, DELETE requests with no body
JSONapplication/jsonSending JSON payloads (most REST APIs)
XMLapplication/xmlSOAP or XML-based APIs
Texttext/plainArbitrary plain-text payloads
Formapplication/x-www-form-urlencodedHTML form submissions, OAuth token endpoints
Multipartmultipart/form-dataFile 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:

JSON body with variables
{
  "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:

OAuth token request body
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 in
  • Authorization / WWW-Authenticate — Auth challenges
  • X-RateLimit-* — Rate limit information
  • Location — Redirect destination
  • Set-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.

💡
Use named saves for milestones Name your saves descriptively ("pre-migration baseline", "v2 API response") so they're easy to identify later. Unnamed timestamped saves are useful for recent history, but names help when comparing across larger time spans.

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

  1. Open a project and click the Environments tab at the top of the main panel.
  2. Click + Environment and give it a name (e.g. "Development", "Staging", "Production").
  3. Add key-value pairs. Use descriptive key names — they appear in autocomplete when you type {{ in any field.
  4. Mark sensitive values (passwords, API keys, tokens) as Secret to mask them in the UI.
  5. 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:

VariableDevelopmentStagingProduction
BASE_URLhttp://localhost:3000https://api-staging.example.comhttps://api.example.com
ACCESS_TOKENdev-token-123stg-token-456(Secret)
DB_SEED_USER_ID142

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.

URL
{{BASE_URL}}/api/v2/users/{{USER_ID}}/profile
Header
Authorization: Bearer {{ACCESS_TOKEN}}
Body
{
  "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.

⚠️
Not encrypted at rest Secret values are stored as plain text in the local SQLite database. The "Secret" flag only masks them in the UI. Do not treat the local database as a secure vault — use OS Keychain or a password manager for production credentials.

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:

Environment variables
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:

TokenOutputExample value
{{$randomUUID}}UUID v4550e8400-e29b-41d4-a716-446655440000
{{$guid}}Alias for $randomUUID6ba7b810-9dad-11d1-80b4-00c04fd430c8
{{$timestamp}}Unix timestamp (seconds)1713177600
{{$isoTimestamp}}ISO 8601 UTC timestamp2024-04-15T12:00:00.000Z
{{$randomInt}}Random integer 0–1000543
{{$randomFloat}}Random float 0.0–1.00.7823
{{$randomBoolean}}true or falsetrue
{{$randomEmail}}Fake email addressalice.walker@example.com
{{$randomFullName}}Fake full nameAlice Walker
{{$randomFirstName}}Fake first nameAlice
{{$randomLastName}}Fake last nameWalker
{{$randomPhoneNumber}}Fake phone number+1-555-867-5309
{{$randomIP}}Random IPv4 address203.0.113.42
{{$randomIPv6}}Random IPv6 address2001:db8::1
{{$randomUrl}}Fake HTTPS URLhttps://example.com/path
{{$randomDomainName}}Fake domainexample-corp.io
{{$randomStreetAddress}}Fake street address123 Main St
{{$randomCity}}Fake city nameSpringfield
{{$randomCountry}}Country nameGermany
{{$randomCountryCode}}ISO 3166-1 alpha-2 codeDE
{{$randomHexColor}}Hex color string#a3f0c2
{{$randomAlphaNumeric}}8-char alphanumeric stringk3mP9xQr
{{$randomLoremWord}}Single lorem ipsum wordlorem
{{$randomLoremSentence}}Lorem ipsum sentenceLorem 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.

💡
Variable Inspector Click the { } icon near the URL bar to open the Variable Inspector. It shows every variable currently in scope (from the active environment plus any emitter values from automation runs) with their resolved values — useful for debugging substitution problems.

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.
📚
Browse the full API + copy-paste examples

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
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
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
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
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
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
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(desc, fn)
Define an assertion. test('name', () => { if (condition) throw new Error('message'); }) — results appear in the Test Results tab. Multiple test() calls are allowed; each is independent.
console
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:

Post-response script — Login request
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:

Post-response script
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):

Pre-request script
// 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:

Pre-request script
// 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:

Post-response script — "Create User" step
const body = response.json();
emitter.emit('newUserId', body.id);
emitter.emit('newUserEmail', body.email);
console.log('Created user', body.id, '—', body.email);
URL in next step — "Get User"
{{BASE_URL}}/api/users/{{emitter.newUserId}}

Compute an HMAC signature in the pre-request script:

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.
ℹ️
No network in scripts Scripts cannot make HTTP requests — 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

📡
RequestExecute a saved request from this project. The request's pre/post scripts run as normal.
⏱️
DelayWait N milliseconds before proceeding. Useful for rate-limit-safe batch operations.
🔁
RepeatLoop a set of nested steps N times. Loop index is available as {{loop.index}} (0-based).
📄
CSV IteratorLoop over every row in a data file. Each column value is injected as {{#columnName}} in nested step templates.
Set EnvSwitch the active environment from this step forward in the run. Useful for cross-environment workflows.
🔀
BranchConditional if/else — evaluates a condition and takes one of two paths.
FanoutExecute multiple lanes in parallel. Each lane is an independent chain of steps. Joins when all lanes complete.
🛑
StopImmediately halt the automation. Can be placed in a Branch "else" path to abort on failure.
📝
LogEmit a static or variable-substituted message to the run history log. Good for marking milestones.

Building a Workflow

  1. Open a project, click the Automations tab, then click + Automation and give it a name.
  2. 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.
  3. Add more steps below. Each step has its own configuration panel on the right when selected.
  4. 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.
  5. Select a global environment in the automation settings for all steps that don't have a pinned environment.
  6. 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.key equals 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:

  1. Create a data file named "test-users" with columns: email, name, role.
  2. Add rows for each test user.
  3. In the automation, add a CSV Iterator step and select "test-users".
  4. 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.
  5. Run the automation — CALLSTACK executes the nested request once per row.

Passing Data Between Steps

Step 1 post-script — Create User
emitter.emit('userId', response.json().id);
emitter.emit('userEmail', response.json().email);
Step 2 URL — Activate User
{{BASE_URL}}/api/users/{{emitter.userId}}/activate
Step 3 body — Send Welcome Email
{
  "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

  1. Open a project and click the Data tab.
  2. Click + Data File and give it a name (e.g. "test-users", "product-catalog").
  3. Add columns by clicking + Column. Column names become variable names in automation steps — use camelCase or snake_case names without spaces.
  4. Add rows by clicking + Row. Fill in values in the grid.
  5. 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 userId not user 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:

Data file with columns: userId, email, role
userId,email,role
1,alice@example.com,admin
2,bob@example.com,viewer
3,carol@example.com,editor
Request URL in nested step
{{BASE_URL}}/api/users/{{#userId}}
Request body in nested step
{
  "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.

  1. In Postman, open your collection, click the three-dot menu, and choose Export. Select Collection v2.1 (recommended).
  2. In CALLSTACK, click the Import button (arrow icon) in the sidebar header.
  3. Select your .json file from the native file picker.
  4. A preview dialog shows the requests and folders to be imported. Check or uncheck items.
  5. 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):

  1. Click the Import button in the sidebar header.
  2. Select the .callstack.json file.
  3. A preview shows all projects, folders, requests, and environments in the file.
  4. 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.

  1. Hover over a project in the sidebar, click the three-dot menu, and choose Export.
  2. Choose what to include: response history, environment variable values, file attachment contents (inline as base64).
  3. Click Export. The native file save dialog opens.
  4. Save the .callstack.json file 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.

Example curl export
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

ColumnDescription
TimestampDate and time the request was sent (YYYY-MM-DD HH:MM:SS)
MethodHTTP method with colour-coded badge
URLFull resolved URL (all variables substituted, not the template)
StatusHTTP status code and text
TimeResponse time in ms
SizeResponse 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.

ℹ️
Session-scoped The Request Log only keeps entries from the current app session. Restarting CALLSTACK clears the log. If you need a permanent record of a request, save the response using S or export the project.

13 Keyboard Shortcuts

Default Shortcuts

MacWindows / LinuxAction
XCtrlXSend / execute the current request
NCtrlNNew request in the current project
RCtrlRRename the selected request
DCtrlDDuplicate (clone) the selected request
SCtrlSSave the current response to history
CCtrlCCopy the response body (formatted) — works globally, even inside editors
=Ctrl=Zoom in (increase UI scale)
-Ctrl-Zoom out (decrease UI scale)
F1F12F1F12Quick-launch an assigned request
ℹ️
Send shortcut is ⌘X, not ⌘↩ X sends the request. This avoids conflicts with the Enter key inside multiline text editors. On Windows and Linux use CtrlX.

F-Key Quick Launch

Assign any request to an F1–F12 key for one-key instant access — no clicking through the sidebar required:

  1. Right-click any request in the sidebar.
  2. Choose Assign shortcut from the context menu.
  3. Press the F-key you want to assign (F1–F12).
  4. 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

  1. Open Settings (gear icon in the sidebar).
  2. Click the Shortcuts tab.
  3. Click the key combination displayed next to the action you want to change.
  4. Press your desired new key combination. CALLSTACK records it immediately.
  5. If there's a conflict with another action, a warning is shown.
  6. 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:

ModeDescription
DarkDeep navy background with teal accents. Default. Easy on the eyes for long coding sessions.
LightClean white background with darker accents. Great for bright environments or printed screenshots.
DimSofter dark theme with reduced contrast — a middle ground between dark and light.
SystemAutomatically 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:

ModeDescription
ColorDefault. 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.
BrightMore vivid, high-saturation version of Color mode. Higher contrast between elements. Good for large monitors or accessibility.
MonoMonochrome — 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

SettingDescriptionDefault
Zoom LevelScale the entire UI from 50% to 200%. Use = / - as quick shortcuts. Useful on high-DPI displays or small laptop screens.100%
ThemeDark / Light / Dim / System appearance mode (same as the sidebar toggle).Dark
AccentColor / Bright / Mono accent scheme (same as the sidebar accent toggle).Color
Response History LimitHow 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

ActionWhat it does
Reset All SettingsRestores all settings to defaults: zoom 100%, Dark theme, Color accent, default shortcuts, response history limit 10. Does not touch project data.
Clear All DataDestructive and irreversible. Deletes all projects, folders, requests, responses, environments, automations, and data files from the database. The app restarts to a clean slate.
⚠️
Export before clearing data Use Export on each project before clicking Clear All Data. This action cannot be undone. There is no trash or recovery mechanism.

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 methods
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

PlatformPath
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":

  1. Right-click (or Control-click) the CALLSTACK icon in Finder.
  2. Choose Open from the context menu.
  3. 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/endpoint in 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 match BASE_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:

  1. Locate the database file at the platform-specific path (see Database Location).
  2. Open it with DB Browser for SQLite (free, cross-platform) to inspect and export tables manually.
  3. If you have a backup copy of the .db file, close CALLSTACK and replace the current file with the backup.
  4. If the WAL file (.db-wal) exists, the database may be in an incomplete state. Delete the .db-wal and .db-shm files, 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