Request
A request is one call: a method, a target, headers, a body, and a reference to auth. It is the unit everything else is built from — chains run them, tests assert on their responses, history records them.
Two origins, one type
Section titled “Two origins, one type”A request can be hand-authored — you wrote it, it lives in a collection YAML file, you committed it — or spec-derived, generated from an endpoint in a bound spec source.
They are the same shape. The difference is who owns the definition:
| Hand-authored | Spec-derived | |
|---|---|---|
| Lives in | A collection YAML you commit | Generated on each spec resolve |
| Survives a spec change | Untouched | Regenerated, with your edits re-applied as an overlay |
| Edited by | You, freely | You, as an overlay over the generated base |
These are reconcilable, not exclusive. Editing a generated endpoint does not fork it — Tinspec records the field groups that differ as an overlay and re-applies them after every refresh. Saving one as a request does fork it: the copy becomes hand-authored and stops tracking the spec.
Protocol
Section titled “Protocol”A request names its protocol. At v0.1.0-preview.10 the file format’s values are http, grpc,
graphql, websocket, langgraph, and mcp; a request with no protocol is HTTP.
Five of them are authored in the generic request editor: HTTP, gRPC, GraphQL, WebSocket, and
MCP. langgraph is deliberately not in the protocol switcher — it has
its own editor and run view, the way chains do.
Protocol changes what the editor shows, not how execution is reached. All of them go through
one engine, so they share auth injection, {{variable}} resolution, the cookie jar, gateway
rewriting, and history.
Calling somebody else’s MCP server
Section titled “Calling somebody else’s MCP server”An mcp request invokes one tool on one MCP server. The server is the request’s url; the
call is mcp.tool plus mcp.arguments, a JSON object stored as a string the way a gRPC message
and GraphQL variables already are.
There is deliberately no MCP server entity and no token field. A server needs an endpoint, a
credential, extra headers and transport settings — all four of which a request already has and
already inherits from collection defaults. So the credential is an ordinary
auth provider, which also means MCP’s own OAuth 2.1 authorization is
just an oauth2 provider rather than a second credential system.
This is the opposite direction from agent access. There, your agent drives Tinspec. Here, a Tinspec request calls someone else’s MCP server, exactly like any other protocol. The two never meet.
Stdio MCP servers — a command rather than a URL — are not supported: the engine does not spawn child processes for MCP, so there is nothing to address.
Fields
Section titled “Fields”The full list, with types, is in Collection file format → Request. The ones worth knowing by name:
queryis a first-class map, not something you hand-edit into the URL string. The Params tab edits it, and it is appended at send time.bodyis text, a multipart form (rows of text and file parts), or a single raw file.authis a reference to an auth provider by name, plus optionally which sender to send as — or a modestinlinecredential. A credential value can never appear here; see below.prerunChainnames a chain to run before this request; its output variables merge into the environment for the send. This is how “log in, then call” becomes a single press of Send.testsare assertions evaluated against this request’s own response.settingsare transport settings for this one call — the most specific layer of the layered send settings below.scriptsare the pre-request and post-response hooks.useCookieJaropts a single request out of the shared cookie jar.
Auth on a request
Section titled “Auth on a request”The normal case is one line: name a provider, optionally name a sender, and the provider decides where the credential goes.
Two things can be said on the request itself:
A per-request injection override. auth.inject replaces the provider’s placement for this
one request — the token in a JSON body field here, in the Authorization header everywhere else
— without forking the provider. The targets are header, query, cookie, body, and
variable; variable places nothing at all and only exposes {{token}} for you to put
somewhere by hand.
The body target is refused up front, before any network call, where it could not mean
anything: on a WebSocket request (there is no body) and on a multipart or file body. For MCP the
JSON slot is the tool’s arguments, and for gRPC and GraphQL it is the message and the variables
respectively.
An inline credential. auth.inline covers the API that is authenticated by a long-lived key
no token dance produces: bearer, basic, or apiKey. It exists because the alternative people
reached for was a raw Authorization: header typed into the request — which commits a literal
credential to git.
Inline auth cannot do that. There is no field in it that holds a value: the credential is named
by valueRef, an OS-keychain key resolved at call time, exactly like everything else in the
format. The only literal is username, which identifies rather than authenticates.
Transport settings
Section titled “Transport settings”settings carries the things that used to be global-only — timeout, redirect policy, response
size cap, HTTP version, a proxy, TLS material (client certificate, CA bundle, OS trust store),
and gRPC message-size and compression tuning.
They resolve in four layers, most specific wins, field by field:
- the request’s own
settings; - the collection’s
defaults.settings; - the API provider’s settings;
- the app’s global send options.
The engine resolves this and keeps provenance rather than flattening it, so a value can be
shown as 30 s — from provider billing-staging. That computation lives in the engine and is not
reimplemented in the UI, so the editor cannot disagree with the send.
Secrets stay references here too: tls.clientKeyRef and proxy.auth.passwordRef are keychain
names, so an effective-settings object is safe to display.
One limit worth knowing before you rely on it: the collection and provider layers do not apply
inside a chain. A chain run takes no settings environment, so a chain node uses its own
settings and nothing else. A provider-level proxy works in the editor and fails in a chain.
There is also no gRPC settings editor at v0.1.0-preview.10 — the gRPC message-size and compression fields are carried in the format and resolved by the engine, but nothing in the UI edits them yet.
scripts.pre and scripts.post are user JavaScript the engine runs immediately before the
request goes out and immediately after the response comes back. The code is stored inline in
the YAML as a block scalar, not as a file path — a hook belongs to exactly one request, and an
imported Postman script has nothing but source.
Both run in the same sandboxed QuickJS runtime as an auth script, under the same interrupt
handler, memory cap and wall-clock bound, and both see the same ts.* standard library: log,
fetch, secret, env, vars, setVar, request, response, hmac, sha256,
base64/base64url encode and decode, and jwt.decode/jwt.sign. That is the whole surface —
there is no module loader and no require, deliberately, because a collection arrives from a
teammate and runs against real credentials.
A pre hook runs after {{var}} substitution and can bind variables that are then substituted
into the request, which is how a computed HMAC signature reaches a header. A post hook is the
scripted counterpart of an extraction.
Hooks inherit from defaults.scripts field by field, so a collection-wide pre coexists
with a request’s own post. A request that declares a hook but leaves it blank suppresses the
inherited one — that is the way to opt out.
Variables
Section titled “Variables”Any string field can contain {{name}}, resolved from the active
environment at send time. Resolution happens in the engine,
immediately before dispatch — so what you see in the editor is the template, and history
records what was actually sent.
Unresolved variables fail the send by default rather than going out literally.
Selecting text in an editable field offers URL-encode, URL-decode and Set as variable… — see Environment for what the last one does with something that looks like a secret.
What comes back
Section titled “What comes back”The response pane is more than a body viewer:
- Timeline splits a buffered HTTP round trip into DNS, connect, waiting, and transfer. The
connect phase is labelled TCP + TLS together, because reqwest’s connector does DNS, TCP and
TLS as one unit and exposes no split — inventing a TLS number nobody measured would be worse
than not having one. A phase that did not happen renders as reused connection, never as
0 ms, because a pooled connection is the single most useful thing the panel can say about a fast request. Time the phases do not account for is shown as unaccounted rather than smeared across them. - The redirect chain lists the hops, which is usually the answer to “it worked in curl”.
- Trailers are their own tab for gRPC.
- Preview renders HTML, images and PDFs. HTML goes into an iframe with
sandbox=""— nothing granted — plus an optional “Block external resources” policy. PDF support is probed rather than assumed, and is expected to be unavailable on Linux’s WebKitGTK webview. - Find (
⌘F), a filter bar, a run-to-run Diff, and Save as example (an example is kept on the endpoint’s overlay, so it is committable and reviewable).
Timeline numbers exist only for buffered HTTP. gRPC, WebSocket and a completed SSE stream carry no timings, and the pane says so instead of showing zeros.
Streaming is the exception, and it says so
Section titled “Streaming is the exception, and it says so”An SSE or otherwise streaming send evaluates no request tests, runs no drift check, and cannot
run a post hook — there is no single buffered response for any of them to read. The pane
names exactly what it skipped for that request rather than quietly reporting a clean run.