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)
| Option | Description |
|---|---|
method | GET, POST, PUT, PATCH, DELETE, ... |
url | Request URL. Emitted as uri in the Logic Apps JSON. |
queries | Query parameters. They replace any query string already in url, as in the cloud. |
headers | Header map. |
cookie | Sent as the Cookie header. |
body | Request body (see Request encoding). |
authentication | Authentication block, the same shape Logic Apps uses (see Authentication). |
retryPolicy | Retry 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-Typeheader 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:
| Type | Local behaviour |
|---|---|
Basic | Sent as Authorization: Basic <base64(username:password)>. |
Raw | value 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. A408,429or5xxanswer is retried up to 4 times at exponential intervals (7.5 s scale, at least 5 s and at most 45 s per wait). ARetry-Afterheader on the answer sets the wait instead. type: 'none': never retry.type: 'fixed': retrycounttimes, waitingintervaleach time (defaultPT20S).type: 'exponential': retrycounttimes; the wait doubles frominterval, bounded byminimumIntervalandmaximumInterval.
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
| Answer | Action status | Outputs | actions('X')?['code'] |
|---|---|---|---|
| Status below 400 | Succeeded | { statusCode, headers, body } | The .NET status name: OK, Created, NoContent, ... |
| Status 400 or above | Failed | { 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 parse | Failed | none | BadRequest, message "Http request failed: the content was not a valid JSON..." |
| Host name that does not resolve | Failed | none | UnresolvableHostName |
| Other connection failures | Failed | none | BadRequest, 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/jsonand*+jsonbodies are parsed.text/*bodies (and bodies without a content type) are strings.- Any other type,
application/xmlincluded, is binary content:{ "$content-type": "...", "$content": "<base64>" }. - An empty body leaves the
bodykey out, soctx.body('X')is null. - Header names .NET knows are written its way (
Content-Type,Cache-Control), repeated headers are joined with,, andContent-Lengthis 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