ctx.api calls an HTTP endpoint from an automation. The engine is Node's global fetch and nothing else: no extra HTTP client, no cookie jar and no browser impersonation. When a task needs a real browser, ctx.page is still the surface (framework/api-types.ts).
#The verbs
ctx.api is an ApiFacade with six methods:
request(method, url, opts?)for any method.get(url, opts?).post(url, body?, opts?),put(url, body?, opts?)andpatch(url, body?, opts?).delete(url, opts?).
ApiRequestOptions carries an optional body, a query map, extra headers, a vault credential name in auth and a per-call timeoutMs. A body that is not a string is serialised as JSON and the content-type header is set to application/json unless you set one. query values are URL-encoded.
The call answers with an ApiResult: status, ok, headers and either json (parsed when the response content-type is JSON) or text. A 2xx or 3xx returns this result and never throws.
#How a status becomes an outcome
The mapping follows the platform's exception lanes, so the runner's retry policy already knows what to do:
- 2xx and 3xx come back as a result.
- 4xx throws a
BusinessExceptionwith codeAPI_HTTP_<status>, such asAPI_HTTP_404. It is not retried. - 5xx throws a
SystemExceptionwith codeAPI_HTTP_<status>, such asAPI_HTTP_503. It is retried. - A timeout throws
API_TIMEOUT, retryable. - A DNS failure, a reset or any other network error throws
API_NETWORK, retryable.
401 and 403 stay in the business lane on purpose. A wrong token is a business failure and retrying it would loop against a wall instead of ending the transaction. The default timeout is 15 seconds, overridable with TS_API_TIMEOUT_MS or a per-call timeoutMs.
#Auth comes from the vault by name
An automation never carries a token. It sets auth to a credential name and the engine resolves the record before the request leaves:
const res = await ctx.api.get(`${base}/api/customers`, { auth: 'crm-api' });
const payload = (res.json ?? { customers: [] }) as { customers?: { id?: string }[] };
ctx.log.info(`Fetched ${(payload.customers ?? []).length} customers`);
The record can carry a header (default Authorization), a valuePrefix such as Bearer and a value, or a headers map applied as it stands. A missing record refuses with API_CREDENTIAL_MISSING before any network call, so a bad name cannot reach the wire.
#What state it is in
The capability audit measured api as WORKING: loopback requests returning 200, 404 and 503, each arriving as the service's typed outcome, with no external endpoint touched. The capability has its own spec and acceptance in the clean-required gate.
#Things that catch people out
A 4xx or 5xx throws, so you do not get to inspect the failing response body through the return value. The status travels in the exception code (API_HTTP_401, API_HTTP_503).
json is set only when the response content-type is JSON. For anything else the body is in text.
Declare 'api'. The runner refuses an undeclared call with API_NOT_DECLARED and a declared call with no provider with API_UNAVAILABLE.
Redirects are followed, which is fetch's own default. This facade offers no option to turn that off.