Skip to content

HTTP lookups

.lookup — pick a file, fire it, pick an item, name the var, write to .env

Lookups solve a very specific problem: a request needs an id (a merchant id, a user id, a location id), the id lives in some other endpoint’s response, and you keep copy-pasting the value between Postman tabs. The lookup picker chains the two — fire the “list” endpoint, pick the row you want, upsert its id into your env file under a name like MERCHANT_ID.

The result: next time you fire any request with {{MERCHANT_ID}} in its URL or body, it resolves to the id you just picked. No tab-switching, no clipboard, no editing the env file by hand.

Drop a .curl (or .http / .rest) file under <workspace>/.rqst/lookups/:

<workspace>/.rqst/lookups/
├── locations.curl
├── merchants.curl
├── delivery-partners.curl
└── orders/
└── recent.curl

The directory is scanned recursively by crate::http::lookup::scan_lookups (skipping target, node_modules, and any dotfile-prefixed entries); subdirectories are fine and show up in the picker with their relative path as the label (orders/recent.curl). All three extensions (.curl / .http / .rest) are picked up by the same walker. The file’s name matters for the var-name suggestion (more on that below) but doesn’t have to match anything else.

Each lookup file is a normal request:

.rqst/lookups/merchants.curl
curl '{{BASE_URL}}/v2/merchants?limit=50' \
-H 'Authorization: Bearer {{TOKEN}}' \
-H 'Accept: application/json'

{{VAR}} substitution runs against the active env before the lookup fires — the picker can’t know which merchant id to pick without a token. The @set-* directives also run, same as a normal :http.send.

SurfaceCall
PaletteHTTP: lookup — fill an env var from a live API response
Ex-command:http.lookup

No default keybinding. Bind under [keys.global] or call from the palette / fuzzy launcher.

The picker walks four stages:

A standard fuzzy picker over every file under .rqst/lookups/:

Lookup file
▸ merchants.curl
locations.curl
delivery-partners.curl
orders/recent.curl

Type to filter. Enter commits the chosen file. Esc cancels.

If .rqst/lookups/ is missing or empty, the picker toasts no lookups in <path> — add a .curl file under that dir and exits.

Enter on a file kicks off a background HTTP send (so the UI doesn’t freeze for a 30-second reqwest::send). A toast confirms lookup: firing request… and App::tick polls the result channel.

The send goes through the full request pipeline:

  • http::parse on the file contents (auto-detecting cURL vs .http shape).
  • @set-env and @set-header directives run.
  • {{VAR}} and {{$uuid}} substitution against EnvSet::select(workspace, None).
  • A blocking http::send on a worker thread.

On error (transport, parse, bad URL) — the picker toasts the error and stops. On success, the response body is parsed for list items.

The picker reads the response body looking for a JSON list shape (more on the heuristic below). When it finds one, every item gets a label and an id, and the second picker opens:

Lookup item · merchants.curl
▸ Hot Pizza Co (id: 2148)
Sushi Spot (id: 1042)
Tacos & Co (id: 8801)

Type to filter (matches label or id). Enter commits. Esc cancels.

If the body doesn’t look like a list:

  • lookup: merchants.curl response wasn't a recognized list shape — no parseable array found.
  • lookup: merchants.curl returned 0 items — array was empty.

The picker pops a prompt:

Env var name for Hot Pizza Co (2148):
> █

Type the env var name you want this id under (MERCHANT_ID, USER_ID, LOCATION_ID, whatever’s conventional in your env file), then Enter. The prompt starts empty so you can pick whatever name fits — no auto-derived suggestion. Convention in mnml’s env files is uppercase-snake-case ending in _ID.

The picker writes <var>=<id> to <workspace>/.rqst/env/<active>.env — preserving every other line, comments and ordering intact. Existing vars with the same name are replaced in place; new vars are appended:

# .rqst/env/dev.env (before)
BASE_URL=https://dev-api.example.com
TOKEN=eyJhbGci...
MERCHANT_ID=1042
# .rqst/env/dev.env (after picking Hot Pizza Co)
BASE_URL=https://dev-api.example.com
TOKEN=eyJhbGci...
MERCHANT_ID=2148

The active env name comes from EnvSet::select(workspace, None).name()$MNML_ENV or .rqst/config’s default_env, falling back to "dev" when neither is set. The write lands in .rqst/env/, not .mnml/env/ — the legacy path, deliberately, so the convention stays portable with rqst.

The toast confirms wrote <var>=<id> → <env-file>.

Step 3 is the most opinionated piece. Lookup tries four shapes, in order:

// 1. Bare array
[ { "id": 1, "name": "First" } ]
// 2. { "data": [ ... ] }
{ "data": [ { "id": 1, "name": "First" } ] }
// 3. { "items": [ ... ] }
{ "items": [ { "id": 1, "name": "First" } ] }
// 4. { "results": [ ... ] }
{ "results": [ { "id": 1, "name": "First" } ] }
// 5. Single-key object whose value is an array
{ "merchants": [ { "id": 1, "name": "First" } ] }

The single-key fallback lets the picker handle the common “REST envelope with a domain-named field” pattern ({"orders": [...]}) without per-API config. Multi-key envelopes ({"data": [...], "meta": {...}}) match the data arm and ignore meta.

For each item in the array, the id is the first match from:

PriorityField
1id (literal)
2Id (PascalCase)
3_id (MongoDB style)
4Any field whose lowercased name ends in id and is longer than 2 chars (merchantId, userId, deliveryPartnerId)

The id can be a string or a number — numbers are stringified (2148"2148") for the env write. Objects and arrays and nulls in the id slot disqualify the item (it’s silently skipped).

The display label uses the first match from:

PriorityField
1name
2displayName
3label
4title
5summary
FallbackThe id itself

When none of those fields are present, the picker still shows the row — just labeled with its id ((id: 2148)).

The env upsert is a line-aware replace:

  • Existing line MERCHANT_ID=1042 → replaced with MERCHANT_ID=2148. Surrounding comments and blanks preserved.
  • No existing line → appended at end, with a trailing newline guard.
  • Var name is rejected when blank (lookup: var name can't be empty).
  • Value containing a newline is rejected (lookup: value can't contain newline) — the env-line format doesn’t support multi-line values.

The write is atomic — std::fs::write overwrites the file in place. The parent directory is created if missing.

  • A way to pick multiple items. One Enter, one id, one env write. To pick five merchants, run :http.lookup five times with five different var names (MERCHANT_A_ID, MERCHANT_B_ID, …) — or write a chain that calls the same lookup endpoint and @captures into different vars.
  • A way to pick by anything other than id. Labels are display-only. If you want to filter by status, name, or anything else, narrow the picker’s fuzzy filter — type the name, press Enter on the right row.
  • A way to pick a non-id value. The fourth stage writes whatever the id field resolved to. To capture, say, a merchant’s currency code or hourly rate, you’d need @capture currency = json $.items[0].currency in a regular request — see HTTP envs & templating for @capture.
  • A way to discover endpoints. The picker only fires lookups that already exist as files under .rqst/lookups/. Adding a new endpoint to the list is cp some.curl .rqst/lookups/merchants.curl.
Terminal window
# Once, when you start using a new endpoint:
$ mkdir -p .rqst/lookups
$ cat > .rqst/lookups/merchants.curl <<'EOF'
curl '{{BASE_URL}}/v2/merchants?limit=50' \
-H 'Authorization: Bearer {{TOKEN}}'
EOF
# Every time you need a merchant id:
:http.lookup merchants.curl pick a row Enter
# Now {{MERCHANT_ID}} works in any other request.
requests/orders/get.http
# @set-env MERCHANT_ID = {{$uuid}} ← override the lookup-set value with a fresh UUID
GET {{BASE_URL}}/v2/merchants/{{MERCHANT_ID}}/orders?limit=10
Authorization: Bearer {{TOKEN}}

The lookup-written MERCHANT_ID resolves from .env by default, but @set-env overrides for a single request — useful for testing with synthetic ids.

Pick a merchant id in dev, switch to staging, expect the same merchant doesn’t exist there. The picker writes to whatever env is currently active (MNML_ENV=staging → staging env file). Rerun :http.lookup to repopulate after switching.

  • HTTP client — the parent overview: how the lookup’s chosen .curl file is parsed and sent
  • HTTP envs & templating{{MERCHANT_ID}} resolution rules and @capture (the chain-style equivalent of lookup writes)
  • HTTP history — lookup’s background fires are not logged here; the user-facing send is what counts
  • HTTP helpers — JWT & bearer — when the lookup needs a fresh token, auth.extract_bearer + jwt.decode help debug it