Everything below uses placeholder hosts (
example.com),your-org, and{{ _.token }}-style variables. Do this only against systems you're authorized to manage, and keep real secrets in a private environment (or a secrets plugin), never in a shared/base environment or an export.
00Environments & variables — the one concept that makes this work
The trick that turns a pile of requests into a remote control is Insomnia's environments.
Instead of hard-coding a host or a token into every request, you define variables once and
reference them with template tags like {{ _.base_url }}. Two ideas to internalize:
- Base vs sub-environments. Put shared, non-secret values in the Base Environment; create sub-environments (e.g. INT, PROD) that override a few keys so one click flips your whole collection between sites.
- Private environments for secrets. Mark the environment holding tokens and passwords as private so it is excluded from exports — secrets never travel with the collection.
A typical environment for this guide:
| gh_base | https://api.github.com |
|---|---|
| owner / repo | your-org / your-repo |
| fmg_host | fmg-eu.example.com |
| fgt_host | fgt-01.example.com |
| adom / vdom | root / root |
| session | (filled at runtime from the login response) |
| gh_token / fgt_token / fmg_pass | (private environment) |
One more superpower: response chaining. Insomnia can pull a value out of one request's response and feed it into another with a Response → Body Attribute tag. We use it below to capture a FortiManager session id automatically — no copy-paste.
01GitHub Actions — trigger pipelines from Insomnia
Authenticate. Create a fine-grained Personal Access Token
(Settings → Developer settings) scoped to the repo with Actions: read & write. In
Insomnia set the request auth to Bearer Token = {{ _.gh_token }}, and add
the standard GitHub headers:
Authorization: Bearer {{ _.gh_token }}
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28
Bearer auth = "Authorization: Bearer " + token
Manipulate the environment. Keep gh_base, owner and
repo in the base environment, and gh_token in a private one. Now every
request is portable across repos by flipping two variables.
Do something. Trigger a workflow_dispatch pipeline — a
204 No Content means it's running:
POST {{ _.gh_base }}/repos/{{ _.owner }}/{{ _.repo }}/actions/workflows/deploy.yml/dispatches
{
"ref": "main",
"inputs": { "environment": "int", "version": "1.4.2" }
}
# Watch it run:
GET {{ _.gh_base }}/repos/{{ _.owner }}/{{ _.repo }}/actions/runs?per_page=5
$ POST .../actions/workflows/deploy.yml/dispatches
HTTP/2 204 No Content # accepted — the workflow is now running
$ GET .../actions/runs?per_page=5
{
"total_count": 1,
"workflow_runs": [
{ "id": 8123456789, "status": "in_progress", "head_branch": "main", "event": "workflow_dispatch" }
]
}
That's a deploy button living in your API client — parameters and all — instead of six clicks in the browser.
02FortiManager — JSON-RPC and the session dance
FortiManager doesn't use REST; it speaks JSON-RPC over a single endpoint,
POST https://{{ _.fmg_host }}/jsonrpc. Authentication is a login call that
returns a session id you must include in every subsequent request.
JSON-RPC request = method + params (url + data) + session + id
Authenticate. The login request needs no HTTP auth — the credentials go in the body:
POST https://{{ _.fmg_host }}/jsonrpc
{
"method": "exec",
"params": [{
"url": "/sys/login/user",
"data": { "user": "{{ _.fmg_user }}", "passwd": "{{ _.fmg_pass }}" }
}],
"id": 1
}
# Response contains the session:
# { "result": [...], "session": "aBcD…", "id": 1 }
$ POST https://{{ _.fmg_host }}/jsonrpc (login)
{
"result": [
{ "status": { "code": 0, "message": "OK" }, "url": "/sys/login/user" }
],
"session": "s3ssI0n-eXAMPLE-t0k3n-0123456789abcdef",
"id": 1
}
Capture the session into a variable. Rather than copy the token by hand, set
the environment's session value to a Response → Body Attribute tag that
points at the login request and the JSONPath $.session. From then on it's just
{{ _.session }}.
Manipulate. Every real call carries the session and targets an ADOM. Reads use
get, writes use set/add/update:
# List managed devices in an ADOM
POST https://{{ _.fmg_host }}/jsonrpc
{
"method": "get",
"params": [{ "url": "/dvmdb/adom/{{ _.adom }}/device" }],
"session": "{{ _.session }}",
"id": 2
}
# Log out cleanly when done
POST https://{{ _.fmg_host }}/jsonrpc
{ "method": "exec", "params": [{ "url": "/sys/logout" }], "session": "{{ _.session }}", "id": 99 }
Because the host and session are variables, the exact same collection points at any FortiManager by switching the sub-environment — one folder, every manager.
03FortiGate — REST with a token
A FortiGate exposes a proper REST API. Authenticate by creating a
REST API Admin on the box (System → Administrators), which yields an API
token; send it as a Bearer token. Keep fgt_host in the base environment and
fgt_token in the private one.
Authorization: Bearer {{ _.fgt_token }}
# Base path: https://{{ _.fgt_host }}/api/v2
FortiGate REST call = base URL + (cmdb | monitor) + resource (+ ?vdom)
Manipulate the config. The API splits into cmdb (configuration)
and monitor (live state). VDOM-aware calls take ?vdom=:
# Read config objects
GET {{ _.fgt_host }}/api/v2/cmdb/firewall/address?vdom={{ _.vdom }}
# Create one
POST {{ _.fgt_host }}/api/v2/cmdb/firewall/address?vdom={{ _.vdom }}
{ "name": "net-lab-10", "subnet": "10.10.0.0 255.255.255.0" }
# Live status (monitor)
GET {{ _.fgt_host }}/api/v2/monitor/system/status
$ GET .../api/v2/monitor/system/status
{
"status": "success",
"serial": "FGVMEV0000EXAMPLE",
"version": "v7.4.4",
"results": { "model": "FortiGate-VM64", "hostname": "fw-edge-01" }
}
Same pattern as before: authenticate once, template the host and token, and the request works against any FortiGate in the fleet.
04The cherry on the cake — run a whole folder at once
Here's where it stops being a client and starts being automation. When you have the same
request across many devices — "pull status from every FortiGate", "check tags on every
FortiManager" — you don't want to click each one. The plugin
insomnia-plugin-run-requests-parallel adds a folder action that
fires every request in a folder concurrently and shows you the results together.
- Install: Insomnia → Preferences → Plugins → install
insomnia-plugin-run-requests-parallel(or drop it into the plugins folder shown on that same screen), then restart. - Use: right-click a folder (say, one request per site) → Run requests (parallel). Twenty status checks come back in the time of one.
Fan out reads, not writes. Parallel is perfect for read-only fan-out (status, config pulls, tag checks). For anything that changes config, or where order matters (log in → act → log out), use Insomnia's built-in sequential collection runner instead — and always mind the target's rate limits so you don't trip protection or lock an account.
05Putting it together
One collection, a couple of environments, and three systems answer to the same remote control: dispatch a GitHub Actions deploy, pull a FortiManager ADOM's devices, tweak a FortiGate address object — then fan a health check across the whole estate in one click. Authenticate once, template everything, keep the secrets private, and let a folder do the clicking.