Skip to content

feat: Flexible canister HTTP outcalls and pay-as-you-go pricing - #254

Merged
eichhorl merged 27 commits into
mainfrom
oggy/flexible-http-outcalls
Sep 17, 2026
Merged

eichhorl merged 27 commits into
mainfrom
oggy/flexible-http-outcalls

Conversation

@mraszyk

@mraszyk mraszyk commented May 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Documents the two new HTTPS outcall features: flexible_http_request and pay-as-you-go pricing (version 2).

Interface specification

  • New flexible_http_request management canister method. A committee of nodes return their individual HTTP responses instead of the subnet reaching consensus on one. Takes an optional replication argument (min_responses, max_responses, total_requests), and returns either the responses or a structured error carrying a global error code and per-node resource reports.
  • New optional pricing_version field on http_request (1 or 2). Version 1 is now deprecated; version 2 prices the resources a call consumes rather than max_response_bytes.
  • New ic0.cost_http_request_v2 and ic0.subnet_self_node_count System APIs.
  • Non-replicated outcalls are no longer described as experimental.
  • public/references/ic.did and the interface-spec changelog updated to match.

Concepts, guide and reference

  • concepts/https-outcalls.md: the three outcall modes, and what each pricing version charges for.
  • guides/backends/https-outcalls.mdx: a mode comparison table, a flexible request section with code, and a cycle costs section covering both versions and the with_expected_* reservation setters.
  • references/cycle-costs.md: the version 2 formulas, plus a separate "What to attach" section.
  • guides/security/https-outcalls.md and dos-prevention.md: flexible mode's weaker integrity guarantee.

Pins

Structural decisions

  • cycle-costs.md separates what a call is charged from what to attach. ic0.cost_http_request_v2 deliberately quotes more than a call settles at: it floors the response size at MAX_CANISTER_HTTP_REJECT_BYTES, scales the fully-replicated consensus fee, and reserves for the most expensive result the call could still produce. Publishing the settle formula as the amount to attach would under-fund calls, so the two are documented separately. The charged formulas are validated against the upstream pricing unit tests.
  • ic-cdk-management-canister gets its own watched entry rather than riding on ic-cdk's pin. It versions independently within the same repo, and the name field in upstream.json exists for this case (the recipes repo uses it for five entries). scripts/check-upstream-releases.mjs derives a distinct slug, issue and label from it.
  • The Rust and Motoko tabs show different pricing versions. The Rust HttpRequest builder always selects version 2; Motoko's Call.httpRequest still attaches the version 1 cost. That is a packaging lag rather than a language difference, and the prose says so. The flexible request section is Rust only for the same reason.

Verification

  • npm run build passes: 210 pages, no errors, with every snippet= region resolving at the new examples pin.
  • The pricing figures quoted in the guide were read from a probe canister calling the replica's own ic0.cost_http_request_v2, rather than computed from the published formulas.
  • Flexible outcalls confirmed live: the feature flag flipped to Enabled on 2026-09-02, and both replica versions currently running across all 42 mainnet subnets contain that commit.

@github-actions github-actions Bot added the interface-spec Changes to the IC interface specification label May 13, 2026
pull Bot pushed a commit to mikeyhodl/ic that referenced this pull request Aug 18, 2026
Implement a new system API function allowing a canister to determine the
size of its current subnet. This will be helpful for flexible outcalls,
in which a canister can individually specify how many replicas should
perform a request, and how many of them should produce a response. This
only makes sense within the context of the own subnet size.

Specified here (draft):
dfinity/developer-docs#254

---------

Co-authored-by: Pierugo Pace <pierugo.pace@dfinity.org>
@marc0olo

Copy link
Copy Markdown
Member

Flagging a downstream dependency, since this PR touches only the spec files (ic-interface-spec/*, ic.did).

Three non-spec pages encode the pricing-v1 assumptions this PR deprecates, and they will need updating in lockstep when v2 becomes the default. Listing them so they are not missed:

Page Line Claim that breaks under v2
concepts/https-outcalls.md 75 "max_response_bytes: … This is what you're charged for, not the actual response size." This PR states v2 ignores max_response_bytes.
concepts/https-outcalls.md 77 "omit … charges roughly 20.85 billion cycles"
concepts/https-outcalls.md 70 names ic0.cost_http_request, deprecated here in favour of _v2
guides/backends/https-outcalls.mdx 142 "costs are based on max_response_bytes, not the actual response size" plus the same 20.85B figure
guides/backends/https-outcalls.mdx 144 names ic0.cost_http_request
references/cycle-costs.md 119-133 the whole base_fee/size_fee formula and the 13/34-node table are v1-only; v2 prices on raw response size, transform instructions, transformed size, and roundtrip time, so it needs a separate section rather than an edit

Line numbers are against main plus #352, which is currently editing the first two of these.

For whoever picks this up: as of dfinity/ic@339d220a83 the replica still gates v2 off for the standard method, so the existing pages are correct today and nothing needs changing before this lands:

// rs/types/management_canister_types/src/http.rs
pub const DEFAULT_HTTP_OUTCALLS_PRICING_VERSION: u32 = PRICING_VERSION_LEGACY;
pub const ALLOWED_HTTP_OUTCALLS_PRICING_VERSIONS: &[u32] = &[PRICING_VERSION_LEGACY];

The cycle-costs.md change is the substantive one: v1 and v2 have different cost shapes, not just different constants, so that page likely needs both documented side by side while v1 remains the default-but-deprecated version.

marc0olo added a commit that referenced this pull request Aug 25, 2026
… default cost (#352)

Closes #351.

The issue reported two problems with how the HTTPS outcalls pages
describe `max_response_bytes`. Both are confirmed against the [interface
spec](https://github.com/dfinity/developer-docs/blob/main/docs/references/ic-interface-spec/management-canister.md)
and fixed here, along with several further defects found while fixing
them.

## What the issue reported

**1. Wrong byte figure.** `2,097,152` → `2,000,000`. The spec: *"the
default value of `2MB` (`2,000,000B`) is used as the limit."* Confirmed
in the replica as `MAX_CANISTER_HTTP_RESPONSE_BYTES = 2_000_000`.

**2. The limit is not body-scoped.** The spec defines the measured
quantity as *"the total number of bytes representing the names and
values of HTTP headers and the HTTP body."* Both pages now say headers
plus body.

**3. The transform bound** (raised in the issue body).
`max_response_bytes` is enforced **twice**: on the raw response as it
arrives, and again on the transform's output. A transform cannot rescue
a response that already exceeded the cap, because the first check runs
before the transform does; it only keeps the transform's own output
within the cap. Stated in the guide's transform section, where a reader
would form the "I'll strip headers to fit" plan, and in the concepts
Limitations bullet.

## Additional defects found

**4. The default-size cost was wrong on both pages.** Both said omitting
`max_response_bytes` costs *~21.5 billion cycles*. The formula already
published on `references/cycle-costs.md` gives:

```
49_140_000 + 10_400 * 2_000_000 = 20_849_140_000   (~20.85 billion)
```

Corrected to ~20.85 billion in both places. 21.5B matches neither the
decimal nor the binary reading, so it appears independently wrong rather
than downstream of the byte-figure error.

**5. `references/cycle-costs.md` said `max_response_bytes` defaults to
"2 MiB".** Same decimal-vs-binary error, on the page the other two link
to for exact pricing. Corrected, with the resulting cycle figure added.

**6. Both pages claimed a single ~30 second timeout, and the guide said
the call *traps*.** There are two timeouts and neither traps:

| Trigger | Reject | Message |
|---|---|---|
| Remote server silent for 30s | `SysFatal` | `Timeout expired` |
| Subnet produces no response within 60s | `SysTransient` | `Canister
http request timed out` |

Telling readers to expect a trap points them at the wrong error
handling.

**7. The Motoko cycle guidance was stale.** Both pages said *"In Motoko,
cycles must be attached explicitly with `await (with cycles = ...)`"*.
The `ic` package provides `Call.httpRequest`, which computes the exact
cost via `ic0.cost_http_request` and attaches it, matching the Rust
wrapper. The pages now also explain why a hand-picked margin is
counterproductive: attached cycles are held for the duration of the
call, so a margin caps outcall concurrency.

## Submodule bump

Item 7 could not be fixed in prose alone, because the embedded Motoko
snippets hardcoded `with cycles = 230_949_972_000`: correcting the text
would have left the page contradicting its own code. That was fixed
upstream first in dfinity/examples#1477, merged as `b4fe175`.

`.sources/examples` is bumped `d4ea422` → `b4fe175` here, so the
snippets now render `await Call.httpRequest(request)` and code and prose
agree.

The old pin predated the examples restructure, so all six `snippet=`
paths moved and are updated:

```
send_http_{get,post}/src/send_http_{get,post}_backend/main.mo    -> send_http_{get,post}/backend/main.mo
send_http_{get,post}/src/send_http_{get,post}_backend/src/lib.rs -> send_http_{get,post}/backend/src/lib.rs
```

Region names (`transform`, `get_request`, `post_request`) are unchanged.

Per `.agents/submodule-bumping.md`: `guides/backends/https-outcalls.mdx`
is the only page using `CodeExample`, so no other page is affected by
the moves, and `examples` tracks master so it carries no
`.sources/VERSIONS` entry.

## Scope

Kept deliberately tight per `CONTRIBUTING.md`: `concepts/` stays
explanatory, and the spec's header limits (≤64 headers, ≤8 KiB per name
or value, ≤48 KiB total) are **not** added. The issue marked them
optional, and enumerating them duplicates content that belongs in the
interface spec and the `https-outcalls` skill.

## Verification

- `npm run validate`: no errors in the touched files.
- `build_and_deploy`: passing against the new submodule. This is the
meaningful check for the bump, since `remark-snippet` treats a missing
file or region as a hard build error.
- Before pushing the bump, all six file+region pairs were confirmed to
resolve at `b4fe175` by replicating the plugin's extraction logic.

## Related

- dfinity/icskills#361 carries the same corrections in the
`https-outcalls` skill, including the reject-message set these pages do
not enumerate.
- #254 (flexible outcalls) will invalidate the v1
pricing assumptions on these pages when it lands: `max_response_bytes`
is *ignored* under pricing v2, and `ic0.cost_http_request` is
deprecated. Flagged there with the specific lines, including that
`references/cycle-costs.md` needs both cost models rather than an edit
in place. As of `dfinity/ic@339d220a83` v2 is still gated off, so the
pages are correct today.
pull Bot pushed a commit to bit-cook/ic that referenced this pull request Sep 2, 2026
…icing (dfinity#11399)

Flipping the `FLEXIBLE_HTTP_REQUESTS_FEATURE` flag does multiple things:
1. Allows fully- and non-replicated outcalls to select the
"pay-as-you-go" pricing version when making the request
2. Allows flexible outcalls (with pay-as-you-go pricing by default) to
be made on normal paying subnets
3. Switches flexible outcalls on free and system subnets from legacy
pricing to pay-as-you-go pricing (they continue to be free just like
before).

Draft spec PR here: dfinity/developer-docs#254

In progress system tests PR: dfinity#11327
@eichhorl eichhorl changed the title feat: flexible canister http outcalls feat: Flexible canister HTTP outcalls and pay-as-you-go pricing Sep 9, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A broken anchor, future-dated release entry, and edge-case documentation errors need correction.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Updates the management canister interface and developer documentation for flexible HTTPS outcalls and pay-as-you-go pricing.

Changes:

  • Adds flexible_http_request and configurable replication.
  • Documents pricing version 2 and new System APIs.
  • Updates related guides, security guidance, and references.

Verified

  • Reviewed all changed files, internal links, API consistency, and relevant implementation details.
File summaries
File Description
public/references/ic.did Adds the new Candid types and method.
docs/references/management-canister.md Summarizes the new APIs and pricing.
docs/references/ic-interface-spec/management-canister.md Specifies flexible outcall behavior.
docs/references/ic-interface-spec/changelog.md Records the interface changes.
docs/references/ic-interface-spec/canister-interface.md Documents the new System APIs.
docs/references/ic-interface-spec/abstract-behavior.md Defines their abstract behavior.
docs/references/cycle-costs.md Adds version 2 pricing formulas.
docs/languages/rust/index.md Corrects the HTTPS outcalls link.
docs/guides/security/https-outcalls.md Updates outcall security guidance.
docs/guides/security/dos-prevention.md Adds budget-limiting guidance.
docs/guides/backends/https-outcalls.mdx Covers all three outcall modes.
docs/concepts/index.md Broadens the outcalls description.
docs/concepts/https-outcalls.md Explains flexible mode and pricing.
Review details
  • Files reviewed: 13/13 changed files
  • Comments generated: 4
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread docs/guides/backends/https-outcalls.mdx Outdated
Comment thread docs/guides/security/dos-prevention.md Outdated
Comment thread docs/references/cycle-costs.md Outdated
Comment thread docs/references/ic-interface-spec/canister-interface.md Outdated
@eichhorl
eichhorl marked this pull request as ready for review September 15, 2026 07:42
@eichhorl
eichhorl requested review from a team as code owners September 15, 2026 07:42
@mraszyk

mraszyk commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Review: Flexible canister HTTP outcalls and pay-as-you-go pricing

Checked against dfinity/ic at 0db6e4a4ce (master, 2026-09-16) and the released dfinity/cdk-rs and caffeinelabs/mops-ic packages.

Must fix

  • Rust wrapper statements are out of date since yesterday: ic-cdk-management-canister 0.2.0 (crates.io, 2026-09-16) removed the free functions http_request and cost_http_request and added the HttpRequest builder (always pricing version 2), the FlexibleHttpRequest builder, cost_http_request_v2, and re-exports of all flexible_http_request types. ic-cdk 0.20.3 (2026-09-15) added api::cost_http_request_v2 and api::subnet_self_node_count. Three passages now contradict the published crates: concepts/https-outcalls.md ("Neither exposes pricing_version or flexible_http_request yet, so a canister that wants version 2 or flexible mode must build the management canister call itself"), and guides/backends/https-outcalls.mdx lines 21 and 175 (ic_cdk_management_canister::http_request attaches the version-1 cost). The Motoko half is still true: mops-ic 4.2.0 only has Call.httpRequest over Prim.costHttpRequest. Either describe the 0.2.0 builders, or scope the sentences to the crate versions the snippets actually pin (.sources/examples/rust/send_http_get/backend/Cargo.toml pins ic-cdk-management-canister = "0.1.1", whose http_request still exists). Note .sources/upstream.json pins ic-cdk at 0.20.2; the watcher will surface 0.20.3, but this PR should not land a claim that is already false.
  • PR description: "version 2 ignores max_response_bytes" is wrong, and the spec text in this PR says so itself. Under version 2 the field still caps the response on every node (rs/https_outcalls/pricing/src/payg.rs, max_response_size.min(...)) and it sizes the withheld reservation via max_usage_fee. Say "no longer prices the call by max_response_bytes". Also fix "experiemental", and list the non-spec pages this PR rewrites (concepts page, backend guide, two security guides, cycle-costs.md, management-canister.md reference) plus the Rust wrapper rename, since the description currently reads as spec-only.

Suggestions

  • Flexible delivery fee omits the per-response overhead: cycle-costs.md gives delivery_fee = n * (10 * n + 600) * response_bytes. For flexible calls the implementation charges n * (10 * n + 600) * sum_i (181 + response_bytes_i) (FLEXIBLE_RESPONSE_SIZE_OVERHEAD = 181 in rs/https_outcalls/pricing/src/fees.rs). Worth a "(plus 181 bytes per delivered response in flexible mode)" so the formula reproduces the charge.
  • Upstream ic.did lags this PR: rs/types/management_canister_types/tests/ic.did in dfinity/ic has pricing_version but not flexible_http_request or its result and error types, even though the Rust types exist there. The Candid added here matches those Rust types field for field, so nothing to change in this PR, but a follow-up in dfinity/ic would keep the two copies from drifting.

I'd ignore the following:

  • Silent fallback also covers unsupported replicas: on a replica whose flexible_http_requests flag is off, pricing_version = 2 is filtered out and the call is priced at version 1 without an error (ALLOWED_HTTP_OUTCALLS_PRICING_VERSIONS in canister_http.rs). The flag has been enabled since release-2026-09-03 (commit c14df8a5b4), so mainnet is fine, but a one-line note in the spec would explain the behavior a developer sees on an older local replica.

Verified

  • Candid types and pricing_version : opt nat32 against rs/types/management_canister_types/src/http.rs; FlexibleHttpRequestErr, HttpRequestResourceReport, ResourceUsage, the four global_error variants, and Ic00Method::FlexibleHttpRequest all exist and match.
  • Default pricing version 1; unrecognized values fall back to 1 with no error (generate_from_args). Legacy http_request_fee takes no replication argument, so version 1 charges non-replicated and replicated calls the same.
  • Replication defaults floor(2N/3)+1, N, N; the bounds 0 <= min <= max <= total, 1 <= total <= N; PUT/DELETE/PATCH allowed only for non-replicated or min == max == total, with the reject text matching the spec wording. Ingress calls rejected; "This API is not enabled on this subnet" when the subnet feature is off.
  • Version 2 formulas: base fee constants (1_000_000, 50/byte, 140_000·n + 800·n², or 90_000·n + (2_000·n + 100_000)·min), usage fee (50/byte, 300/ms, instructions/13, 50·n gossip for non-replicated and flexible), consensus fee n(10n+600), extra-response fee (2_000n + 100_000)·n·(K − min). The 13-node and 34-node table values reproduce from these constants.
  • Admission: rejected when payment is below the base fee; per_replica_allowance = min(payment − base, max_usage_fee) / node_count; the excess above that is left in request.payment and refunded with the response; node budgets refunded asynchronously; retained contexts discarded after DELIVERED_CANISTER_HTTP_REQUEST_CONTEXT_TIMEOUT = 60 s. Per-node limits shrink with the remaining budget (payg.rs), and a node over budget rejects.
  • ic0.cost_http_request_v2: signature (I, I, I) -> (), params record as documented, blob capped at 2 × 144 bytes, Candid skipping quota of 1 (so reserved payloads must be null), outcall_type absent means fully replicated, flexible: null uses the endpoint defaults, counts not validated. Maxima: MAX_RESPONSE_TIME 60 s, MAX_INSTRUCTIONS_PER_QUERY_MESSAGE 5 B, CANDID_OVERHEAD_RESERVE_BYTES 1024.
  • ic0.subnet_self_node_count : () -> i32, traps only in start, available in all other contexts including queries and cleanup, matching the * marker. Env.subnet_size exists in the abstract behavior, so the new rule is well formed.
  • Flexible result semantics in payload_builder/utils.rs: result delivered once min_responses OK responses exist; smallest responses selected first; MAX_CANISTER_HTTP_PAYLOAD_SIZE = 2 MiB on encoded size plus proof overhead; too_many_rejects when rejects exceed total − min and it lists at least total − min + 1 rejecting nodes; responses_too_large and out_of_cycles list all seen nodes with code ok/reject and a bytes or cycles message; timeout after 60 s with empty node_details; report always empty; fire-and-forget (min = max = 0) returns ok [] on the first share regardless of its outcome.
  • Internal links and anchors resolve (#be-aware-of-http-request-and-response-sizes, #outcall-modes, #ic-flexible_http_request, #flexible_http_request, #https-outcalls, guides/backends/https-outcalls.md). No em-dashes in added lines. Copilot's four inline comments are addressed in the current head.
  • Not verified: npm run build (no npm on the review machine).

@eichhorl

Copy link
Copy Markdown
Contributor

@mraszyk Addressed the blockers and suggestions. Also submitted an examples PR dfinity/examples#1485, which needs to be merged before we can repin them here.

@mraszyk

mraszyk commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up review: Flexible canister HTTP outcalls and pay-as-you-go pricing

Covers commits 5cdc7ed and 0ad22b0, checked against ic-cdk-management-canister 0.2.0 (cdk-rs main) and dfinity/examples at 81f756a7 (PR #1485, merged).

Fixed

  • Rust wrapper statements now match 0.2.0: HttpRequest and FlexibleHttpRequest always price with version 2 through ic0.cost_http_request_v2, and the Motoko Call.httpRequest remains version 1. The builder semantics described in the guide are correct: unset expectations resolve to 60 000 ms, max_response_bytes, max_response_bytes + 1024, and the 5 billion query limit (Reservation::resolve in ic-cdk-management-canister/src/lib.rs).
  • The quoted reservation figures reproduce. For the GET example (request bytes 82, max_response_bytes 3 000, 13 nodes) the fee constants give 5 329 580 997 cycles at the worst case and 135 581 004 with a 10 s round trip and 1 M transform instructions, so "about 5.33 billion" and "about 136 million" are right.
  • 181-byte per-response overhead is now in the flexible delivery fee formula in cycle-costs.md.
  • upstream.json: ic-cdk bumped to 0.20.3 and a separate ic-cdk-management-canister 0.2.0 entry added. scripts/check-upstream-releases.mjs keys entries by repo plus name, so the second dfinity/cdk-rs entry does not collide.
  • No em-dashes or -- in the new prose; anchors unchanged and still resolve.

Still needs work

  • .sources/examples is not repinned. It still points at 42c474dd, which predates #1485. At that pin the rendered GET snippet calls the 0.1.1 http_request(&request) and has no with_expected_* lines, so the new sentence "The two with_expected_* calls size the cycles reservation" and the paragraph under Version 2 describe code the reader cannot see. Repin to 81f756a7 or later and update .sources/VERSIONS. The get_request, transform, and post_request region markers survive in the new commit, so the snippets should resolve; run npm run build after the bump (I could not run it here).
  • PR description is unchanged. It still says version 2 "ignores max_response_bytes", still has "experiemental", and still reads as spec-only while the PR rewrites the concepts page, the backend guide, two security guides, cycle-costs.md, management-canister.md, and now upstream.json.
  • Minor precision, optional: the guide says the two byte expectations default to max_response_bytes. The raw one does; the transformed one defaults to max_response_bytes + 1024, and without a transform the instruction expectation defaults to 0 rather than the query limit. One clause would make the "leave at their default" advice exact.
  • Optional: with #1485 merged, the "Flexible request" section could quote send_http_flexible/backend/src/lib.rs#flexible_request and #reconcile via CodeExample once the submodule is repinned; it is currently the only mode in the guide without code. The spec note about pricing_version = 2 silently falling back to 1 on a replica without the feature was not added; it was a suggestion, not a blocker.

@eichhorl

Copy link
Copy Markdown
Contributor

dfinity/examples#1485 was merged so I repinned the examples and linked to the new flexible one

@mraszyk

mraszyk commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up review: Flexible canister HTTP outcalls and pay-as-you-go pricing

Covers commit 5c9b0d1 and the rewritten description.

Fixed

  • .sources/examples repinned to 1a9a0249, the merge commit of feat(https-outcalls): adopt the 0.2 HttpRequest builder, add a flexible outcall example examples#1485. All eight snippet= regions in the guide resolve at that pin, including the two new ones (flexible_request, reconcile). .sources/VERSIONS correctly stays unchanged: it lists only release-pinned submodules, and examples tracks a branch.
  • Flexible request section now has code, and the prose matches the example: a committee of five capped by subnet_self_node_count(), majority min_responses, no transform, tally of distinct bodies with per-response status checks.
  • PR description now states version 2 "prices the resources a call consumes rather than max_response_bytes", fixes the typo, lists every non-spec page and both pin changes, and documents the structural decisions. The rollout claim checks out: the 42 mainnet subnets run 7360f8f3 (15) or dea4a9af (27), and both have FLEXIBLE_HTTP_REQUESTS_FEATURE = Enabled.
  • No em-dashes, no headings inside <TabItem>, no new links to verify.

Still needs work

Both are minor accuracy edits, neither blocks merging.

  • docs/guides/backends/https-outcalls.mdx, Version 2 section, last paragraph. The builder's defaults are max_response_bytes for the raw expectation but max_response_bytes + 1024 for the transformed one, and with no transform the instruction expectation defaults to zero (Reservation::resolve in ic-cdk-management-canister). The download cap a node applies also follows its remaining budget, not the byte expectation, so "is still downloaded" is only conditionally true. Replace the last two sentences with:

    The two byte expectations are the ones to leave at their defaults: max_response_bytes for the raw response, and max_response_bytes plus 1 KiB of Candid overhead for the transformed one. Declaring less is a bet on the server. The download cap each node applies follows its remaining budget rather than the byte expectation, so a larger response may still be downloaded, and the nodes may then be unable to fund delivering it. The call then fails at delivery, after the request has already been made. Without a transform there is no instruction expectation to narrow: the builder reserves zero for it.

  • Same file, Flexible request section, paragraph after the first snippet. "The committee size comes from subnet_self_node_count()" reads as if the whole subnet is used, while the example caps it at five. Replace the first sentence with:

    The committee is five nodes, or the whole subnet if it is smaller: total_requests cannot exceed the number of nodes, which subnet_self_node_count() reports.

Optional, for the "Limitations and pitfalls" section of the same guide: a replica built before 2026-09-02 (an older local network or PocketIC server) treats pricing_version = 2 as version 1 without an error and rejects flexible_http_request with "This API is not enabled on this subnet". One sentence there would save a developer on a stale toolchain some confusion. Not added in this PR so far, and fine to leave out.

mraszyk and others added 11 commits September 16, 2026 15:12
…size precisely

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…, version 1 lead-in, trimmed changelog entry

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ode_details phrasing

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e refund clause

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…the call

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ery fee

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…erever it is stated

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mraszyk

mraszyk commented Sep 16, 2026

Copy link
Copy Markdown
Contributor Author

@marc0olo Thanks for the list. All of it is addressed in the current head, along the lines you suggested: the version 1 statements stay, scoped to version 1, and version 2 is documented next to them rather than by editing them.

  • concepts/https-outcalls.md: the "what you're charged for" and 20.85 billion statements now sit under a Version 1 lead-in, followed by a Version 2 paragraph. The cost APIs paragraph names both ic0.cost_http_request (version 1, deprecated) and ic0.cost_http_request_v2.
  • guides/backends/https-outcalls.mdx: the cycle costs section has a comparison table and separate Version 1 and Version 2 subsections; the max_response_bytes and 20.85 billion claims live under Version 1.
  • references/cycle-costs.md: the version 1 formula and table are kept under Version 1 (default, deprecated), with the version 2 formulas, a separate table, and a "What to attach" section alongside.

On the timing note: the replica gate is open now. The flexible_http_requests flag flipped to Enabled in dfinity/ic@c14df8a5b4 (2026-09-02) and both replica versions currently running across all 42 mainnet subnets contain it.

…t, Motoko pricing_version warning

- cost_http_request_v2: the 2MiB / min_responses figure for
  transformed_response_bytes covers delivery, not a node's own
  dissemination charge
- http_request v2: name the aggregate CANISTER_REJECT ("Out of cycles")
  delivered when the unspent allowances cannot fund any result
- flexible_http_request: the budget-derived size cap applies to the
  download; the transform output is held to max_response_bytes and its
  dissemination charged afterwards
- concepts and guide: do not set pricing_version = 2 on a request passed
  to the Motoko Call.httpRequest wrapper, which attaches the version 1
  amount

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@marc0olo

Copy link
Copy Markdown
Member

@eichhorl @mraszyk is this ready to be merged now from your side? really nice that you guys also added an example which is referenced in the docs now, thanks! just asking because I don't see any approval of core-protocol or interface-spec

@eichhorl

Copy link
Copy Markdown
Contributor

@marc0olo yes, it's ready to be merged now

@eichhorl
eichhorl merged commit 707596d into main Sep 17, 2026
11 checks passed
@eichhorl
eichhorl deleted the oggy/flexible-http-outcalls branch September 17, 2026 08:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

interface-spec Changes to the IC interface specification

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants