# Flexible HTTPS outcalls

**URL:** <https://forum.dfinity.org/t/flexible-https-outcalls/75694>\
**Category:** Developers\
**Created:** [September 18, 2026, 11:54am UTC](https://forum.dfinity.org/t/flexible-https-outcalls/75694 "2026-09-18T11:54:42Z")\
**Posts on this page:** 1\
**Showing post:** 1

<div class="post-metadata">

**Author:** ![eichhorl](https://avatars.discourse-cdn.com/v4/letter/e/b4bc9f/32.png) [@eichhorl](https://forum.dfinity.org/u/eichhorl)\
**Post date:** [September 18, 2026, 11:54am UTC](https://forum.dfinity.org/t/flexible-https-outcalls/75694/1 "2026-09-18T11:54:42Z")

</div>

**TL;DR** : DFINITY is announcing `flexible_http_request`, a new management canister method in which a committee of nodes performs the same HTTPS request, and the canister receives their **individual responses** instead of one the subnet agreed on. This makes non-deterministic APIs usable without relying on complex transform functions. The new method lets a caller choose the cost, latency and trust trade-off per call, and reports failures as a typed error.

## Background

Each subnet’s management canister offers the functionality to make HTTPS requests via two modes of the `http_request` method:

A **fully replicated** outcall has every node perform the request and requires a supermajority of them to agree on the response byte for byte. By reaching such an agreement, the result is trustworthy without having to trust any single node. However, it also means anything non-deterministic breaks the call. For instance, this could be a timestamp in the body, a per-request ID, a live price that ticks between requests, or the output of a language model. The usual workaround is a `transform` function that strips the varying parts, but this breaks cases in which the **varying part** is the one of interest.

A **non-replicated** outcall drops the agreement requirement entirely. It is the right tool for a non-idempotent request such as sending an email, but it does so by having one node perform the call, and that node has to be fully trusted.

`flexible_http_request` represents a compromise between the two. The caller chooses how many nodes perform the request and how many of their responses are needed. The **individual answers** reach the canister rather than being reduced to one. A call that cannot meet what was asked for returns a **typed error** , allowing different failure cases to be handled programmatically.

## How it works

A flexible outcall is configured with three counts:

```candid
replication : opt record {
  min_responses : nat32; // the fewest responses a successful outcall may carry
  max_responses : nat32; // the most the caller is willing to receive
  total_requests : nat32; // how many nodes issue the HTTP request
}

```

They must satisfy `0 <= min_responses <= max_responses <= total_requests` and `1 <= total_requests <= N`, where `N` is the number of nodes on the subnet, available from the new `ic0.subnet_self_node_count` system API. Omitting `replication` defaults to `min_responses = floor(2 / 3 * N) + 1`, `max_responses = N` and `total_requests = N`, which asks every node and implies an honest majority of responses.

`min_responses` determines when the call returns, and what its fault tolerance should be: setting it below `total_requests` means the outcall still succeeds when a committee node is down or slow. A successful result therefore carries between `min_responses` and `max_responses` responses, and how many is not known in advance.

Reconciling the responses is the canister’s job. Some approaches that fit different response shapes could be the following:

- **Median, or a trimmed mean** , over a numeric field. Tolerates a minority of outliers and stale feeds, which could be relevant for fetching price information from several sources.
- **Majority vote** over a normalized form of the response: hash the response after normalization, then take the most frequent hash. This is the fully replicated guarantee rebuilt in the canister, except that the canister chooses the replication.
- **Agreement on a single field** , ignoring differences elsewhere. Two price quotes can disagree on timestamp and request ID and still be a usable pair, i.e. as long as the price matches to two decimals.
- **First usable response** , when one live answer is enough but `min_responses` is greater than one to improve fault tolerance.

Two properties of the delivered set constrain those choices. The responses do not identify the node that produced them and their order is unspecified, so a strategy cannot weigh, prefer or exclude particular nodes. Additionally, the selection is size-biased: candidates are considered smallest-response-first, so when not all of them fit, it is the smallest responses that are delivered, which matters if response size correlates with content.

Each node runs the `transform` function on its own response, if one is set. It is no longer there to force agreement, but it is still the right place to drop headers or fields that are not needed, because response size drives most of the cost.

Setting `min_responses = max_responses = 0` gives a fire-and-forget outcall: it returns an empty success as soon as any committee member responds, and therefore reserves no cycles for response delivery. In this case there is no guarantee whether any call actually succeeded or failed.

Note that `PUT`, `DELETE` and `PATCH` are accepted only when `min_responses == max_responses == total_requests`. This is to prevent unexpected race conditions between multiple outcalls made by the same canister. `GET`, `HEAD` and `POST` are unrestricted.

## What comes back

The result is a variant, differentiating a success from potential error cases:

```candid
variant {
  ok : vec http_request_result;
  err : record {
    global_error : opt variant { timeout; out_of_cycles; responses_too_large; too_many_rejects };
    node_details : vec flexible_http_node_detail;
    message : text;
  };
}

```

**A failed HTTPS outcall is still a successful call.** When the requested replication cannot be met, the management canister replies with `err` instead of rejecting. The failure shows up in the returned value, not in the call’s own result. Error handling that only checks whether the _call_ succeeded will miss it entirely. Only problems detected before the HTTP requests are dispatched come back as rejects: invalid arguments, replication counts that violate the constraints, or too few attached cycles to cover the base fee.

The four global errors:

- `timeout`: fewer than `min_responses` successful responses were collected within the system-defined timeout of one minute.
- `too_many_rejects`: more than `total_requests - min_responses` nodes returned rejects, so `min_responses` successful responses can no longer be collected. For instance, a node rejects when it cannot reach the server, when the response or the transform output exceeds the size limit it enforces, when the transform traps, when its own time limit expires, or when it exhausts its share of the attached cycles.
- `responses_too_large`: the smallest combination of responses that would have to be delivered together does not fit the block space available for outcall responses.
- `out_of_cycles`: what the nodes left unspent no longer covers delivering any result the call could still produce.

`node_details` gives per-node visibility, and which nodes appear depends on the error: `timeout` carries none, `too_many_rejects` lists the rejecting nodes it selected, and `responses_too_large` and `out_of_cycles` list every node whose response the system has seen. It is deliberately not a contract. It is not guaranteed to list every node. The diagnostic `code` strings are not a stable enumeration. The per-node resource `report` is not populated yet: every field is optional, and today all of them are absent. Use `node_details` to debug, not to drive control flow.

## Limitations

**The block limit applies to a combination of responses, not to each one.** A block currently has 2 MiB (2\_097\_152 bytes) for HTTPS outcall responses. If the smallest responses that would have to be delivered together exceed this limit, a `responses_too_large` error is delivered instead. Note that one large response among many small ones is fine as long as the combination that has to be delivered fits. For that reason, a successful outcall may deliver fewer than `max_responses`: the selection shrinks until it fits the block limit.

Note that this is a different limit from the cap on a single response, which is `max_response_bytes` (default 2\_000\_000 bytes, decimal), enforced per node.

**Canisters only.** `flexible_http_request` cannot be called by ingress messages, only by canisters.

**Pricing is version 2 only.** Flexible outcalls are always priced with the new [pay-as-you-go model](https://forum.dfinity.org/t/pay-as-you-go-pricing-for-https-outcalls/75696). Two details are specific to flexible calls: the withheld budget is split between the `total_requests` nodes rather than across the whole subnet. A node that exhausts its own share of the budget produces a reject, which counts towards `too_many_rejects`.

## How to use it

In Rust, the `ic-cdk-management-canister` crate exposes a builder that computes and attaches the cycles:

```rust
use ic_cdk_management_canister::{
    FlexibleHttpRequest, FlexibleHttpRequestResult, ReplicationCounts,
};

// ask 3 nodes, accept any 2 or 3 answers
let result = FlexibleHttpRequest::new("https://example.com/price")
    .with_replication(ReplicationCounts {
        min_responses: 2,
        max_responses: 3,
        total_requests: 3,
    })
    .with_max_response_bytes(4_000)
    .with_expected_roundtrip_time_ms(300)
    .send()
    .await?;

match result {
    FlexibleHttpRequestResult::Ok(responses) => {
        // between min_responses and max_responses of them
    }
    FlexibleHttpRequestResult::Err(err) => {
        // branch on err.global_error; node_details is for diagnostics only
    }
}

```

Documentation:

- HTTPS outcalls [skill](https://skills.internetcomputer.org/skills/https-outcalls/)
- `flexible_http_request` in the [interface specification](https://docs.internetcomputer.org/references/ic-interface-spec/management-canister/#ic-flexible_http_request)
- HTTPS outcalls [concepts](https://docs.internetcomputer.org/concepts/https-outcalls) and [guide](https://docs.internetcomputer.org/guides/backends/https-outcalls/)
- Flexible outcalls [example](https://github.com/dfinity/examples/tree/master/rust/send_http_flexible)
- Pay-as-you-go pricing [forum post](https://forum.dfinity.org/t/pay-as-you-go-pricing-for-https-outcalls/75696)

## Future work

The per-node resource report is defined but not yet populated, so today a failed outcall reports which way it failed and which nodes were involved, but not necessarily what those nodes consumed. Filling it in is planned, which will make `node_details` useful for diagnosing a failure rather than just describing it.

The block space reserved for HTTPS outcalls is currently limited. This is because responses are carried in blocks in full. However, there is a known path to raising this limit substantially, which is replacing response content with hashes. Until then, the sum of responses must fit into a size limit of a single block.

Additionally, Motoko support for flexible outcalls is planned to arrive in a future release.

---

_[View the full topic](https://forum.dfinity.org/t/flexible-https-outcalls/75694)._
