Your first request
You compose a request with a method, URL, headers, query parameters, body, auth, scripts, and settings, then click Send.
What happens on every send
Section titled “What happens on every send”Fetchpier runs the same pipeline for every request:
- Pre-request script (if any) runs first.
- Auth application — the chosen auth scheme injects the appropriate header or query parameter.
- 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. - Form encoding —
x-www-form-urlencodedbodies have their enabled key/value pairs URL-encoded and joined. - Settings resolution — per-request setting overrides are collapsed against the workspace defaults.
- Cookie attachment — matching cookies from the active environment’s jar are added as a
Cookie:header, unless disabled. - TLS material — custom CAs and a matching client certificate are attached for HTTPS sends.
- Send — the request goes out over HTTP/1.1 or HTTP/2, optionally through a proxy.
- Cookie capture —
Set-Cookieheaders from the response are parsed and persisted into the active environment’s jar. - Post-response script (if any) runs last.
- Activity record — the full request, response, scripts, settings, and cookies in/out are
written as an
.httpfile underrequests/.runs/.
The response appears in a viewer with a status badge, latency, size, and four sections: body, cookies, headers, and tests.
Default headers and compressed responses
Section titled “Default headers and compressed responses”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.
Methods, headers, and parameters
Section titled “Methods, headers, and parameters”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.
Body types
Section titled “Body types”The body type is one of:
- none — no body sent.
- raw — a free-form body whose sub-type is
json,xml,javascript,text, orhtml, driving syntax highlighting plus the defaultContent-Typeheader. - 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.httpfile stores only a reference, not the bytes.Content-Typeis 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 wirefilenameoverride, independent of the on-disk name. TheContent-Type: multipart/form-dataheader 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-addressedrequests/.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.
File attachment size limits
Section titled “File attachment size limits”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.
Cancellation and timeouts
Section titled “Cancellation and timeouts”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 send that fails always says why
Section titled “A send that fails always says why”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.