Skip to content

Your first request

You compose a request with a method, URL, headers, query parameters, body, auth, scripts, and settings, then click Send.

Fetchpier runs the same pipeline for every request:

  1. Pre-request script (if any) runs first.
  2. Auth application — the chosen auth scheme injects the appropriate header or query parameter.
  3. Variable interpolation — {{var}} placeholders are resolved in the URL, headers, body, and form fields. Unresolved names stay literal and are flagged in the UI; the request is sent anyway so you can inspect what would have gone out.
  4. Form encoding — x-www-form-urlencoded bodies have their enabled key/value pairs URL-encoded and joined.
  5. Settings resolution — per-request setting overrides are collapsed against the workspace defaults.
  6. Cookie attachment — matching cookies from the active environment’s jar are added as a Cookie: header, unless disabled.
  7. TLS material — custom CAs and a matching client certificate are attached for HTTPS sends.
  8. Send — the request goes out over HTTP/1.1 or HTTP/2, optionally through a proxy.
  9. Cookie capture — Set-Cookie headers from the response are parsed and persisted into the active environment’s jar.
  10. Post-response script (if any) runs last.
  11. Activity record — the full request, response, scripts, settings, and cookies in/out are written as an .http file under requests/.runs/.

The response appears in a viewer with a status badge, latency, size, and four sections: body, cookies, headers, and tests.

When a request does not set them, Fetchpier sends User-Agent: Fetchpier/<app version> and Accept-Encoding: gzip, deflate, br. Both defaults are applied at send time and never written into the request file; the headers editor shows them as read-only rows. A value already saved in a file is still sent as written.

Compressed responses (gzip, deflate, br) are decoded, so the viewer and your assertions read the real body. The viewer shows both the received and the decoded size, and flags a body that could not be decoded: such a body is kept as received, with a warning, and fp.response.json() fails with that cause. Scripts can check fp.response.decodingStatus.

Advisories about a send — a cookie jar or TLS material that could not be used, an HTTP/2 downgrade, a replaced multipart Content-Type, a script write that could not be saved — appear in a dismissible notice above the response.

The Cookies section lists both directions of the send — the cookies attached to the request and the ones the response offered — each with its name, value, domain, path, expiry, and its host-only, secure, HTTP-only and cross-site attributes. A cookie the server sent but the jar refused is listed too, struck through, with the reason stated above the tables. Columns fit the panel by default and can be resized by dragging a border, which trades width between the two adjacent columns so the table always fits. On a redirect chain each row also shows the hop it belongs to. The section’s tab carries the row count, plus a distinct mark when the send produced any cookie warning. See Cookies for how the jar decides what to store and send.

All standard HTTP methods are supported (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS) as well as arbitrary custom verbs. Headers and query parameters are edited as key/value lists where each row has an enable/disable checkbox, so you can leave context-specific entries in place and toggle them. Rows are reordered by dragging them by a handle, and the order can also be changed from the keyboard for accessibility. A header name may be repeated (several Accept or X-Custom rows): duplicates and their order are preserved end to end — kept on save, restored on reload, and sent on the wire in the order you authored them.

URLs with spaces and other literal characters are preserved exactly as written and sent unchanged — the app does not rewrite or “fix” the URL on the wire, so validity is your responsibility.

Lossless save and reload. Everything you author in a request survives a save and reopen equivalently: method, URL, query parameters, headers (duplicates and disabled rows included), all body types, auth, scripts, settings, and comments. The body is opaque — a line that merely looks like a separator (###) or a directive (# @…) sitting inside a body is kept as body content, never cut off or pulled out as configuration. Internal and trailing blank lines in a text body, and whether it ends with a final newline, are preserved.

Multiple requests in one file. A request file may contain more than one request, e.g. imported from another tool. The editor shows and edits the first request and preserves the rest untouched on save; both the app and the command-line runner execute only the first one, and the command line prints an informational notice when a file holds more.

Query parameters written without a value. A parameter authored as a bare flag token — ?wsdl, ?debug — is kept exactly that way: it is not rewritten to ?wsdl=. The distinction matters because a server is free to treat “the parameter is present as a flag” and “the parameter is present with an empty value” as different requests, and some do. The authored form is preserved end to end: the URL bar gives back the text you typed, the saved file stores it, the request goes out that way, and the recorded activity entry shows it — identically from the desktop and the command-line runner.

The flag form is created by writing the URL (typing it, or opening a request whose stored URL has it) and survives renaming the parameter, reordering it, disabling and re-enabling it, and any number of save/reopen cycles. It is cleared by editing the parameter’s value, and stays cleared even if the value is emptied again — so a parameter added from the parameters grid, or one whose value was ever touched, shows and sends the =. The two forms look identical in the parameters grid (both have an empty value cell); the URL bar is where the difference is written and read. Existing saved requests are not migrated: a stored ?wsdl= keeps its =.

Tolerant load of malformed query parameters. Opening a request whose stored URL contains a query parameter with malformed percent-encoding (for example ?%E0=x) never fails the load — a single bad parameter in a shared, untrusted file can’t make the request unusable. The parameter is kept exactly as written and the request stays runnable, sending the literal value as-is. The desktop marks such a request as having unsaved changes and shows a visible reason explaining that a query parameter could not be decoded and that saving removes it — it is not repaired or transformed; you can instead edit the parameter by hand to keep it, then save normally. Either way, saving clears the unsaved state. The command-line runner shares the same tolerant load: it warns naming the parameter, runs the request with the literal value, and does not fail the run for this reason.

The same tolerance holds wherever the app decodes a stored value, not only in the query editor. A malformed escape in a parameter name governed by an authentication scheme, or in a disabled row, no longer prevents the file from opening: the value is kept exactly as written and the rest of the file — including every other request in it — loads normally. Only query parameters carry the “saving removes it” remediation; elsewhere the authored bytes are simply preserved.

The body type is one of:

  • none — no body sent.
  • raw — a free-form body whose sub-type is json, xml, javascript, text, or html, driving syntax highlighting plus the default Content-Type header.
  • x-www-form-urlencoded — key/value pairs encoded as a URL form payload.
  • binary — a single file from disk sent as the raw request body. You pick the file through a dropdown listing the files already staged in the workspace (with a search input to filter by name), falling back to a native file picker via “Select local file…”. Fetchpier copies new files into the workspace under requests/.files/ — content-addressed, so identical files dedupe — keeping the workspace self-contained and portable. The .http file stores only a reference, not the bytes. Content-Type is inferred from the file extension unless you set the header explicitly.
  • multipart/form-data — a list of independently authored parts, each either a text value or a file from disk. You add parts one at a time, pick file content through the same staged-file dropdown as the binary body, and can reorder or disable individual parts without losing the configuration on save. Each part carries a form field name and an optional per-part Content-Type; file parts also carry an optional wire filename override, independent of the on-disk name. The Content-Type: multipart/form-data header is generated by Fetchpier at send time with a fresh per-send boundary — you do not type it, and it does not appear in the headers editor. Non-ASCII wire filenames are encoded per RFC 6266 / RFC 5987. File parts use the same content-addressed requests/.files/ store as binary bodies, so identical files are reused across requests.

Selecting a raw sub-type auto-fills the Content-Type header unless you already overrode it.

Workspaces shared via Git inherit the hosting provider’s per-file limits. Fetchpier guards against unexpected push failures by enforcing two thresholds when you attach a file to a binary body or a multipart file part, configurable per workspace:

  • Warn threshold (default 50 MB, matching GitHub’s per-file push warning): attaching a file above this size shows a confirmation prompt with the file’s size and the limit.
  • Block threshold (default 100 MB, matching GitHub’s hard block): files above this size cannot be attached at all, and you get an error explaining the limit.

A “Reset to GitHub defaults” link in workspace settings restores the original 50 / 100 MB pair. The same thresholds apply to binary bodies and to every individual file part in a multipart request — the limit is per file, not aggregate.

Scripts cannot read the bytes of a binary body: fp.request.body reads as undefined and assigning to it throws, while fp.request.bodyType and fp.request.binaryFileRef are exposed read-only so a script can branch on the body mode and reference the attached file by its workspace identifier. Multipart bodies do expose a structured fp.request.body.parts array to pre-request scripts, which can read, mutate (name / value / content-type / filename / disabled), reorder, remove, or add parts; each existing file part’s reference is read-only, and a new file part can only reference an already-staged file. Post-response scripts see the same structure but cannot mutate it.

While a request is in flight you can cancel it from the Send button, and a global timeout aborts requests that exceed it. Cancellations surface as an inline error on the request tab rather than discarding what was attempted. An expired timeout is reported as timed out after <limit>, not as a cancellation.

A request that never reaches its server — the host is unknown, the connection is refused, the handshake fails, the timeout expires — is reported on the tab with the reason, never as a silent no-op. When the host resolves to several addresses (a hostname published for both address families, localhost among them) and every one of them fails, the reason names what happened on each address rather than only the first: a server listening on one family while the client tried the other is the common cause, and only the full list tells the two situations apart. The same reason reaches the command-line runner’s reports.