The API/makeApiRequest extension sends an HTTP request from inside a Virtuoso journey and returns response details such as the response body, HTTP status, response headers, and request duration.
This is useful when your test needs to call an API, validate a response, capture response headers, or measure how long the request takes before reusing the returned value in later journey steps.
Parameters:
-
methodrequired, the HTTP method as a string, for exampleGET,POST,PUT,PATCH, orDELETE; -
urlrequired, the full API endpoint URL where the request should be sent; -
bodyrequired forPOST,PUT, andPATCHrequests. This must be a valid JSON string because the source code parses it withJSON.parse(body)for these methods; -
headersoptional, request headers as a valid JSON string. If this input is blank, the extension sends an empty headers object.
Note: The body and headers inputs are parsed as JSON strings. Use valid JSON with double-quoted keys and values, for example {"content-type":"application/json"}.
How to apply this to your journey
Use the extension in a journey by calling API/makeApiRequest with the execute command. Pass each value to the matching extension input using as inputName.
Note: For GET and DELETE requests, the extension does not add the body value to the Axios request configuration. For POST, PUT, and PATCH requests, the body value is required and must be valid JSON.
To make a POST API request:
execute "API/makeApiRequest" using "POST" as method, "https://reqres.in/api/login" as url, '{"email":"peter@klaven"}' as body, '{"content-type":"application/json"}' as headers returning $responseTo make a GET API request:
execute "API/makeApiRequest" using "GET" as method, "https://reqres.in/api/users/2" as url, "" as body, '{"content-type":"application/json"}' as headers returning $responseYou can also use Virtuoso variables to make the same step reusable across different methods, URLs, request bodies, or headers:
execute "API/makeApiRequest" using "$method" as method, "$url" as url, "$body" as body, "$headers" as headers returning $responseExample setup using variables before calling the extension:
store value "POST" in $method
store value "https://reqres.in/api/login" in $url
store value '{"email":"peter@klaven"}' in $body
store value '{"content-type":"application/json"}' in $headers
execute "API/makeApiRequest" using "$method" as method, "$url" as url, "$body" as body, "$headers" as headers returning $responseThe extension returns a JSON string that includes the response data, HTTP status, response headers, and request duration:
{
"data": {},
"status": 200,
"headers": {},
"duration": "123.45 ms"
}This extension requires the following resource:
The extension should be configured as:
- Run asynchronously: Yes
- Scope: Global
Limitation: This extension depends on Axios loading from the configured external resource and on the target API being reachable from the browser context used by the Virtuoso journey. Browser CORS rules, blocked CDN access, proxy or VPN requirements, authentication failures, invalid JSON in body or headers, unsupported HTTP methods, and API rate limits can prevent the request from completing. The source code does not set an explicit Axios timeout, retry policy, or cancellation handler, so long requests rely on the browser/network behavior and Virtuoso's documented 120-second maximum execution window for async extensions. HTTP error responses are returned through done(JSON.stringify(...)) rather than doneError, so the step can still complete with an error payload; use later journey steps to assert the returned status, data, or error values. Cross-browser note: this extension uses browser-based HTTP requests through Axios and performance.now(); validate it in each browser/device or remote grid configuration used by your plan, because network handling, CORS behavior, header exposure, timing values, and platform restrictions can differ from the default browser execution.
Add the extension to your Virtuoso instance
Select the domain that matches your Virtuoso account.
View source
Last updated: 22/05/2026
Resources:
const makeRequestWithTiming = async (method, url, body, headers = '') => {
const startTime = performance.now(); // Capture the start time with high resolution
try {
const config = {
method: method.toLowerCase(),
url,
headers: headers ? JSON.parse(headers) : {},
};
// Only include the body for methods that support it
if (['post', 'put', 'patch'].includes(method.toLowerCase())) {
config.data = JSON.parse(body);
}
const response = await axios(config);
const { data, status, headers: responseHeaders } = response;
const duration = performance.now() - startTime; // Calculate the duration (request time) in milliseconds
const responseBody = {
data,
status,
headers: responseHeaders,
duration: `${duration.toFixed(2)} ms`, // Include the duration in the response with "ms" suffix
};
done(JSON.stringify(responseBody));
} catch (e) {
const duration = performance.now() - startTime; // Calculate the duration (request time) in milliseconds
const responseBody = e.response
? {
data: e.response.data,
status: e.response.status,
headers: e.response.headers,
duration: `${duration.toFixed(2)} ms`, // Include the duration in the response with "ms" suffix
}
: {
error: e.message,
duration: `${duration.toFixed(2)} ms`, // Include the duration in the response with "ms" suffix
};
console.error(e); // Log the error for debugging purposes
done(JSON.stringify(responseBody));
}
};
// Validate required parameters
if (!method) {
throw new Error('HTTP method parameter is missing');
}
if (!url) {
throw new Error('URL parameter is missing');
}
if (['POST', 'PUT', 'PATCH'].includes(method.toUpperCase()) && !body) {
throw new Error(`Body parameter is missing for ${method.toUpperCase()} request`);
}
// Execute the request
makeRequestWithTiming(method, url, body, headers);
Comments
0 comments
Please sign in to leave a comment.