# Every request, explained end to end.

> The try-it console in depth: schema-driven forms, parameter serialization, request imports and exports, history with replay, network insights.
>
> Source: https://apiglow.dev/docs/try-it/index.md


Every operation page carries a panel that sends real `fetch` requests from the reader's browser, measures their duration, and renders the response. This guide is the reference for that panel. The [feature page](/features/workbench/) makes the case, and [Try-it & CORS](/docs/cors/) covers why a correct request can still be blocked.

## The form comes from the schema

Each declared type gets a matching field. Where no form makes sense, the raw JSON body is the escape hatch.

| Declared schema | Field |
|---|---|
| Primitive (string, number, …) | Text input |
| `enum` up to 7 values (a `oneOf` of constants counts as one), `boolean` | Select |
| `enum` beyond 7 values | Filterable combobox, free text still allowed |
| Array of primitives | Repeatable rows |
| `prefixItems` tuple (3.1) | One fixed slot per position |
| Object with declared properties | One field per property |
| Free-form object (`additionalProperties`) | Key + value rows |
| `format: binary` | File input |
| Composite of objects (`oneOf`, …) | Structured editor; a discriminated `oneOf` gets a variant selector |

Fields stay text-typed even for numbers and dates. A native number or date input would refuse a `{{var}}`, and a variable has to remain typable everywhere. Values are coerced to their declared type at send time, and the placeholder shows the declared example when one exists.

In a discriminated `oneOf` body, switching variants removes the other variant's keys, and the discriminator property is filled in automatically and left read-only.

The body's kind follows the selected media type:

- JSON gets a text editor plus structured fields.
- `multipart/*` gets one field per property, with file inputs for binary parts.
- urlencoded gets the same fields, as text.
- A binary media type gets a file picker.
- Text-family types, XML included, get a text editor. An XML body starts pre-filled with a generated sample document.

One degradation to know: a `format: binary` property under urlencoded becomes a text field, because percent-encoded form values cannot carry a file. Paste the encoded value instead.

Body validation is minimal on purpose: well-formed JSON, plus the presence of top-level `required` fields, checked after `{{var}}` interpolation. The form helps you build the request rather than gate it.

## One request, two editors

The request is editable from the central doc column and from the panel, and the two always show the same request. Edit a field in either, and the other follows.

The media type is a single choice shared by both, since it decides which editor the body gets.

In a JSON body, a `{{var}}` may sit unquoted (`"petId": {{petId}}`), so the interpolated value lands as a number rather than a string. A missing variable blocks the send with a visible signal, and the literal is never sent. Typing `{{` autocompletes from the variables in scope, each with its provenance. Credentials follow their own conventions — see [Auth & onboarding](/docs/auth/).

## Parameter serialization

`style` and `explode` are resolved with their spec defaults — `form` in query and cookie, `simple` in path and header — and applied on send. Exploded query arrays repeat the pair (`?tags=cat&tags=dog`), non-exploded forms join on the style's delimiter, `deepObject` brackets each property (`?owner[city]=Lyon`), and `label`/`matrix` path styles add their prefixes.

Three declarations change what leaves the browser, and each gets a visible badge in the doc:

- `allowReserved` — reserved characters pass through unencoded. A space becomes `%20`, never `+`.
- `allowEmptyValue` — `?verbose=` is a value in itself, sent through an explicit toggle. A blank field still means "don't send".
- `in: cookie` parameters — edited like any other, folded into one `Cookie` header. Browsers drop a `Cookie` header set by a script, and the panel says so under the fields. The header still reaches the cURL export and the snippets, which is usually the point.

## Snippets in ten languages

Every request renders as a live snippet in cURL, `fetch`, Node, Python, PHP, Ruby, Java, C#, Go and HTTPie. The snippet follows what you type, so it is the request that would actually be sent. The selected language is remembered across operations.

## Import: cURL, Postman, HAR

The import dialog, in the header toolbar, turns a request written elsewhere into a pre-filled try-it. It never sends and never writes anything on its own.

- **cURL** — paste the command. The parser understands `-X`, `-H`, the `-d`/`--data*` family, `--data-urlencode`, `-F`, `-u`, `--url`, `-G` and attached short arguments (`-XPOST`). Unknown options are listed as ignored rather than guessed at.
- **Postman Collection v2.1** — the folder tree is flattened, path variables are substituted, and `raw`/`urlencoded`/`formdata`/`graphql` bodies plus `basic`/`bearer`/`apikey` auth come through. Collection variables are reported rather than turned into environments.
- **HAR** — one entry per recorded request. Recorded cookies are dropped, with a warning.

Format detection reads the content, not the file extension. Matching against the schema strips known server prefixes and scores operations by literal path segments matched. Equal scores are presented as an ambiguity for you to resolve, and anything unimportable is listed before you commit.

A matched credential becomes the conventional `auth.X` variable in the session's run scope: sensitive, redacted in history, and never written into your stored environments.

## Export: four formats, redacted by default

Any request — from the panel or from history — exports as:

- a copyable cURL command, multi-line, with a "substitute variables" toggle
- a Postman v2.1 collection, which Insomnia imports natively
- Markdown ready for a GitHub issue: request, response, environment, timing
- HAR 1.2

Sensitive values are redacted by default in every format, and can be included explicitly. A secret carried in the URL is masked even when URL encoding changes it — a base64 API key reads `%2B`, `%2F` and `%3D` there. A per-request share link exists too: it re-templates sensitive values back into `{{var}}` before encoding, so a pasted link carries no token.

One HAR detail: `httpVersion` carries the protocol the transfer snapshot actually observed — `http/1.1`, `h2`, `h3` — never a hardcoded guess. When the protocol cannot be known, such as a cross-origin response without `Timing-Allow-Origin` or an entry archived before the field existed, the field is left empty.

## History: replay, reload, bounded

Every sent request writes an IndexedDB entry: the resolved request, the response, the duration, the environment, and the list of sensitive values used — which is what makes redaction on display and export possible.

The list filters by endpoint, environment, status and free text. Each entry offers two actions: **replay as-is**, which sends again unchanged, and **reload into the try-it**, which lets you edit first. Above the response panel, a run selector lists past calls for the operation on screen; selecting one shows its archived response without re-sending.

The same history feeds two read-only views in the doc itself: a recent-calls strip at the bottom of each operation page (time, status, duration, environment), and a most-used card on the overview, ranked by the reader's own call counts.

Retention is `history.maxEntries` (default 500) and `history.maxAgeDays` (default 30), whichever is reached first, purged on write and stated in the dialog. Bodies beyond 256 KB are truncated with a visible flag.

File uploads never reach storage: only the file's name, size and type are kept. Replay is therefore disabled on such an entry, while reload stays available — that is where you pick the file again.

## When it fails, and when it succeeds

A failed send is diagnosed in the panel: offline, mixed content, CORS, or unreachable host. The diagnosis is phrased as "most likely", because the browser hides the details from the page.

A successful response gets its headers read back to you: rate limits, `Retry-After`, `Deprecation` and `Sunset`, `Link` pagination, `ETag` and `Last-Modified` validators, correlation ids, plus protocol, compression and cache facts.

Two of those insights are buttons rather than labels. A `Link` header with `next`/`prev` relations gets a follow button that loads the neighbouring page into the try-it. A response carrying validators gets a conditional replay: the same request re-sent with `If-None-Match`/`If-Modified-Since`, expecting a `304`. GET and HEAD only.

Cross-origin, two response headers unlock the full material. Without `Access-Control-Expose-Headers` the browser reveals only a safelist, and the panel says the list may be incomplete. Without `Timing-Allow-Origin` transfer sizes read zero and the compression and cache chips stay hidden. Both are covered in [Try-it & CORS](/docs/cors/).

What the browser refuses to send is named rather than failing in silence. A header no page may set — `Origin`, `Content-Length`, any `Sec-` header, the full list of the Fetch standard — is named under the headers as soon as the request carries it, and a TRACE operation says browsers refuse the method. A body the document declares on a GET or HEAD is shown with a note and left out of the send. All of it stays in the cURL command, which is how you send it. A request to `http://localhost` or `127.0.0.1` is never called mixed content: browsers allow those from an https page.

## Three details worth knowing

- **An in-flight request is cancelable.** A Cancel control sits next to Send while one is out. An abort is announced as information rather than as a network failure, and writes no history entry.
- **Server variables are filled in.** `https://{region}.api.example.com` sends to the variable's default rather than to a literal `{region}`, and an environment created from that server keeps `{{region}}` as one of its variables — change it once and every request moves.
- **The most specific server wins.** An operation that declares its own `servers` keeps it; an environment's base URL replaces the root server only. The base URL displayed is the one the send will hit.

