Documentation

Reference for workspaces, collections, selectors, scripts, and secrets.

1. First launch

On first launch, Synapse asks for a workspaces directory: the folder where every workspace (one per API project) is saved. The choice is remembered.

Start optionResult
Try sample projectExports the two sample workspaces below into an empty folder of your choice. Both are ready to send.
Start from scratchCreates an empty workspace folder named after your project (lowercase, hyphenated) containing workspace.json.
Sample workspaceContents
DummyJSONJWT login, Bearer-protected endpoints, GET/POST/PUT, script-line extraction, and a .kts script that decodes the JWT.
ShowcasesOne request per Synapse feature.

2. Workspace structure

The workspaces directory holds one folder per workspace. Workspaces are completely independent: each has its own requests, selectors and scripts, and nothing is shared between them. Each workspace has the same layout:

synapse-workspaces/           ← Synapse home directory (workspaces directory)
├── workspace-one/            ← independent workspace
│   ├── workspace.json        ← workspace name (required)
│   ├── collections/          ← HTTP requests (required)
│   ├── selectors/            ← dropdown definitions (optional)
│   └── scripts/              ← .kts automation scripts (optional)
├── workspace-two/            ← independent workspace
│   ├── workspace.json
│   └── collections/
└── workspace-three/          ← independent workspace
    ├── workspace.json
    ├── collections/
    └── selectors/
Secrets are not stored in the workspace. They are encrypted and saved in Synapse's local config folder, outside any repository. See Secrets.

workspace.json

The only file required at the root. It holds the display name.

{
  "name": "DummyJSON API"
}

Live file watching

Synapse watches the workspace folder. A JSON file saved from any editor is reloaded immediately, without a restart.

3. Collections — request files

Each .json file in collections/ defines one HTTP request. Sub-folders become groups. Filenames prefixed with 01-, 02-, … sort by prefix; the rest sort alphabetically.

Requests and folders can also be created, duplicated, moved, and deleted inside Synapse, so the files never have to be written by hand.

Responses

Status, headers, and body are shown for every response. Binary payloads (PDF, ZIP, images) are not rendered and can be saved to a file. Response bodies can be searched with plain text or a regular expression, case-insensitive by default.

Field reference

FieldTypeRequiredDefaultDescription
idstring✅—Stable unique identifier, lowercase-kebab-case.
namestring✅—Display label.
methodstring✅—GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.
urlstring✅—Full URL. Supports {{...}} templates.
headersobject (string→string)—{}Request headers. Values support templates.
queryParamsobject (string→string)—{}Query parameters (?key=value). Values support templates.
pathParamsobject (string→string)—{}Values for {{var.KEY}} placeholders in the URL.
authobject or null—nullPer-request authentication. See Auth.
settingsobject or null—nullHTTP version, redirects, cookies, timeout. See Settings.
bodyTypestring—"none"none, raw, table, multipart, or graphql. Selects the editor only. See How the body is encoded.
bodystring or null—nullRaw request body. For JSON, escape inner quotes with \". Supports templates.
bodyFilePathstring or null—nullAbsolute path of a file sent as the raw body. See Binary body.
bodyPartsarray—[]Multipart form-data parts. See Multipart form-data body.
graphqlobject or null—nullGraphQL query, variables and operation name. See GraphQL body.
descriptionstring or null—nullInfo text shown next to the request.
preScriptLinesarray of strings—[]Script lines run before the request is sent. See Scripts.
postScriptLinesarray of strings—[]Script lines run after the response arrives. See Scripts.

Templates

Templates work in every string field: url, headers, queryParams, body, graphql, and all auth fields. They resolve at send time.

TemplateResolves toExampleDefined in
{{selector.<id>}}The selected option's value{{selector.version}} → v2selectors/
{{selector.<id>.<KEY>}}A variable of the selected option{{selector.environment.BASE_URL}} → https://dummyjson.comselectors/
{{script.<NAME>}}A variable captured by a script{{script.TOKEN}} → eyJhbGciOiJIUzI1…preScriptLines, postScriptLines, .kts scripts
{{script.<function>()}}The output of a built-in function{{script.uuid()}} → 550e8400-e29b-41d4-a716-446655440000Built in
{{secrets.<KEY>}}A decrypted, device-local secret{{secrets.API_KEY}} → sk-live-abc123Secrets vault
{{var.<KEY>}}A path-param value saved with the request{{var.userId}} → 5pathParams

Templates can be combined in one string and nested: an inner {{…}} resolves first and becomes part of the outer expression.

ExpressionResolution
{{selector.environment.BASE_URL}}/users/{{var.userId}}?ts={{script.timestamp()}}https://dummyjson.com/users/5?ts=1718358792000
{{secrets.CLIENT_SECRET_{{selector.brand}}_{{selector.environment}}}}With Brand IKEA and Environment TEST1: reads the secret CLIENT_SECRET_IKEA_TEST1
{{script.sign({{var.payload}},{{secrets.SIGNING_KEY}})}}Both arguments resolve first, then sign() runs on the resolved values

Resolution rules:

CaseBehavior
A secret or script variable contains {{…}}Sent as-is, never re-resolved. Data returned by an API cannot inject a template.
A selector variable contains {{…}}Resolved again, so one selector can be composed from another. See Composed from another selector.
A template cannot be resolvedSent literally, e.g. a typo like {{selector.enviroment.BASE_URL}} is transmitted unchanged.

Path params

A URL can contain {{var.KEY}} placeholders, e.g. https://api.example.com/users/{{var.userId}}. Their values are saved with the request under pathParams.

Auth

Per-request authentication is stored in the request file as an auth block. All values support templates. An auth-generated header overrides a manual header of the same name.

TypeFieldsWhat is sent
bearertokenAuthorization: Bearer <token>
basicusername, passwordAuthorization: Basic base64(username:password)
apikeykey, value, addTo (header/query)Header <key>: <value>, or query parameter <key>=<value>
digestusername, passwordAnswers the server's 401 digest challenge (RFC 2617, MD5) automatically
oauth1oauth1 (nested object)Authorization: OAuth ..., signed per RFC 5849, header only
oauth2oauth2 (nested object), addTo (header/query)Authorization: <headerPrefix> <token>, or query parameter access_token
"auth": { "type": "bearer", "token": "{{script.TOKEN}}" }

Keep real credentials out of the file and reference them as {{secrets.KEY}}. The Showcases workspace contains a working example of each type.

OAuth 1.0 (oauth1)

FieldDescription
consumerKeyOAuth consumer key
consumerSecretOAuth consumer secret
tokenOAuth token
tokenSecretOAuth token secret
signatureMethodHMAC-SHA1 (default), PLAINTEXT, or RSA-SHA1
realmOptional realm included in the Authorization header
callbackOptional oauth_callback value
verifierOptional oauth_verifier value
privateKeyPEM-encoded PKCS#8 private key, used only when signatureMethod is RSA-SHA1

The request is signed locally at send time per RFC 5849: the signature base string covers the method, the normalized URL, and all query and form parameters, with a fresh oauth_nonce and oauth_timestamp on every send. The result always goes in the Authorization header.

"auth": {
  "type": "oauth1",
  "oauth1": {
    "consumerKey": "{{secrets.CONSUMER_KEY}}",
    "consumerSecret": "{{secrets.CONSUMER_SECRET}}",
    "token": "{{secrets.OAUTH_TOKEN}}",
    "tokenSecret": "{{secrets.OAUTH_TOKEN_SECRET}}",
    "signatureMethod": "HMAC-SHA1"
  }
}

OAuth 2.0 (oauth2)

FieldDescription
grantTypeclient_credentials (default), authorization_code, password, refresh_token, or implicit
authUrlAuthorization endpoint, used by authorization_code and implicit
tokenUrlToken endpoint, used by every grant except implicit
clientIdOAuth client ID
clientSecretOAuth client secret
scopeSpace-separated scopes
redirectUrlRedirect URI registered with the provider
username, passwordUsed by the password grant
refreshTokenUsed by the refresh_token grant
accessTokenA manually set token; always wins over a fetched token
headerPrefixScheme prefix sent with the token, default Bearer
clientAuthHow clientId/clientSecret reach the token endpoint: body (default) or basic
usePkceBoolean, adds S256 PKCE to the authorization_code flow, default false
autoRefreshBoolean, default true
GrantToken acquisition
client_credentials, password, refresh_tokenFetched from tokenUrl at send time; a cached token is reused until it goes stale.
authorization_code, implicitRequires a browser authorization step first.

Fetched tokens are held in memory only, never written to the request file, and cleared on workspace switch. Only a manually set accessToken is saved to disk.

"auth": {
  "type": "oauth2",
  "oauth2": {
    "grantType": "client_credentials",
    "tokenUrl": "https://auth.example.com/oauth/token",
    "clientId": "{{secrets.CLIENT_ID}}",
    "clientSecret": "{{secrets.CLIENT_SECRET}}",
    "scope": "read write",
    "clientAuth": "basic"
  }
}

Settings

Per-request transport behavior is stored in the request file as a settings block.

FieldValuesDefaultDescription
httpVersionhttp/1.1, http/2http/2http/2 is negotiated over TLS and falls back to HTTP/1.1 when the server does not support it. http/1.1 forces HTTP/1.1.
followRedirectstrue, falsefalseFollow 30x responses via their Location header.
cookiesEnabledtrue, falsefalseSend and store cookies via the shared in-memory cookie jar.
timeoutMs1000–600000nullSingle budget for the whole call. Unset keeps the staged defaults: 30 s connect, 60 s read, 30 s write. Out-of-range values are clamped.

The cookie jar is one in-memory store shared by every request with cookiesEnabled: true. It is not isolated per request and resets on every app restart.

"settings": { "httpVersion": "http/1.1", "followRedirects": true, "cookiesEnabled": true, "timeoutMs": 5000 }

How the body is encoded

bodyType selects the editor, not the encoding. raw and table both produce a JSON object; the Content-Type header decides what goes on the wire.

Content-Type headerSent as
absentNo Content-Type header. Synapse never guesses a media type.
absent, with bodyFilePath setapplication/octet-stream
absent, with bodyType: graphqlapplication/json; charset=utf-8
application/x-www-form-urlencodedForm-urlencoded; a JSON object body is converted to form fields
anything elseThat value, body sent verbatim
RuleBehavior
Text body, media type without charset; charset=utf-8 is appended, e.g. application/json; charset=utf-8.
File bodySent untouched.
bodyParts holds a named part on a body-carrying methodSynapse writes the multipart Content-Type itself and ignores a manual one.
GET, HEAD, DELETE with empty bodyNo body is sent.
POST, PUT, PATCH with empty bodySent with Content-Length: 0.

Table body field types

With bodyType: table, the JSON body is built from key/value pairs. Each value is sent as a string (default) or as raw JSON. A raw-JSON value that is not valid JSON falls back to a string.

KeyValueSent asResulting JSON
nameAlicestring"name": "Alice"
age30JSON"age": 30
activetrueJSON"active": true
notenullJSON"note": null
address{"city":"Prague"}JSON"address": {"city":"Prague"}
tags["a","b"]JSON"tags": ["a","b"]

Binary body (file upload)

A file's raw bytes are sent as the body. The file's absolute path is saved with the request as bodyFilePath. Without a Content-Type header the body goes out as application/octet-stream; a manual header (e.g. image/png) overrides it. The path is machine-specific and does not resolve on another computer.

Multipart form-data body

Text fields and file uploads are combined in one multipart/form-data request. The parts are saved with the request as bodyParts.

Part fieldTypeDefaultDescription
namestring—Form field name. Required; unnamed parts are not sent.
typetext, filetextPart kind. Unknown values are treated as text.
valuestring""Text parts only. Supports templates.
filePathstring or nullnullFile parts only. Absolute path of the file; machine-specific.
fileNamestring or nullfile's own nameFile parts only. Overrides the filename= parameter.
contentTypestring or nullsee descriptionPer-part Content-Type. Default: detected from the extension for files, omitted for text.
enabledbooleantrueDisabled parts are kept in the file but not sent.

Synapse writes Content-Type: multipart/form-data; boundary=... itself, with a fresh random boundary on every send, and ignores a manual one. Parts are never sent on GET or HEAD.

GraphQL body

With bodyType: graphql, the query and its variables are stored separately and unescaped in a graphql block, and sent as the standard JSON envelope {"query", "variables", "operationName"}.

FieldTypeDefaultDescription
querystring""The GraphQL document. Supports templates.
variablesobject or nullnullA native JSON object, not an escaped string. Templates resolve inside string values.
operationNamestring or nullnullNeeded only when query holds several operations.
RuleBehavior
TemplatesResolved first, then JSON-encoded, so a value with quotes or newlines stays valid JSON.
variables null or emptyLeft out of the envelope.
operationName null or blankLeft out of the envelope.
No Content-Type headerSent as application/json; charset=utf-8. A manual header overrides it.
bodyType other than graphqlThe graphql block is kept but not sent; body is sent instead.

The GraphQL-over-HTTP specification recommends Accept: application/graphql-response+json, application/json. GraphQL is sent as a POST; a GET is not encoded into query parameters.

{
  "id": "graphql-character",
  "name": "Get Character",
  "method": "POST",
  "url": "https://rickandmortyapi.com/graphql",
  "bodyType": "graphql",
  "graphql": {
    "query": "query Character($id: ID!) {\n  character(id: $id) { name status }\n}",
    "variables": { "id": "{{var.characterId}}" }
  }
}

The encoded body is what request.body and response.body script paths see, e.g. request.body.variables.id or response.body.data.character.name.

Example — login with token extraction

{
  "id": "login",
  "name": "Login",
  "method": "POST",
  "url": "{{selector.environment.BASE_URL}}/auth/login",
  "headers": {
    "Content-Type": "application/json",
    "Accept": "application/json",
    "User-Agent": "{{selector.device.USER_AGENT}}",
    "X-APP-OS-SYSTEM-VERSION": "{{selector.device.OS_VERSION}}"
  },
  "bodyType": "raw",
  "body": "{\"username\":\"emilys\",\"password\":\"emilyspass\"}",
  "postScriptLines": [
    "saveVariable(\"TOKEN\", response.body.accessToken)",
    "saveVariable(\"REFRESH\", response.body.refreshToken)",
    "saveVariable(\"USER_ID\", response.body.id)",
    "runScript(\"parse-token.kts\")"
  ]
}
FeatureWhereEffect
Selector in the URL{{selector.environment.BASE_URL}}The Environment dropdown switches the base URL.
Selector in headers{{selector.device.USER_AGENT}}, {{selector.device.OS_VERSION}}The Device dropdown switches both headers.
Post-response script linessaveVariable("TOKEN", response.body.accessToken), …{{script.TOKEN}} is available in every later request.
Custom scriptrunScript("parse-token.kts")Runs scripts/parse-token.kts, which decodes the JWT into JWT_USER_ID.

4. Selectors — dynamic dropdowns

Each .json file in selectors/ defines one dropdown. Switching a selector retargets every request that references it. Selectors are independent, so each testing axis (environment, profile, device, API version, region, locale) is one selector rather than one copy of the whole environment per combination.

The DummyJSON sample ships three:

SelectorProvidesTemplates
environmentBase URL per environment (Local, Staging, Live){{selector.environment.BASE_URL}}
profileOne option per teammate with their own test data{{selector.profile.PRODUCT_ID}}, {{selector.profile.PRODUCT_NAME}}
deviceUser-Agent and OS version headers per platform{{selector.device.USER_AGENT}}, {{selector.device.OS_VERSION}}

Selectors, options, and variables can also be managed inside Synapse.

Field reference

FieldTypeRequiredDescription
idstring✅Used in templates: {{selector.<id>}} or {{selector.<id>.<KEY>}}.
namestring✅Display label.
defaultValuestring or null—Pre-selected option value. Falls back to the first option.
optionsarray✅List of options (below).

Each option has:

FieldTypeRequiredDescription
valuestring✅Injected by {{selector.<id>}}. Never expanded as a template.
labelstring✅Display text.
variablesobject (string→string)—Extra key/value pairs accessed as {{selector.<id>.<KEY>}}.

Three patterns

With variables: several values per option.

{
  "id": "device",
  "name": "Device",
  "defaultValue": "android",
  "options": [
    {
      "value": "android",
      "label": "🤖 Android",
      "variables": { "USER_AGENT": "DummyJSON-App/3.2.0 (Android 14; Pixel 8)", "OS_VERSION": "14" }
    },
    {
      "value": "ios",
      "label": "🍎 iOS",
      "variables": { "USER_AGENT": "DummyJSON-App/3.2.0 (iPhone; iOS 17.5)", "OS_VERSION": "17.5" }
    },
    {
      "value": "desktop",
      "label": "🖥️ Desktop",
      "variables": { "USER_AGENT": "DummyJSON-App/3.2.0 (Windows NT 10.0; Win64; x64)", "OS_VERSION": "10" }
    }
  ]
}

Without variables: the option's value is used directly.

{
  "id": "version",
  "name": "API version",
  "defaultValue": "v1",
  "options": [
    { "value": "v1", "label": "v1 (stable)" },
    { "value": "v2", "label": "v2 (beta)" }
  ]
}

Composed from another selector: a variable may itself contain templates.

{
  "id": "environment",
  "name": "Environment",
  "defaultValue": "live",
  "options": [
    { "value": "live",    "label": "🌍 Live",    "variables": { "BASE_URL": "https://api.example.com/{{selector.version}}" } },
    { "value": "staging", "label": "🧪 Staging", "variables": { "BASE_URL": "https://staging.example.com/{{selector.version}}" } }
  ]
}
SelectionTemplateResolves to
Device ios{{selector.device.USER_AGENT}}DummyJSON-App/3.2.0 (iPhone; iOS 17.5)
API version v2{{selector.version}}v2
Environment live, API version v2{{selector.environment.BASE_URL}}https://api.example.com/v2

A variable can reference anything a request can: another selector, {{secrets.<KEY>}}, {{script.<function>()}}. Chains of any length are followed. A reference that leads back to itself is left unresolved.

5. Scripts — dynamic values & automation

Built-in functions

Always available, no setup. Usable in any template field.

FunctionTemplateOutput example
uuid(){{script.uuid()}}550e8400-e29b-41d4-a716-446655440000
timestamp(){{script.timestamp()}}1718358792000
isoTimestamp(){{script.isoTimestamp()}}2026-06-14T10:33:12.000Z
randomNumeric(n){{script.randomNumeric(6)}}482910
randomAlphabetic(n){{script.randomAlphabetic(9)}}xKvPtRmoQ
base64(value){{script.base64(hello)}}aGVsbG8=
sign(payload,secret){{script.sign(data,mySecret)}}Base64 HMAC-SHA256 digest

Arguments are literal text separated by commas; a value containing a comma is split into two arguments. To pass a resolved value as an argument, nest the template: {{script.sign({{var.payload}},{{secrets.SIGNING_KEY}})}}.

Script lines (pre-request and post-response extraction)

A request holds two script-line lists with the same syntax: preScriptLines runs before the send, postScriptLines after the response arrives.

Every line is a function call:

Script lineEffect
saveVariable("NAME", source.path)Stores the value at source.path as {{script.NAME}}. The name is in double quotes.
runScript("name.kts")Runs scripts/name.kts. See Custom Kotlin scripts.
clearScriptVariables()Discards all session variables. See Session variables.

Lines starting with // or # are comments.

Breaking change: older versions used NAME = source.path and run:name. These lines no longer run. Each one fails with an error that shows the line in the current form, e.g. saveVariable("NAME", source.path) or runScript("name.kts").
SourceAvailable inExtracts
request.body.<key>.<key>…pre, postRequest JSON body (dot-separated key chain, integer = array index)
request.headers.<header-name>pre, postRequest header
request.urlpre, postResolved request URL (after template substitution)
request.methodpre, postHTTP method, e.g. POST
response.body.<key>.<key>…postResponse JSON body (dot-separated key chain, integer = array index)
response.headers.<header-name>postResponse header
response.statuspostHTTP status code, e.g. 200 (0 if the server was never reached)
response.statusTextpostHTTP status message, e.g. Not Found
response.durationpostRound-trip time in milliseconds

The body path is a plain dot-separated key walk, not JSONPath. Wildcards, $, and filters are not supported.

Script lineCapturesAccess afterward
saveVariable("TOKEN", response.body.accessToken)accessToken field of the response JSON{{script.TOKEN}}
saveVariable("FIRST_ITEM", response.body.products.0.title)title of the first item in the products array{{script.FIRST_ITEM}}
saveVariable("TRACE_ID", response.headers.X-Request-Id)X-Request-Id response header{{script.TRACE_ID}}
saveVariable("CODE", response.status)Status code{{script.CODE}}
saveVariable("SEED", request.headers.X-Seed)X-Seed request header, usable in the same request (pre){{script.SEED}}

Pre-request (preScriptLines)

A variable set here is substituted into the same request's {{script.VAR}} templates (URL, headers, query and path params, body, auth fields) before it is sent.

Post-response (postScriptLines)

Values captured here become session variables for later requests.

Failure behavior

PhaseFailureEffect
Pre-requestA line reads response.*Send is aborted, no HTTP call is made.
Pre-requestA line is not a valid script lineSend is aborted, no HTTP call is made.
Pre-requestA runScript(...) script failsSend is aborted, no HTTP call is made.
Post-responseA line is invalid or a runScript(...) script failsNothing is aborted; the response is shown and saved to history.

Example abort message:

Pre-request script cannot read "response.body.token": there is no response yet —
move "saveVariable("TOKEN", response.body.token)" to the post-request script.

Session variables and clearScriptVariables()

Captured variables are kept in memory for the session and discarded when Synapse closes. They can also be reviewed and cleared inside Synapse.

A line clearScriptVariables() discards all variables at that point: variables captured above it are dropped, lines below it are kept. It is valid in both lists and runs before that phase's runScript(...) scripts.

Script linesResult after the phase
clearScriptVariables()No session variables remain
clearScriptVariables()
saveVariable("TOKEN", response.body.accessToken)
Only TOKEN remains

Custom Kotlin scripts (.kts)

Script lines only read what the request and response already contain. A .kts script in scripts/ can run any code: call another API, run a command-line tool, read a file, query a database, sign or decode a token. Trigger it with runScript("<name>.kts") from either script-line list. Every entry the script writes into result becomes a {{script.KEY}} value.

Scripts are not sandboxed. They run with the same permissions as Synapse itself. Only run scripts you trust.
BindingTypeExample usage
context.variablesMap<String, String>context.variables["TOKEN"]
context.responseBodyStringcontext.responseBody
context.responseHeadersMap<String, String>context.responseHeaders["X-Request-Id"]
context.statusCodeIntif (context.statusCode == 401) …
context.statusMessageStringcontext.statusMessage
context.durationMsLongif (context.durationMs > 1000) …
context.requestHeadersMap<String, String>context.requestHeaders["Authorization"]
context.requestBodyStringcontext.requestBody
context.urlStringcontext.url
context.methodStringcontext.method
context.phasePre or Postif (context.phase == ScriptPhase.Pre) …
result (output)MutableMap<String, String>result["JWT_USER_ID"] = "42"
SituationBinding values
Pre phaseresponseBody, responseHeaders, statusMessage are empty; statusCode and durationMs are 0.
Server never reached (Post)statusCode is 0, which distinguishes a transport failure from an HTTP error response.

A script used in both phases should branch on context.phase.

Example: decode a JWT and extract the user ID.

scripts/parse-token.kts

import java.util.Base64

val token = context.variables["TOKEN"] ?: error("TOKEN not set — send Login first")
val payloadJson = String(Base64.getUrlDecoder().decode(token.split(".")[1]))

val idMatch = """"id"\s*:\s*(\d+)""".toRegex().find(payloadJson)
result["JWT_USER_ID"] = idMatch?.groupValues?.get(1) ?: ""

Trigger in a request's postScriptLines:

saveVariable("TOKEN", response.body.accessToken)
runScript("parse-token.kts")

After Login, {{script.JWT_USER_ID}} is available in every request.

6. Secrets — encrypted local credentials

API keys, tokens, and passwords are kept in an encrypted, device-local vault saved outside the workspace and outside any repository. They never appear in collection files.

PropertyValue
LocationSynapse's local config folder (next to config.json), per workspace. Nothing about secrets is tracked in Git.
KeyValid only on the current device and never written to disk.
SharingNot shared through the repository. Each developer imports their own .env once per machine.
DisplayA resolved secret is shown and copied as its {{secrets.KEY}} placeholder in request and response views by default. Hiding can be turned off.

Import from a .env file

API_KEY=sk-live-abc123
DB_PASSWORD=hunter2
WEBHOOK_SECRET=whsec_xyz

Synapse reads the file, encrypts each value, and saves the result in its local config folder. The .env file itself is not copied into the workspace. Keep it outside the repository.

Manage secrets

Secrets can be added, edited, and removed one at a time. Export writes all current secrets to a plain <workspace>_secrets.env file for backup or another machine. Store it outside the repository.

Use them in any request

Reference a secret with {{secrets.KEY}} wherever templates work. The value is decrypted at send time.

{
  "headers": {
    "Authorization": "Bearer {{secrets.API_KEY}}"
  }
}

7. Tips & recipes

GoalHow
Chain requestsCapture saveVariable("TOKEN", response.body.accessToken) in Login's postScriptLines; later requests use {{script.TOKEN}} in Authorization.
Switch environments or identitiesURLs in an environment selector, per-teammate test data in a profile selector, device headers in a device selector.
Control sidebar orderPrefix filenames and folders: 01-login.json, 02-current-user.json.
Combine templates{{selector.environment.BASE_URL}}/products/{{selector.profile.PRODUCT_ID}}?ts={{script.timestamp()}}

8. Importing and creating workspaces with AI

Let an AI write your collections

Every file is structured JSON in a predictable path. Any LLM can generate collections/ and selectors/ files from a description of the API. Secrets stay encrypted and out of the files.

Import an existing collection

Three skills, built on the agent-skill standard, work with any compatible AI coding assistant.

SkillWhat it does
synapse-import-workspaceBuilds a workspace from OpenAPI/Swagger, Postman, Insomnia, Bruno, RapidAPI, HAR, or curl, with an environment selector for every base URL and {{secrets.KEY}} placeholders for credentials.
synapse-validate-workspaceChecks a workspace's files, ids, templates and scripts, and flags credentials written in plain text.
synapse-docsGives the assistant this documentation, which the other two skills read.

All three are one download in the download section. Unpack it into your assistant's skills folder and keep the three skills side by side.