Skip to content

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.

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-authoredSpec-derived
Lives inA collection YAML you commitGenerated on each spec resolve
Survives a spec changeUntouchedRegenerated, with your edits re-applied as an overlay
Edited byYou, freelyYou, 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.

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.

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.

The full list, with types, is in Collection file format → Request. The ones worth knowing by name:

  • query is 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.
  • body is text, a multipart form (rows of text and file parts), or a single raw file.
  • auth is a reference to an auth provider by name, plus optionally which sender to send as — or a modest inline credential. A credential value can never appear here; see below.
  • prerunChain names 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.
  • tests are assertions evaluated against this request’s own response.
  • settings are transport settings for this one call — the most specific layer of the layered send settings below.
  • scripts are the pre-request and post-response hooks.
  • useCookieJar opts a single request out of the shared cookie jar.

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.

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:

  1. the request’s own settings;
  2. the collection’s defaults.settings;
  3. the API provider’s settings;
  4. 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.

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.

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.