HTTP API

Complete HTTP client API for load testing

HTTP Methods

http.get(url, [options])

Performs a GET request

http.post(url, body, [options])

Performs a POST request

http.put(url, body, [options])

Performs a PUT request

http.patch(url, body, [options])

Performs a PATCH request

http.del(url, [options])

Performs a DELETE request

http.head(url, [options])

Performs a HEAD request

http.options(url, [options])

Performs an OPTIONS request

http.request({method, url, body, headers, ...})

Generic request builder

http.batch(requests, [onProgress])

Execute multiple requests in parallel, optional progress callback

http.graphql(url, query, [variables], [options])

Send GraphQL query (convenience wrapper for POST with JSON body)

Request Options

headers: Object - Custom HTTP headers

name: String - Custom metric tag for aggregating dynamic URLs

tags: Object - Custom tags for filtering metrics

timeout: String - Request timeout ("10s", "500ms"). Default: "60s"

retry: Number - Retry attempts on failure (default: 0)

retryDelay: Number - Initial retry delay in ms (default: 100, doubles each retry)

retryOn: Function - Custom retry predicate (res) => boolean

retryDelayFn: Function - Custom delay calculator (count) => ms

followRedirects: Boolean - Whether to follow redirects (default: true)

maxRedirects: Number - Maximum redirects to follow (default: 5)

# Retry with Exponential Backoff

const res = http.request({
  method: 'GET',
  url: 'https://api.example.com/flaky-endpoint',
  retry: 3,        // Retry up to 3 times
  retryDelay: 200  // Start with 200ms, then 400ms, 800ms
});

// Custom retry conditions - retry on rate limit or server errors
const res = http.request({
  method: 'GET',
  url: 'https://api.example.com/data',
  retry: 5,
  retryOn: (r) => r.status === 429 || r.status >= 500,
  retryDelayFn: (count) => count * 1000  // Linear: 1s, 2s, 3s...
});

Response Object

status: Number - HTTP status code (200, 404, etc). 0 for network/timeout errors

statusText: String - HTTP status text ("OK", "Not Found", "Network Error")

body: String - Response body as string. null/empty when response_sink enabled

headers: Object - Response headers

proto: String - Protocol version: "h1" (HTTP/1.x), "h2" (HTTP/2), "h3" (HTTP/3)

cookies: Object - Cookies from Set-Cookie headers

timings: Object - Request timing breakdown (see below)

error: String - Error type: TIMEOUT, DNS, TLS, CONNECT, RESET, NETWORK

errorCode: String - Error code: ETIMEDOUT, ENOTFOUND, ECERT, ECONNREFUSED, ECONNRESET, EPIPE, ENETWORK

Response Helpers

res.json()

Parse body as JSON and return object

res.html(selector)

Query HTML with CSS selector (returns array of matches)

res.hasHeader(name, [value])

Check if header exists (optionally matches value)

res.bodyContains(substring)

Check if body contains string

res.bodyMatches(regex)

Check if body matches regex pattern

res.isJson()

Check if content-type is application/json

res.matchesSchema(schema)

Validate JSON body matches type schema ({key: 'string'|'number'|'boolean'|'array'|'object'})

Timings Object

duration - Total request time (ms)

blocked - Time waiting for connection (ms)

connecting - TCP connection time (ms)

tls_handshaking - TLS handshake time (ms)

sending - Time sending request (ms)

waiting - Time waiting for response (ms)

receiving - Time receiving response (ms)

Utility Functions

http.url(baseUrl, params)

Build URL with query parameters

http.formEncode(obj)

Encode object as x-www-form-urlencoded

http.basicAuth(user, pass)

Generate Basic auth header value

http.bearerToken(token)

Generate Bearer auth header value

http.setDefaults(options)

Set global defaults for all requests

http.file(path, [filename], [contentType])

Create file for multipart upload

new FormData()

Create multipart form data

# URL and Form Helpers

// Build URL with query parameters
const url = http.url('https://api.example.com/search', { q: 'test', page: 1 });
// Returns: https://api.example.com/search?q=test&page=1

// Form URL encoding
const body = http.formEncode({ username: 'user', password: 'pass' });
http.post('/login', body, {
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
});

Parallel Requests

const responses = http.batch([
  { method: 'GET', url: 'https://api.example.com/users' },
  { method: 'GET', url: 'https://api.example.com/posts' },
  { method: 'POST', url: 'https://api.example.com/log', body: JSON.stringify({ event: 'test' }) }
]);

// responses is array in same order
check(responses[0], { 'users loaded': (r) => r.status === 200 });
check(responses[1], { 'posts loaded': (r) => r.status === 200 });

// Track progress with callback
const results = http.batch(requests, (completed, total) => {
  print(`Progress: ${completed}/${total}`);
});

GraphQL

// Simple GraphQL query
const res = http.graphql('https://api.example.com/graphql', `
  query GetUser($id: ID!) {
    user(id: $id) { name email }
  }
`, { id: '123' });

check(res, { 'has data': (r) => r.json().data !== undefined });

// With custom headers
const res2 = http.graphql('https://api.example.com/graphql',
  'mutation { createUser(name: "Test") { id } }',
  null,
  { headers: { 'Authorization': 'Bearer token' } }
);

Cookies are automatically managed per worker. Cookies received via Set-Cookie headers are stored and sent on subsequent requests to matching domains.

# Automatic Cookie Management

// First request - server sets session cookie
http.post('https://api.example.com/login', { user: 'test', pass: 'secret' });

// Subsequent requests automatically include the session cookie
http.get('https://api.example.com/dashboard');  // Cookie header sent automatically

Cookie Jar API

http.cookieJar()

Get the current worker's cookie jar

jar.set(url, name, value, [options])

Set a cookie

jar.get(url, name)

Get a cookie object

jar.delete(url, name)

Delete a cookie

jar.clear()

Clear all cookies

jar.cookiesForUrl(url)

Get all cookies for a URL (array)

const jar = http.cookieJar();

// Set a cookie manually
jar.set('https://api.example.com', 'session', 'abc123', {
  domain: 'api.example.com',
  path: '/',
  secure: true,
  httpOnly: true,
});

// Get a specific cookie
const session = jar.get('https://api.example.com', 'session');
print(session.value);  // "abc123"

// Read cookies from response
const res = http.post('https://api.example.com/login', credentials);
const sessionCookie = res.cookies['session_id'];
if (sessionCookie) {
  print(`Session: ${sessionCookie.value}, expires: ${sessionCookie.expires}`);
}

// Get all cookies for a URL
const cookies = jar.cookiesForUrl('https://api.example.com');
// Returns array: [{name: 'session', value: 'abc123', ...}, ...]

Cookie Properties

name - Cookie name

value - Cookie value

domain - Cookie domain

path - Cookie path

expires - Expiration timestamp (Unix epoch)

maxAge - Max age in seconds

secure - HTTPS only

httpOnly - HTTP only (no JS access)

sameSite - SameSite policy (Strict/Lax/None)

Checks & Assertions

Validate responses with the check() function. Failed checks are tracked in metrics.

const res = http.get('https://api.example.com/users');

check(res, {
  'status is 200': (r) => r.status === 200,
  'response time < 500ms': (r) => r.timings.duration < 500,
  'body contains users': (r) => r.bodyContains('users'),
  'body matches pattern': (r) => r.bodyMatches(/user-\d+/),
  'has content-type': (r) => r.hasHeader('content-type'),
  'content-type is json': (r) => r.hasHeader('content-type', 'application/json'),
  'response is valid json': (r) => r.isJson(),
});

Custom Failure Messages

Check functions can return a string instead of a boolean to provide custom failure messages:

check(res, {
  'status is 200': (r) => r.status === 200 || `Expected 200, got ${r.status}`,
  'has valid data': (r) => {
    const data = r.json();
    if (!data.items) return 'Missing items array';
    if (data.items.length === 0) return 'Items array is empty';
    return true;
  },
});

// Return values:
// - true = check passed
// - false = check failed (generic message)
// - string = check failed with custom message

Request Grouping & URL Naming

Group related requests for better metrics organization.

Automatic URL Naming

Fusillade automatically normalizes URLs for metrics by replacing numeric and UUID segments with :id:

// These requests are automatically grouped under the same metric:
http.get('/users/123');           // Metric: GET /users/:id
http.get('/users/456');           // Metric: GET /users/:id
http.get('/orders/abc-def-123');  // Metric: GET /orders/:id (UUID detected)

// Override with explicit name if needed:
http.get('/users/123', { name: 'GetSpecificUser' });

Manual Grouping

// Use name option to group dynamic URLs
http.get(`https://api.example.com/users/${userId}`, {
  name: 'GET /users/:id'
});

// Use tags for filtering metrics
http.get('https://api.example.com/products', {
  tags: { type: 'catalog', region: 'us-east' }
});

Request/Response Hooks

// Before request hook
http.addHook('beforeRequest', (request) => {
  request.headers['X-Request-ID'] = utils.uuid();
  print(`Sending ${request.method} to ${request.url}`);
});

// After response hook
http.addHook('afterResponse', (response) => {
  if (response.status >= 400) {
    print(`Error: ${response.status} - ${response.body}`);
  }
});

// Clear all hooks
http.clearHooks();

File Upload

// Simple file upload
const file = http.file('./data/image.png', 'image.png', 'image/png');
http.post('https://api.example.com/upload', JSON.stringify({ file }));

// FormData for complex multipart
const form = new FormData();
form.append('file', http.file('./data/document.pdf'));
form.append('name', 'My Document');
form.append('tags', JSON.stringify(['important', 'work']));

http.post('https://api.example.com/documents', form.body(), {
  headers: { 'Content-Type': form.contentType() }
});