Skip to Content
SDK & PluginsHTTP Requests

HTTP Requests

ctx.http is a thin wrapper around ky  — a popular, well-tested HTTP client for Node.js. All ky APIs are available (retry, timeout, hooks, searchParams, json, etc.). Glubean adds automatic tracing, metrics, and schema validation on top.

Three differences from vanilla ky:

  • Leading slashes work with prefixUrlctx.http.get("/users") just works. Ky normally rejects this ; Glubean strips the leading / automatically.
  • Non-2xx responses don’t throw — ky defaults to throwHttpErrors: true, but in a verification context you need to assert on 4xx/5xx responses, not catch exceptions. Glubean sets throwHttpErrors: false so you always get a response object back. Use ctx.expect(res).toHaveStatus(404) to verify error status codes.
  • Empty searchParams don’t pollute URLs — passing searchParams: {} in ky appends a bare ? to the URL. Glubean removes empty searchParams automatically so your traces stay clean.

Every request automatically produces traces and metrics — no extra setup needed.

Tired of repeating URLs and headers? Use configure() to create a shared HTTP client with prefixUrl, auth headers, and environment-specific settings — then just write ctx.http.get("/users") in your tests.

Basic usage

import { test } from "@glubean/sdk"; export const getUser = test("get-user", async (ctx) => { const baseUrl = ctx.vars.require("BASE_URL"); const users = await ctx.http.get(`${baseUrl}/users`).json(); const created = await ctx.http.post(`${baseUrl}/users`, { json: { name: "Alice" }, }).json(); });

Create a scoped client

When multiple requests share a base URL or headers, use .extend():

export const userFlow = test("user-flow", async (ctx) => { const api = ctx.http.extend({ prefixUrl: ctx.vars.require("BASE_URL"), headers: { Authorization: `Bearer ${ctx.secrets.require("TOKEN")}`, }, }); await api.get("users/1").json(); await api.get("users/1/settings").json(); });

Request options

OptionTypeDescription
jsonobjectJSON body (auto-serialized)
bodyFormData | stringRaw body
searchParamsRecord<string, string>Query string
headersRecord<string, string>Request headers
timeoutnumber | falseTimeout in ms (default 10000, false to disable)
retrynumber | objectRetry count or { limit, statusCodes, methods }
throwHttpErrorsbooleanThrow on 4xx/5xx (default false)
schemaHttpSchemaOptionsPer-call Zod validation for request/response body, query, and headers. See Inline schema validation below.

Inline schema validation

Pass schema on any call to validate request and response payloads against Zod (or any safeParse / parse-compatible) schemas. Violations are recorded as assertion failures without disrupting the rest of the test.

import { z } from "zod"; const BodySchema = z.object({ name: z.string(), email: z.string().email() }); const ResponseSchema = z.object({ id: z.string(), createdAt: z.string() }); await ctx.http.post(url, { json: { name: "Alice", email: "alice@example.com" }, headers: { "X-Tenant-Id": "t1" }, schema: { request: BodySchema, response: ResponseSchema, query: z.object({ version: z.string() }), requestHeaders: z.object({ "X-Tenant-Id": z.string() }), responseHeaders: z.object({ "content-type": z.string() }), }, }).json();

Per-entry options:

KeyValidates
requestThe json body before sending
responseThe parsed body when .json() is called
queryThe searchParams before sending
requestHeadersThe per-call headers (not merged client-level defaults)
responseHeadersThe response headers on the final attempt

Every entry accepts either a bare schema or { schema, severity: "error" | "warn" | "fatal" }. Default severity is "error".

schema: { query: { schema: QuerySchema, severity: "warn" }, // logs but doesn't fail }

Headers are normalized to Record<string, string> before validation — Headers instances and string[][] forms both work.

For structured, file-level API specifications covering every case of an endpoint (instead of inline per-call validation), use contract.http() instead.

Response methods

const res = await ctx.http.get(url); await res.json<T>(); // Parse JSON body await res.text(); // Plain text await res.blob(); // Binary blob await res.arrayBuffer(); // Raw buffer

Retries

For flaky environments, you can retry selected status codes:

await ctx.http.get("https://api.example.com/status", { retry: { limit: 3, statusCodes: [429, 502, 503, 504], }, });

Auto-instrumentation

Every ctx.http request automatically records:

  • An API trace via ctx.trace() (method, URL, status, duration)
  • A metric http_duration_ms via ctx.metric() (with method and path tags)

No manual instrumentation needed. Results appear in the Result Viewer and Cloud dashboard.

Next

Last updated on