feat: Flexible canister HTTP outcalls and pay-as-you-go pricing - #254
Conversation
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>
|
Flagging a downstream dependency, since this PR touches only the spec files ( 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:
Line numbers are against For whoever picks this up: as of // 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 |
… 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.
…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
There was a problem hiding this comment.
🟡 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_requestand 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.
Review: Flexible canister HTTP outcalls and pay-as-you-go pricingChecked against Must fix
Suggestions
I'd ignore the following:
Verified
|
|
@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. |
Follow-up review: Flexible canister HTTP outcalls and pay-as-you-go pricingCovers commits Fixed
Still needs work
|
|
dfinity/examples#1485 was merged so I repinned the examples and linked to the new flexible one |
Follow-up review: Flexible canister HTTP outcalls and pay-as-you-go pricingCovers commit Fixed
Still needs workBoth are minor accuracy edits, neither blocks merging.
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 |
…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>
|
@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.
On the timing note: the replica gate is open now. The |
…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 yes, it's ready to be merged now |
Summary
Documents the two new HTTPS outcall features:
flexible_http_requestand pay-as-you-go pricing (version2).Interface specification
flexible_http_requestmanagement canister method. A committee of nodes return their individual HTTP responses instead of the subnet reaching consensus on one. Takes an optionalreplicationargument (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.pricing_versionfield onhttp_request(1or2). Version1is now deprecated; version2prices the resources a call consumes rather thanmax_response_bytes.ic0.cost_http_request_v2andic0.subnet_self_node_countSystem APIs.public/references/ic.didand 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 thewith_expected_*reservation setters.references/cycle-costs.md: the version2formulas, plus a separate "What to attach" section.guides/security/https-outcalls.mdanddos-prevention.md: flexible mode's weaker integrity guarantee.Pins
ic-cdk-management-canisteradded toupstream.jsonas a seconddfinity/cdk-rsentry, pinned at 0.2.0.ic-cdkbumped to 0.20.3..sources/examplesrepinned to1a9a0249, which carries the Rust outcall examples migrated to the 0.2 builder and the newsend_http_flexibleexample (feat(https-outcalls): adopt the 0.2 HttpRequest builder, add a flexible outcall example examples#1485).Structural decisions
cycle-costs.mdseparates what a call is charged from what to attach.ic0.cost_http_request_v2deliberately quotes more than a call settles at: it floors the response size atMAX_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-canistergets its own watched entry rather than riding onic-cdk's pin. It versions independently within the same repo, and thenamefield inupstream.jsonexists for this case (the recipes repo uses it for five entries).scripts/check-upstream-releases.mjsderives a distinct slug, issue and label from it.HttpRequestbuilder always selects version2; Motoko'sCall.httpRequeststill attaches the version1cost. 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 buildpasses: 210 pages, no errors, with everysnippet=region resolving at the new examples pin.ic0.cost_http_request_v2, rather than computed from the published formulas.Enabledon 2026-09-02, and both replica versions currently running across all 42 mainnet subnets contain that commit.