Skip to main content

HTTP Connector

The HTTP action (ctx.http()) sends a raw HTTP request. It needs no connection reference and no token flag: locally it is always available, and when deployed it compiles to the Logic Apps Http action. The local engine builds the request and shapes the response the way the cloud does, so expressions that read the response work the same in both places.

Usage​

@Action()
async run(ctx: FlowContext) {
await ctx.http('CallApi', {
method: 'GET',
url: 'https://api.contoso.com/orders',
queries: { status: 'open' },
headers: { Accept: 'application/json' },
});

await ctx.compose('FirstOrder', ctx.body('CallApi')?.['value']?.[0]);
await ctx.compose('Status', ctx.outputs('CallApi')?.['statusCode']);
}

The action's outputs are { statusCode, headers, body }. ctx.body('CallApi') reads the response body and ctx.outputs('CallApi') the whole object (status code and headers included). See Outputs and failures for the general rules.

Request options (HttpInputs)​

OptionDescription
methodGET, POST, PUT, PATCH, DELETE, ...
urlRequest URL. Emitted as uri in the Logic Apps JSON.
queriesQuery parameters. They replace any query string already in url, as in the cloud.
headersHeader map.
cookieSent as the Cookie header.
bodyRequest body (see Request encoding).
authenticationAuthentication block, the same shape Logic Apps uses (see Authentication).
retryPolicyRetry behaviour (see Retry policy).

Request encoding​

  • A string body is sent as text/plain; charset=utf-8.
  • Binary content ({ "$content-type": "...", "$content": "<base64>" }, for example a file read by another action) is sent as its bytes, with its own content type.
  • Any other body (object, array, number) is sent as JSON, application/json; charset=utf-8.
  • A Content-Type header set on the action always wins over these defaults.

When run from the CLI, the VS Code extension or the MCP server, no Accept header is added unless the action sets one, as in the cloud. Redirects are not followed: a 3xx answer is the action's outcome.

Authentication​

await ctx.http('CallApi', {
method: 'GET',
url: 'https://api.contoso.com/data',
authentication: { type: 'Raw', value: 'Bearer <token>' },
});

await ctx.http('CallLegacyApi', {
method: 'GET',
url: 'https://legacy.contoso.com/data',
authentication: { type: 'Basic', username: 'svc-flow', password: ctx.parameters('ApiPassword') },
});

authentication takes a type plus the fields that type needs (tenant, audience, clientId, secret, username, password, value, ...). It is always written into the compiled Logic Apps JSON as is.

Local runs apply two types:

TypeLocal behaviour
BasicSent as Authorization: Basic <base64(username:password)>.
Rawvalue is sent as the Authorization header.
None (or no authentication)Nothing added.
Any other type (ActiveDirectoryOAuth, ClientCertificate, ManagedServiceIdentity, ...)The action fails with code AuthenticationNotSupportedLocally. Use Raw or an Authorization header with a token for local runs, or mock the action.

In the action's recorded inputs (actions('CallApi')?['inputs'], the trace, the debugger), the secret fields (password, value, secret, pfx, clientSecret) are shown as *sanitized*.

Retry policy​

await ctx.http('CallApi', {
method: 'GET',
url: 'https://api.contoso.com/data',
retryPolicy: { type: 'exponential', count: 4, interval: 'PT10S', minimumInterval: 'PT5S', maximumInterval: 'PT1M' },
});
  • No retryPolicy (the default): the cloud's default policy. A 408, 429 or 5xx answer is retried up to 4 times at exponential intervals (7.5 s scale, at least 5 s and at most 45 s per wait). A Retry-After header on the answer sets the wait instead.
  • type: 'none': never retry.
  • type: 'fixed': retry count times, waiting interval each time (default PT20S).
  • type: 'exponential': retry count times; the wait doubles from interval, bounded by minimumInterval and maximumInterval.

Only 408, 429 and 5xx answers are retried, whatever the policy. Intervals are ISO 8601 durations between PT5S and P1D, and count is 1 to 90; Power Automate refuses to save anything else.

The local engine honours the policy, including the default one, so a local run against an endpoint that keeps answering 503 waits about a minute and a half (7.5 + 15 + 30 + 45 seconds) before the action fails. Set retryPolicy: { type: 'none' } on calls you expect to fail while testing.

You can also set the policy with a @retryPolicy {...} JSDoc tag on the call; the tag wins over the retryPolicy input.

Status codes and failures​

AnswerAction statusOutputsactions('X')?['code']
Status below 400Succeeded{ statusCode, headers, body }The .NET status name: OK, Created, NoContent, ...
Status 400 or aboveFailed{ statusCode, headers, body } (still readable in a catch scope)The .NET status name: NotFound, InternalServerError, ... A status .NET has no name for is the number as text, such as '429'
JSON content type with a body that does not parseFailednoneBadRequest, message "Http request failed: the content was not a valid JSON..."
Host name that does not resolveFailednoneUnresolvableHostName
Other connection failuresFailednoneBadRequest, message "Http request failed: ..."

A failed HTTP call carries no error object, only its outputs. Read ctx.outputs('X')?.['statusCode'] and ctx.body('X') in the catch scope.

Asynchronous pattern (202 + Location)​

When the answer is 202 Accepted with a Location header, the action polls the Location URL with GET (waiting for Retry-After when given, otherwise 10 seconds) until the answer is no longer 202, and that answer is the outcome. To turn polling off, add the DisableAsyncPattern operation option:

/** @operationOptions "DisableAsyncPattern" */
await ctx.http('StartJob', { method: 'POST', url: 'https://api.contoso.com/jobs' });

Response shaping​

  • application/json and *+json bodies are parsed.
  • text/* bodies (and bodies without a content type) are strings.
  • Any other type, application/xml included, is binary content: { "$content-type": "...", "$content": "<base64>" }.
  • An empty body leaves the body key out, so ctx.body('X') is null.
  • Header names .NET knows are written its way (Content-Type, Cache-Control), repeated headers are joined with ,, and Content-Length is always the length of the body as read.

Running locally​

flowforger run flow.ff.ts

No token is needed. To run the rest of a flow without calling the endpoint, mock the action (--mock CallApi=outputs.json), or use --live to allow only the calls you name; see Local testing.

Package​

@flowforger/connectors-http