[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6GPTHGHVG05G1SADG23ZKF4":3,"skill-same:01M6GPTHGHVG05G1SADG23ZKF4":101},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":16,"sourceHtmlUrl":17,"categories":18,"keywords":19,"publishedAt":43,"linkOnly":44,"markdownContent":45,"markdownBody":46,"frontMatter":47,"refName":52,"commitSha":53,"skillDirectory":54,"forksCount":55,"repositoryHtmlUrl":56,"repositoryHomepageUrl":6,"ownerHtmlUrl":57,"ownerBlogUrl":58,"ownerDescription":59,"updatedAt":60,"install":61,"resources":74},"01M6GPTHGHVG05G1SADG23ZKF4","https-outcalls",null,"Make HTTPS requests from canisters to external web APIs. Covers replicated, non-replicated and flexible outcalls, transform functions for consensus, both cycle pricing versions (legacy and pay-as-you-go via ic0.cost_http_request_v2), response size limits, and idempotency patterns. Use when a canister needs to call an external API, fetch data from the web, make HTTP requests, choose a pricing_version, or use flexible_http_request. Do NOT use for EVM\u002FEthereum calls — use evm-rpc instead.","jiayaoqijia\u002Fcryptoskill","jiayaoqijia","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F1453879?v=4","01M6GMYTX7Q7S504VC6E756ENR","YQ","01M6GMYV1QKJ1CC75MTTGT9PR0",81,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fchains\u002Fdfinity-official-https-outcalls\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fchains\u002Fdfinity-official-https-outcalls\u002FSKILL.md",[],[20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42],"https","https outcalls","request","limits","when","as","make","patterns","ethereum","go","data","transform","cli","api","you","pay","only","icp","rust","choose","cycle","consensus","fetch","2026-11-10 10:12:30",false,"---\nname: https-outcalls\ndescription: \"Make HTTPS requests from canisters to external web APIs. Covers replicated, non-replicated and flexible outcalls, transform functions for consensus, both cycle pricing versions (legacy and pay-as-you-go via ic0.cost_http_request_v2), response size limits, and idempotency patterns. Use when a canister needs to call an external API, fetch data from the web, make HTTP requests, choose a pricing_version, or use flexible_http_request. Do NOT use for EVM\u002FEthereum calls — use evm-rpc instead.\"\nlicense: Apache-2.0\ncompatibility: \"icp-cli >= 0.2.2; pricing version 2 and flexible outcalls need ic-cdk >= 0.20.3 with ic-cdk-management-canister >= 0.2.0 (Rust), or the ic mops package >= 6.0.0 with moc >= 2.1.0 (Motoko)\"\nmetadata:\n  title: HTTPS Outcalls\n  category: Integration\n---\n\n# HTTPS Outcalls\n\n## What This Is\n\nHTTPS outcalls allow canisters to make HTTP requests to external web services directly from on-chain code. In the default **replicated** mode, every node on the subnet makes the same request and all of them must agree on the response. A transform function strips non-deterministic fields (timestamps, request IDs, ordering) so that every replica sees an identical response and can reach consensus.\n\nTwo other modes avoid that agreement step: **non-replicated** (`is_replicated = false`, one node makes the request) and **flexible** (`flexible_http_request`, a committee of nodes return their individual responses). See [Outcall modes](#outcall-modes).\n\n## Support matrix\n\n| | Rust | Motoko |\n|---|---|---|\n| Package | `ic-cdk` **0.20.3+** with `ic-cdk-management-canister` **0.2+** | `ic` **6.x** (mops), which requires `core` **2.5.0+** and `moc` **2.1.0+** |\n| Entry point | `HttpRequest::new(url)` builder, `.send()` | `Call.httpRequest(url)` builder, `.send()` |\n| Pricing version 1 (legacy) | only by pinning `ic-cdk-management-canister` **0.1**, or calling `aaaaa-aa` directly with `ic_cdk::api::cost_http_request` | only by pinning `ic` **4.x** or **5.x**, or calling `aaaaa-aa` directly with `Prim.costHttpRequest` |\n| Pricing version 2 (pay-as-you-go) | **yes**, the builder always selects it | **yes**, the builder always selects it |\n| `flexible_http_request` | **yes**, `FlexibleHttpRequest::new(url)` | **yes**, `Call.flexibleHttpRequest(url)` |\n| `subnet_self_node_count` | `ic_cdk::api::subnet_self_node_count()` | `Prim.subnetSelfNodeCount()` (via `import Prim \"mo:⛔\"`) |\n\nRead this before writing code; what is available depends on the language and the dependency versions in the project. Both languages reach the same outcall surface, through a builder that selects version 2 and computes the cycles to attach, but only on recent dependencies. On `ic` **4.x** or **5.x** Motoko is still version 1: 4.x `HttpRequestArgs` has no `pricing_version` field at all, Candid omits the absent optional, and the replica reads it as version 1, which is exactly what that `Call.httpRequest` funds. The builder and `flexible_http_request` arrive in **6.0.0**, which requires `moc` **2.1.0** for the `costHttpRequestV2` primitive it calls.\n\nFor Rust the right code depends on which of three worlds the project is in. `ic-cdk` 0.19 has `ic_cdk::management_canister::http_request(&args)` (version 1; the module was removed in 0.20). `ic-cdk` 0.20 with `ic-cdk-management-canister` 0.1 has `ic_cdk_management_canister::http_request(&args)` (version 1). `ic-cdk` 0.20.3+ with `ic-cdk-management-canister` 0.2 has the builders, and is **version 2 only**: 0.2.0 removed the free `http_request` and the builder hard-codes `pricing_version: Some(2)` with no opt-out, so upgrading the crate *is* the migration. Rust also needs `serde_json` for JSON parsing.\n\n**Which version to write.** Version 1 is deprecated and version 2 is the direction, so *new* Rust code should use the 0.2 builders. But do **not** bump a project's pinned versions in order to migrate it as a side effect of an unrelated task: 0.2 is a breaking change (it deletes the free `http_request` and `HttpRequestArgs` gains a required field), so the upgrade is the caller's decision. Write correct code for the line the project is actually on, and tell them the upgrade exists.\n\n## Canister IDs\n\nHTTPS outcalls use the IC management canister:\n\n| Name | Canister ID | Used For |\n|------|-------------|----------|\n| Management canister | `aaaaa-aa` | The `http_request` and `flexible_http_request` management call targets |\n\nYou do not deploy anything extra. The management canister is built into every subnet.\n\n## Mistakes That Break Your Build\n\n1. **Forgetting the transform function.** In **replicated** mode, without a transform the raw HTTP response often differs between replicas (different headers, different ordering in JSON fields, timestamps). Consensus fails and the call is rejected. ALWAYS provide one there. In **non-replicated** and **flexible** mode it is optional *for consensus*, because each node's own response is delivered. Whether to set one there is a tradeoff. **No transform reserves nothing for it** (the instruction term defaults to 0, not to the query limit), which is the cheapest configuration available. Adding one defaults that term to the full query limit, so you must also declare `with_expected_transform_instructions`. What it buys back is the delivery fee, charged per byte on the **transformed** size: every byte stripped lowers the bill, so what decides it is how much there is worth stripping (bulky headers yes, a response that is already mostly payload no). The reservation floors that term at about 1 KB, so `get_cost()` understates the saving on a small response. Two non-cycle reasons can decide it anyway: in flexible mode the combined 2 MiB budget is measured after the transform, so smaller responses mean fewer get dropped, and reconciling is impossible if per-node noise makes every response distinct.\n\n2. **Not attaching cycles to the call.** On a normal Application subnet, HTTPS outcalls are not free — the calling canister must attach cycles to cover the cost, and attaching zero fails the call. Both Motoko and Rust have builders that compute and attach the required cycles automatically: in Motoko, `Call.httpRequest(url)....send()` from the `ic` mops package (`import Call \"mo:ic\u002FCall\"`); in Rust, the `HttpRequest` builder's `.send()` from `ic-cdk-management-canister` 0.2. On current versions both price version 2 with `ic0.cost_http_request_v2`; an older `ic` (4.x or 5.x) prices version 1 with `ic0.cost_http_request` instead. Both are cost-schedule aware, so the same wrapper attaches the correct amount on any subnet type. **Under version 2 the up-front check is against the base fee only**, so an under-sized attachment is *not* rejected at call time: the call runs with tighter per-node limits and can fail partway, after the remote server was already contacted. See `references\u002Fpricing-version-2.md`. On a **cloud engine** (`CloudEngine` subnet), that amount is always 0 by design; do not \"fix\" a working outcall by attaching a hardcoded non-zero fee there — see the `cloud-engine-canisters` skill.\n\n3. **Using HTTP instead of HTTPS.** The IC only supports HTTPS outcalls. Plain HTTP URLs are rejected. The target server must have a valid TLS certificate.\n\n4. **Sizing `max_response_bytes` against the expected body — the limit is not body-only.** The spec defines the size of an HTTP request or response as *the total number of bytes representing the names and values of HTTP headers and the HTTP body*. Response **headers count against `max_response_bytes`**, and a real API commonly sends 1–2 KB of response headers (a unique request id, `Date`, the rate-limit family, CDN headers) before a single byte of body — against a tight cap that is a large share of the budget. Size the cap for **headers + body as they arrive from the server**, then add margin. The failure mode is total: the call fails every time rather than returning a truncated response. The maximum is 2MB = `2_000_000` bytes (decimal, not 2^21), and the same headers-plus-body definition caps the **request** you send at `2_000_000` bytes.\n\n5. **Expecting the transform function to shrink an oversized response under the cap.** The cap is enforced **twice**: once on the raw response as it arrives from the server (headers first, then the body against what remains), and again on the transform's Candid-encoded **output**, which includes serialization overhead. Stripping headers in the transform cannot rescue a raw response that already exceeded the cap, because that first check fails before the transform ever runs. It *can* keep the transform's own output under the cap — worth doing when the transform would otherwise echo the headers back and its encoded output would exceed the limit. Both checks compare against the **same** `max_response_bytes` value, which is why a raw response that only just fits can still fail after the transform: the Candid overhead is added on top. So: size `max_response_bytes` for the raw response, and keep the transform's output small; never set a tight cap on the theory that stripping headers afterwards will make an oversized response fit.\n\n6. **Ignoring the header limits.** Independent of `max_response_bytes`, the spec caps HTTP requests and responses at **64 headers**, **8 KiB** per header name or value, and **48 KiB** for all header names and values combined. The URL must not exceed **8192** bytes. On the request side these are enforced when the replica decodes your arguments, so an over-limit request never leaves the subnet and fails with `InvalidManagementPayload` — e.g. `Deserialize error: The number of elements exceeds maximum allowed 64` — rather than with any HTTP-looking error. If you send no `user-agent` header the IC adds `user-agent: ic\u002F1.0`, and that added header does not count toward these limits.\n\n7. **Omitting `max_response_bytes`.** Under **version 1**, if you do not set it the system assumes the maximum (2MB) and charges cycles accordingly — roughly 20.85 billion cycles on a 13-node subnet. Under **version 2** it no longer sets the price, but it is still the default for the `raw_response_bytes` expectation, so omitting it reserves for 2MB: 34.0 billion cycles rather than 5.3 billion for a 4,000-byte cap. Either way, always set it to a reasonable upper bound for your expected response (see pitfall 4 for what counts toward it).\n\n8. **Non-idempotent POST requests without caution.** Because multiple replicas make the same request, a POST endpoint that is not idempotent (e.g., \"create order\") will be called N times (once per replica, typically 13 on a 13-node subnet). Use idempotency keys, or design endpoints to handle duplicate requests, or set `is_replicated = ?false` (Rust: `.non_replicated()`), which has a single node send the request and removes the rate-limit pressure entirely — at the cost of trusting that node not to observe or modify the response.\n\n9. **Not handling outcall failures.** External servers can be down, slow, or return errors. Always handle the error case. There are **two distinct timeouts**, and neither traps — both come back as rejects (in Motoko the `await` raises a catchable `Error`; in Rust the wrapper returns `Err`):\n   - The remote server does not respond within **30 seconds**: `SysFatal`, message `Timeout expired`.\n   - The subnet does not produce a response within **60 seconds**: `SysTransient`, message `Canister http request timed out`. This one is normally the retryable one; the exception is a version 2 call that is under-funded *and* missing a node's report (see `references\u002Fpricing-version-2.md`).\n\n10. **Calling localhost or private IPs.** HTTPS outcalls can only reach public internet endpoints. Localhost, 10.x.x.x, 192.168.x.x, and other private ranges are blocked.\n\n11. **Forgetting the `Host` header.** Some API endpoints require the `Host` header to be explicitly set. The IC does not automatically set this from the URL.\n\n12. **Leaving the version 2 expectations unset.** Anything you do not declare is *reserved* at its maximum: a 60-second round trip, and a transform running to the full query instruction limit. For a 4,000-byte cap that is around 5.3 billion cycles held, nearly all of it the transform reserve, against about 112 million once you declare `with_expected_transform_instructions` and `with_expected_roundtrip_time_ms`. Declare those two. Leave `raw_response_bytes` alone, because the server decides it; lower `max_response_bytes` instead if the byte terms dominate. Declare `with_expected_transformed_response_bytes` when your transform bounds its own output, which is the one case where the cap cannot be the lever. The *charge* is unaffected by any of this unless the smaller budget actually cuts the call short. `references\u002Fpricing-version-2.md` has the figures, the per-resource reasoning, and the failure modes.\n\n13. **Expecting to reach version 1 through `ic-cdk-management-canister` 0.2.** You cannot. The builder is the only path the crate offers, it hard-codes `pricing_version: Some(2)`, and there is no `with_pricing_version`. `HttpRequest::from_args` looks like the escape hatch and is not: it is the documented way to migrate an existing call site, and it **silently overwrites `pricing_version` with 2**, so args that deliberately set 1 change version when you pass them through it. To stay on version 1, pin `ic-cdk-management-canister` 0.1, or call `aaaaa-aa` directly and price with `ic_cdk::api::cost_http_request`, which is still present in 0.20.3.\n\n14. **Setting `pricing_version` by hand and funding it with the wrong cost function.** The field and the attachment have to agree. Set `pricing_version = 2` while attaching a `cost_http_request` (version 1) amount and the call is *accepted*, because the up-front check is only the base fee — it then runs on a smaller per-node allowance than version 2 intended and can fail partway. The reverse, a version 1 call funded with a `cost_http_request_v2` amount, is rejected outright with `http_request request sent with \u003CX> cycles, but \u003CY> cycles are required.`, because version 1 wants the whole `max_response_bytes` up front. Let the builder set both: in either language it pairs version 2 with `cost_http_request_v2`, and overwrites `pricing_version` even when you start from arguments you assembled yourself (`httpRequestFromArgs`, `HttpRequest::from_args`), so the field and the amount cannot drift apart. Note also that the replica *filters* an unrecognised version to version 1 with no error, so a bogus value fails as a funding mismatch rather than as a validation error.\n\n15. **Hardcoding `total_requests` for a flexible outcall.** `total_requests` must not exceed the subnet size, which differs per subnet (13 or 34 on mainnet). Derive it:\n    ```rust\n    \u002F\u002F WRONG, even when you want five nodes: 5 may exceed the subnet size.\n    let replication = ReplicationCounts { total_requests: 5, min_responses: 3, max_responses: 5 };\n\n    \u002F\u002F RIGHT: ask for five, but never more than the subnet has.\n    let total_requests = subnet_self_node_count().min(5);\n    let min_responses = total_requests \u002F 2 + 1;\n    ```\n\n    Also handle **any** count between `min_responses` and `max_responses` in the success arm — fewer than `max_responses` is a normal success, not a degraded one — and check each response's `status` separately, because no node had to agree with any other. See `references\u002Fflexible-outcalls.md`.\n\n## Outcall modes\n\nReplicated and non-replicated are selected by `is_replicated`; flexible is a separate method.\n\n| | Replicated (default) | Non-replicated (`is_replicated = false`) | Flexible (`flexible_http_request`) |\n|---|---|---|---|\n| Who sends it | All N nodes | One node chosen by the system | A committee of `total_requests` nodes |\n| What you get | One agreed response | That node's response | Between `min_responses` and `max_responses` responses |\n| Transform | Required in practice | Optional, often still worth it | Optional, often still worth it |\n| Pricing | Version 1 or 2 | Version 1 or 2 | Always version 2 |\n| Extra methods | `GET`, `HEAD`, `POST` | plus `PUT`, `DELETE`, `PATCH` | plus those when `total_requests`, `min_responses` and `max_responses` are all equal |\n| Risk | N simultaneous requests trip API rate limits | That node could observe or modify the response | Reconciling the responses is your job |\n\nUse replicated when you need the integrity guarantee consensus gives, non-replicated for rate-limited APIs and non-idempotent POSTs, and flexible when the data changes faster than nodes could ever agree on it.\n\n## Implementation\n\n### Motoko\n\nImport both the builder and the types from the `ic` mops package: `import Call \"mo:ic\u002FCall\"` and `import IC \"mo:ic\u002FTypes\"`. `Call.httpRequest(url)` starts a request and `.send()` computes and attaches the required cycles.\n\n**This is pricing version 2**, on `ic` 6.x. On `ic` 4.x or 5.x the entry point is `Call.httpRequest(args)` taking the whole record, and it prices version 1; see the support matrix.\n\n```motoko\nimport Blob \"mo:core\u002FBlob\";\nimport Nat \"mo:core\u002FNat\";\nimport Text \"mo:core\u002FText\";\nimport Call \"mo:ic\u002FCall\";\nimport IC \"mo:ic\u002FTypes\";\n\npersistent actor {\n\n  \u002F\u002F Transform function: strips headers so all replicas see the same response for consensus.\n  \u002F\u002F MUST be a `shared query` function.\n  public query func transform({\n    context : Blob;\n    response : IC.HttpRequestResult;\n  }) : async IC.HttpRequestResult {\n    {\n      response with headers = []; \u002F\u002F Strip headers -- they often contain non-deterministic values\n    };\n  };\n\n  \u002F\u002F GET request: fetch a JSON API\n  public func getIcpPriceUsd() : async Text {\n    let url = \"https:\u002F\u002Fapi.coingecko.com\u002Fapi\u002Fv3\u002Fsimple\u002Fprice?ids=internet-computer&vs_currencies=usd\";\n\n    \u002F\u002F send() computes and attaches the cycles the call may need.\n    let response = await Call.httpRequest(url)\n      \u002F\u002F Bounds the response; a larger one fails the call. The cap covers\n      \u002F\u002F headers and body together, and the transform cannot bring an\n      \u002F\u002F oversized response back under it. Under version 2 it no longer sets\n      \u002F\u002F the price, because only the bytes that arrive are charged.\n      .withMaxResponseBytes(10_000)\n      .withHeader(\"User-Agent\", \"ic-canister\")\n      .withTransform({ function = transform; context = Blob.fromArray([]) })\n      \u002F\u002F Unset expectations reserve the worst case: 60 seconds of round trip,\n      \u002F\u002F and a transform running to the full query instruction limit.\n      \u002F\u002F Declaring what the call expects holds far fewer cycles while it runs;\n      \u002F\u002F the charge is unchanged, because only what is used is billed.\n      .withExpectedRoundtripTimeMs(10_000)\n      .withExpectedTransformInstructions(1_000_000)\n      .send();\n\n    switch (Text.decodeUtf8(response.body)) {\n      case (?text) { text };\n      case (null) { \"Response is not valid UTF-8\" };\n    };\n  };\n\n  \u002F\u002F POST transform: also discards the body, because httpbin.org echoes the\n  \u002F\u002F sender's IP in \"origin\", which differs across replicas.\n  public query func transformPost({\n    context : Blob;\n    response : IC.HttpRequestResult;\n  }) : async IC.HttpRequestResult {\n    {\n      response with\n      headers = [];\n      body = Blob.fromArray([]);\n    };\n  };\n\n  \u002F\u002F POST request: send JSON data\n  public func postData(jsonPayload : Text) : async Text {\n    let url = \"https:\u002F\u002Fhttpbin.org\u002Fpost\";\n\n    \u002F\u002F send() computes and attaches the cycles the call may need.\n    let response = await Call.httpRequest(url)\n      .withMethod(#post)\n      .withMaxResponseBytes(50_000)\n      .withHeader(\"Content-Type\", \"application\u002Fjson\")\n      .withHeader(\"User-Agent\", \"ic-canister\")\n      \u002F\u002F Idempotency key: prevents duplicate processing if multiple replicas hit the endpoint\n      .withHeader(\"Idempotency-Key\", \"unique-request-id-12345\")\n      .withBody(Text.encodeUtf8(jsonPayload))\n      .withTransform({ function = transformPost; context = Blob.fromArray([]) })\n      .withExpectedRoundtripTimeMs(10_000)\n      .withExpectedTransformInstructions(1_000_000)\n      .send();\n\n    if (response.status == 200) {\n      \"POST successful (status 200)\";\n    } else {\n      \"POST failed with status \" # Nat.toText(response.status);\n    };\n  };\n};\n```\n\n### Rust\n\n```toml\n# Cargo.toml\n[package]\nname = \"https_outcalls_backend\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nic-cdk = \"0.20\"\nic-cdk-management-canister = \"0.2\"\ncandid = \"0.10\"\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\n```\n\n```rust\nuse ic_cdk_management_canister::{\n    transform_context_from_query, HttpMethod, HttpRequest, HttpRequestResult, TransformArgs,\n};\nuse ic_cdk::{query, update};\n\n\u002F\u002F\u002F Transform function: strips non-deterministic headers so all replicas agree.\n\u002F\u002F\u002F MUST be a #[query] function.\n#[query(hidden = true)]\nfn transform(args: TransformArgs) -> HttpRequestResult {\n    HttpRequestResult {\n        status: args.response.status,\n        body: args.response.body,\n        headers: vec![], \u002F\u002F Strip all headers for consensus\n        \u002F\u002F If you need specific headers, filter them here:\n        \u002F\u002F headers: args.response.headers.into_iter()\n        \u002F\u002F     .filter(|h| h.name.to_lowercase() == \"content-type\")\n        \u002F\u002F     .collect(),\n    }\n}\n\n\u002F\u002F\u002F GET request: Fetch JSON from an external API\n#[update]\nasync fn fetch_price() -> String {\n    let url = \"https:\u002F\u002Fapi.coingecko.com\u002Fapi\u002Fv3\u002Fsimple\u002Fprice?ids=internet-computer&vs_currencies=usd\";\n\n    \u002F\u002F The builder always selects pricing version 2. `.send()` prices the call\n    \u002F\u002F with ic0.cost_http_request_v2 and attaches that amount.\n    let request = HttpRequest::new(url)\n        .with_method(HttpMethod::GET)\n        \u002F\u002F Budget for response headers + body: the cap covers both, and the\n        \u002F\u002F transform cannot bring an oversized response back under it.\n        .with_max_response_bytes(10_000)\n        .with_header(\"User-Agent\", \"ic-canister\")\n        .with_transform(transform_context_from_query(\"transform\".to_string(), vec![]))\n        \u002F\u002F Unset expectations reserve the worst case: a 60s round trip and a\n        \u002F\u002F transform running to the full 5-billion-instruction query limit.\n        \u002F\u002F Declaring them holds far fewer cycles. See pitfall 12.\n        .with_expected_roundtrip_time_ms(10_000)\n        .with_expected_transform_instructions(1_000_000);\n\n    match request.send().await {\n        Ok(response) => {\n            let body = String::from_utf8(response.body)\n                .unwrap_or_else(|_| \"Invalid UTF-8 in response\".to_string());\n\n            if response.status != candid::Nat::from(200u64) {\n                return format!(\"HTTP error: status {}\", response.status);\n            }\n\n            body\n        }\n        Err(err) => {\n            format!(\"HTTP outcall failed: {:?}\", err)\n        }\n    }\n}\n\n\u002F\u002F\u002F POST transform: strips headers AND body, because httpbin.org echoes the\n\u002F\u002F\u002F sender's IP in \"origin\", which differs across replicas.\n#[query(hidden = true)]\nfn transform_post(args: TransformArgs) -> HttpRequestResult {\n    HttpRequestResult {\n        status: args.response.status,\n        body: vec![],\n        headers: vec![],\n    }\n}\n\n\u002F\u002F\u002F POST request: Send JSON data to an external API\n#[update]\nasync fn post_data(json_payload: String) -> String {\n    let url = \"https:\u002F\u002Fhttpbin.org\u002Fpost\";\n\n    let request = HttpRequest::new(url)\n        .with_method(HttpMethod::POST)\n        .with_max_response_bytes(50_000)\n        .with_header(\"Content-Type\", \"application\u002Fjson\")\n        .with_header(\"User-Agent\", \"ic-canister\")\n        \u002F\u002F Idempotency key: prevents duplicate processing across replicas\n        .with_header(\"Idempotency-Key\", \"unique-request-id-12345\")\n        .with_body(json_payload.into_bytes())\n        .with_transform(transform_context_from_query(\"transform_post\".to_string(), vec![]))\n        .with_expected_roundtrip_time_ms(10_000)\n        .with_expected_transform_instructions(1_000_000);\n    \u002F\u002F Replicated, so every node POSTs: hence the idempotency key above, and\n    \u002F\u002F `transform_post`. `.non_replicated()` would send it once instead and make\n    \u002F\u002F both unnecessary, at the cost of trusting that one node (pitfall 8).\n\n    match request.send().await {\n        Ok(response) => {\n            if response.status == candid::Nat::from(200u64) {\n                \"POST successful (status 200)\".to_string()\n            } else {\n                format!(\"POST failed with status {}\", response.status)\n            }\n        }\n        Err(err) => {\n            format!(\"HTTP outcall failed: {:?}\", err)\n        }\n    }\n}\n```\n\n### Cycle Cost Estimation\n\nThere are two pricing versions. Version 1 is the replica default and is deprecated; version 2 (\"pay-as-you-go\") charges for the resources a call consumes instead of the bytes it reserves. **The current builders in both languages are version 2 only**, so a project on `ic-cdk-management-canister` 0.2 or `ic` 6.x is already on it. Version 1, which follows, is still the replica default and is what older dependencies attach; for version 2 in full, including the reservation mechanics and its distinct failure modes, read `references\u002Fpricing-version-2.md`.\n\nThe `ic0.cost_http_request` system API computes the exact cycle cost at runtime, so canisters do not need to hard-code the formula. `Call.httpRequest` on `ic` 4.x or 5.x (Motoko) calls it internally and attaches the required cycles automatically; on the Rust side the same is true of the free `http_request` in `ic-cdk-management-canister` 0.1, or `ic_cdk::management_canister::http_request` on ic-cdk 0.19. For manual use: in Motoko, `Prim.costHttpRequest(requestSize, maxResponseBytes)` (via `import Prim \"mo:⛔\"`); in Rust, `ic_cdk::api::cost_http_request(request_size, max_res_bytes)`.\n\n`request_size` is the sum of byte lengths of the URL, all header names and values, the body, the transform function name, and the transform context.\n\nFor reference, the underlying formula on a 13-node subnet (n = 13) is:\n\n```text\nBase cost:                      49_140_000 cycles  (= (3_000_000 + 60_000*13) * 13)\n+ per request byte:              5_200 cycles      (= 400 * 13)\n+ per max_response_bytes byte:  10_400 cycles      (= 800 * 13)\n\nIMPORTANT: Under version 1 the charge is against max_response_bytes, NOT the\nactual response size. Under version 2 it is the reverse: the charge follows\nthe bytes that arrive, the round-trip time and the transform instructions.\nOmitting max_response_bytes assumes the 2MB maximum (2_000_000 bytes) and costs\n49_140_000 + 10_400 * 2_000_000 = 20_849_140_000 cycles (~20.85B), plus the\nper-request-byte term.\n```\n\nUnder version 1, unused cycles are refunded when the call returns, so over-budgeting is **safe but not free**. Under version 2 it is not merely wasteful: the attachment beyond the base fee is the per-node allowance, which sets each node's response deadline and transform instruction limit, so a margin is permission to spend rather than idle reserve. Version 2 refunds also arrive asynchronously, with a node that never reported returning its allowance about a minute after the response. Attached cycles leave the canister's spendable balance for the *duration* of the call, so a hand-attached margin directly caps how many outcalls the canister can have in flight before it runs out of balance. For a canister making one outcall per user action, that margin is a concurrency limit. This is why both wrappers attach the exact computed amount rather than a round number — as the `ic` mops package puts it: *\"Only minimal amount of cycles are added to the call. This helps the canister to make more calls in parallel without running out of cycles.\"*\n\nDo not hand-attach a buffer \"to be safe\". Call the wrapper, or compute the exact cost with `Prim.costHttpRequest` \u002F `ic_cdk::api::cost_http_request` and attach that. Under version 1, the way to lower the cost of an outcall is a tighter `max_response_bytes`, never a larger attachment. Under version 2 the price follows what the call consumes; the round trip and the transform are governed by their `with_expected_*` setters, and size is still governed by `max_response_bytes`, which now sizes the reservation rather than the price.\n\nThis formula applies on a normal **Application subnet**. It does not apply on a **cloud engine** (`CloudEngine` subnet): there, `ic0.cost_http_request` returns 0 regardless of `request_size` or `max_response_bytes`, because engines run under a free cost schedule. Load the `cloud-engine-canisters` skill for the engine's call rules before writing or debugging outcall code that will run there.\n\n## Deploy & Test\n\n### Local Deployment\n\n```bash\nicp network start -d\nicp deploy backend\n```\n\nNote: HTTPS outcalls work on the local replica. icp-cli proxies the requests through the local HTTP gateway. Flexible outcalls run locally too, but every response comes from the same single node, so reconciliation never sees disagreement there.\n\n### Mainnet Deployment\n\n```bash\nicp canister status backend -e ic && icp deploy -e ic backend   # check balance, then deploy\n```\n\n## Verify It Works\n\n```bash\n# 1. GET outcall. Expected: '(\"{\\\"internet-computer\\\":{\\\"usd\\\":12.34}}\")', price varies.\nicp canister call backend getIcpPriceUsd '()'   # Motoko example above\nicp canister call backend fetch_price '()'      # Rust example above\n\n# 2. POST outcall. Expected: httpbin.org echoing your data back.\nicp canister call backend postData '(\"{\\\"test\\\": \\\"hello\\\"}\")'    # Motoko\nicp canister call backend post_data '(\"{\\\"test\\\": \\\"hello\\\"}\")'   # Rust\n\n# 4. Balance should have decreased. Under version 2 it keeps settling for about\n#    a minute afterwards as per-node refunds arrive, so re-read before judging.\nicp canister status backend\n\n# 5. Error handling: add a function calling a non-existent domain and verify it\n#    returns an error rather than trapping.\n```\n\n### Debugging Outcall Failures\n\nIf an outcall fails:\n\n```bash\n# Check the replica log for detailed error messages\n# Local: icp output shows errors inline\n# Mainnet: check the canister logs\n\n# Exact reject messages from the replica (match on these, not on paraphrases):\n#\n# \"Timeout expired\"                                                     [SysFatal]\n#     The remote server did not respond within 30s.\n#\n# \"Canister http request timed out\"                                  [SysTransient]\n#     The subnet did not produce a response within 60s. Normally retryable.\n#     Under version 2 it can also mean under-funding, but only when a node went\n#     silent AND the remaining contributors cannot fund delivery without it;\n#     then a retry just times out again. See references\u002Fpricing-version-2.md.\n#\n# \"Deadline Exceeded\"                                                [SysTransient]\n#     The adapter did not answer the replica within its 60s deadline. Rarer\n#     than the two above: the adapter's own 30s timeout usually fires first.\n#\n# \"No consensus could be reached. Replicas had different responses.\n#  Details: request_id: \u003Cid>, hashes: \u003C...>\"                         [SysTransient]\n#     Transform is missing or not stripping enough non-determinism.\n#\n# There are THREE distinct size-limit messages, all [SysFatal], because response\n# headers are counted against max_response_bytes BEFORE the body is read:\n#\n# \"Header size exceeds specified response size limit \u003CN>\"\n#     The response headers ALONE exceeded max_response_bytes.\n#\n# \"Http body exceeds size limit of \u003CN> bytes.\"\n#     The body exceeded the allowance REMAINING after header bytes were\n#     subtracted. Note the message prints the full cap \u003CN>, not the remainder,\n#     so the body that failed can be well under \u003CN>. Do not read this message\n#     as \"my body is too big\" -- it means headers + body are too big.\n#\n# \"Transformed http response exceeds limit: \u003CN>\"\n#     The Candid-encoded output of your transform exceeded max_response_bytes.\n#\n#     Raising max_response_bytes fixes all three. Stripping headers in the\n#     transform fixes ONLY this last one: the first two are checked before\n#     the transform runs.\n#\n# \"http_request request sent with \u003CX> cycles, but \u003CY> cycles are required.\"\n#                                                              [CanisterReject]\n#     Attached less than the computed cost. Use the wrapper rather than a\n#     hand-picked number. Version 1 only: version 2 checks just the base fee up\n#     front and fails later instead, as one of the next two.\n#\n# \"Insufficient cycles\"                                        [CanisterReject]\n#     Version 2: a node exhausted its own per-node allowance. Ordinary response\n#     content, so it reaches the canister only if a quorum produce the same\n#     reject; otherwise it becomes a divergence.\n#\n# \"Out of cycles: \u003Ck> of the assigned replicas reported a collective spend of\n#  \u003Cn> cycles, leaving \u003Cm> cycles of the attached payment (after base fee\n#  deduction). Delivering a response would cost at least \u003Cmin> cycles.\"\n#                                                              [CanisterReject]\n#     Version 2 pooled shortfall: the nodes agreed, but what they left unspent\n#     does not cover putting the response in a block. Raise the reservation.\n```\n\n### Transform Debugging\n\nIf you get \"no consensus could be reached\" errors, your transform function is not making responses identical. Common culprits:\n\n1. **Response headers differ** -- strip ALL headers in the transform\n2. **JSON field ordering differs** -- parse and re-serialize the JSON in the transform\n3. **Timestamps in response body** -- extract only the fields you need\n\nFor a typed JSON parser and a transform that normalizes field ordering, see `references\u002Fjson-responses.md`.\n\n## Additional References\n\n- **`references\u002Fpricing-version-2.md`** — the pay-as-you-go model: what it charges, the four expectations and their defaults, why they fund one pooled per-node budget, the reject messages for an under-funded call, and what to check before migrating.\n- **`references\u002Fjson-responses.md`** — parsing a typed JSON body with `serde_json`, and a transform that re-serializes to normalize field ordering for consensus.\n- **`references\u002Fflexible-outcalls.md`** — `flexible_http_request` in full: replication counts derived from `subnet_self_node_count`, a compiling example, reconciliation strategies and their threat model, the four `global_error` values, the two separate size limits, why a local run never shows disagreement, and whether a transform pays for itself.\n- Load `cloud-engine-canisters` for canisters running on a cloud engine, including why outcall cost drops to 0 there and why outcalls must never be routed through the engine's console proxy.\n","# HTTPS Outcalls\n\n## What This Is\n\nHTTPS outcalls allow canisters to make HTTP requests to external web services directly from on-chain code. In the default **replicated** mode, every node on the subnet makes the same request and all of them must agree on the response. A transform function strips non-deterministic fields (timestamps, request IDs, ordering) so that every replica sees an identical response and can reach consensus.\n\nTwo other modes avoid that agreement step: **non-replicated** (`is_replicated = false`, one node makes the request) and **flexible** (`flexible_http_request`, a committee of nodes return their individual responses). See [Outcall modes](#outcall-modes).\n\n## Support matrix\n\n| | Rust | Motoko |\n|---|---|---|\n| Package | `ic-cdk` **0.20.3+** with `ic-cdk-management-canister` **0.2+** | `ic` **6.x** (mops), which requires `core` **2.5.0+** and `moc` **2.1.0+** |\n| Entry point | `HttpRequest::new(url)` builder, `.send()` | `Call.httpRequest(url)` builder, `.send()` |\n| Pricing version 1 (legacy) | only by pinning `ic-cdk-management-canister` **0.1**, or calling `aaaaa-aa` directly with `ic_cdk::api::cost_http_request` | only by pinning `ic` **4.x** or **5.x**, or calling `aaaaa-aa` directly with `Prim.costHttpRequest` |\n| Pricing version 2 (pay-as-you-go) | **yes**, the builder always selects it | **yes**, the builder always selects it |\n| `flexible_http_request` | **yes**, `FlexibleHttpRequest::new(url)` | **yes**, `Call.flexibleHttpRequest(url)` |\n| `subnet_self_node_count` | `ic_cdk::api::subnet_self_node_count()` | `Prim.subnetSelfNodeCount()` (via `import Prim \"mo:⛔\"`) |\n\nRead this before writing code; what is available depends on the language and the dependency versions in the project. Both languages reach the same outcall surface, through a builder that selects version 2 and computes the cycles to attach, but only on recent dependencies. On `ic` **4.x** or **5.x** Motoko is still version 1: 4.x `HttpRequestArgs` has no `pricing_version` field at all, Candid omits the absent optional, and the replica reads it as version 1, which is exactly what that `Call.httpRequest` funds. The builder and `flexible_http_request` arrive in **6.0.0**, which requires `moc` **2.1.0** for the `costHttpRequestV2` primitive it calls.\n\nFor Rust the right code depends on which of three worlds the project is in. `ic-cdk` 0.19 has `ic_cdk::management_canister::http_request(&args)` (version 1; the module was removed in 0.20). `ic-cdk` 0.20 with `ic-cdk-management-canister` 0.1 has `ic_cdk_management_canister::http_request(&args)` (version 1). `ic-cdk` 0.20.3+ with `ic-cdk-management-canister` 0.2 has the builders, and is **version 2 only**: 0.2.0 removed the free `http_request` and the builder hard-codes `pricing_version: Some(2)` with no opt-out, so upgrading the crate *is* the migration. Rust also needs `serde_json` for JSON parsing.\n\n**Which version to write.** Version 1 is deprecated and version 2 is the direction, so *new* Rust code should use the 0.2 builders. But do **not** bump a project's pinned versions in order to migrate it as a side effect of an unrelated task: 0.2 is a breaking change (it deletes the free `http_request` and `HttpRequestArgs` gains a required field), so the upgrade is the caller's decision. Write correct code for the line the project is actually on, and tell them the upgrade exists.\n\n## Canister IDs\n\nHTTPS outcalls use the IC management canister:\n\n| Name | Canister ID | Used For |\n|------|-------------|----------|\n| Management canister | `aaaaa-aa` | The `http_request` and `flexible_http_request` management call targets |\n\nYou do not deploy anything extra. The management canister is built into every subnet.\n\n## Mistakes That Break Your Build\n\n1. **Forgetting the transform function.** In **replicated** mode, without a transform the raw HTTP response often differs between replicas (different headers, different ordering in JSON fields, timestamps). Consensus fails and the call is rejected. ALWAYS provide one there. In **non-replicated** and **flexible** mode it is optional *for consensus*, because each node's own response is delivered. Whether to set one there is a tradeoff. **No transform reserves nothing for it** (the instruction term defaults to 0, not to the query limit), which is the cheapest configuration available. Adding one defaults that term to the full query limit, so you must also declare `with_expected_transform_instructions`. What it buys back is the delivery fee, charged per byte on the **transformed** size: every byte stripped lowers the bill, so what decides it is how much there is worth stripping (bulky headers yes, a response that is already mostly payload no). The reservation floors that term at about 1 KB, so `get_cost()` understates the saving on a small response. Two non-cycle reasons can decide it anyway: in flexible mode the combined 2 MiB budget is measured after the transform, so smaller responses mean fewer get dropped, and reconciling is impossible if per-node noise makes every response distinct.\n\n2. **Not attaching cycles to the call.** On a normal Application subnet, HTTPS outcalls are not free — the calling canister must attach cycles to cover the cost, and attaching zero fails the call. Both Motoko and Rust have builders that compute and attach the required cycles automatically: in Motoko, `Call.httpRequest(url)....send()` from the `ic` mops package (`import Call \"mo:ic\u002FCall\"`); in Rust, the `HttpRequest` builder's `.send()` from `ic-cdk-management-canister` 0.2. On current versions both price version 2 with `ic0.cost_http_request_v2`; an older `ic` (4.x or 5.x) prices version 1 with `ic0.cost_http_request` instead. Both are cost-schedule aware, so the same wrapper attaches the correct amount on any subnet type. **Under version 2 the up-front check is against the base fee only**, so an under-sized attachment is *not* rejected at call time: the call runs with tighter per-node limits and can fail partway, after the remote server was already contacted. See `references\u002Fpricing-version-2.md`. On a **cloud engine** (`CloudEngine` subnet), that amount is always 0 by design; do not \"fix\" a working outcall by attaching a hardcoded non-zero fee there — see the `cloud-engine-canisters` skill.\n\n3. **Using HTTP instead of HTTPS.** The IC only supports HTTPS outcalls. Plain HTTP URLs are rejected. The target server must have a valid TLS certificate.\n\n4. **Sizing `max_response_bytes` against the expected body — the limit is not body-only.** The spec defines the size of an HTTP request or response as *the total number of bytes representing the names and values of HTTP headers and the HTTP body*. Response **headers count against `max_response_bytes`**, and a real API commonly sends 1–2 KB of response headers (a unique request id, `Date`, the rate-limit family, CDN headers) before a single byte of body — against a tight cap that is a large share of the budget. Size the cap for **headers + body as they arrive from the server**, then add margin. The failure mode is total: the call fails every time rather than returning a truncated response. The maximum is 2MB = `2_000_000` bytes (decimal, not 2^21), and the same headers-plus-body definition caps the **request** you send at `2_000_000` bytes.\n\n5. **Expecting the transform function to shrink an oversized response under the cap.** The cap is enforced **twice**: once on the raw response as it arrives from the server (headers first, then the body against what remains), and again on the transform's Candid-encoded **output**, which includes serialization overhead. Stripping headers in the transform cannot rescue a raw response that already exceeded the cap, because that first check fails before the transform ever runs. It *can* keep the transform's own output under the cap — worth doing when the transform would otherwise echo the headers back and its encoded output would exceed the limit. Both checks compare against the **same** `max_response_bytes` value, which is why a raw response that only just fits can still fail after the transform: the Candid overhead is added on top. So: size `max_response_bytes` for the raw response, and keep the transform's output small; never set a tight cap on the theory that stripping headers afterwards will make an oversized response fit.\n\n6. **Ignoring the header limits.** Independent of `max_response_bytes`, the spec caps HTTP requests and responses at **64 headers**, **8 KiB** per header name or value, and **48 KiB** for all header names and values combined. The URL must not exceed **8192** bytes. On the request side these are enforced when the replica decodes your arguments, so an over-limit request never leaves the subnet and fails with `InvalidManagementPayload` — e.g. `Deserialize error: The number of elements exceeds maximum allowed 64` — rather than with any HTTP-looking error. If you send no `user-agent` header the IC adds `user-agent: ic\u002F1.0`, and that added header does not count toward these limits.\n\n7. **Omitting `max_response_bytes`.** Under **version 1**, if you do not set it the system assumes the maximum (2MB) and charges cycles accordingly — roughly 20.85 billion cycles on a 13-node subnet. Under **version 2** it no longer sets the price, but it is still the default for the `raw_response_bytes` expectation, so omitting it reserves for 2MB: 34.0 billion cycles rather than 5.3 billion for a 4,000-byte cap. Either way, always set it to a reasonable upper bound for your expected response (see pitfall 4 for what counts toward it).\n\n8. **Non-idempotent POST requests without caution.** Because multiple replicas make the same request, a POST endpoint that is not idempotent (e.g., \"create order\") will be called N times (once per replica, typically 13 on a 13-node subnet). Use idempotency keys, or design endpoints to handle duplicate requests, or set `is_replicated = ?false` (Rust: `.non_replicated()`), which has a single node send the request and removes the rate-limit pressure entirely — at the cost of trusting that node not to observe or modify the response.\n\n9. **Not handling outcall failures.** External servers can be down, slow, or return errors. Always handle the error case. There are **two distinct timeouts**, and neither traps — both come back as rejects (in Motoko the `await` raises a catchable `Error`; in Rust the wrapper returns `Err`):\n   - The remote server does not respond within **30 seconds**: `SysFatal`, message `Timeout expired`.\n   - The subnet does not produce a response within **60 seconds**: `SysTransient`, message `Canister http request timed out`. This one is normally the retryable one; the exception is a version 2 call that is under-funded *and* missing a node's report (see `references\u002Fpricing-version-2.md`).\n\n10. **Calling localhost or private IPs.** HTTPS outcalls can only reach public internet endpoints. Localhost, 10.x.x.x, 192.168.x.x, and other private ranges are blocked.\n\n11. **Forgetting the `Host` header.** Some API endpoints require the `Host` header to be explicitly set. The IC does not automatically set this from the URL.\n\n12. **Leaving the version 2 expectations unset.** Anything you do not declare is *reserved* at its maximum: a 60-second round trip, and a transform running to the full query instruction limit. For a 4,000-byte cap that is around 5.3 billion cycles held, nearly all of it the transform reserve, against about 112 million once you declare `with_expected_transform_instructions` and `with_expected_roundtrip_time_ms`. Declare those two. Leave `raw_response_bytes` alone, because the server decides it; lower `max_response_bytes` instead if the byte terms dominate. Declare `with_expected_transformed_response_bytes` when your transform bounds its own output, which is the one case where the cap cannot be the lever. The *charge* is unaffected by any of this unless the smaller budget actually cuts the call short. `references\u002Fpricing-version-2.md` has the figures, the per-resource reasoning, and the failure modes.\n\n13. **Expecting to reach version 1 through `ic-cdk-management-canister` 0.2.** You cannot. The builder is the only path the crate offers, it hard-codes `pricing_version: Some(2)`, and there is no `with_pricing_version`. `HttpRequest::from_args` looks like the escape hatch and is not: it is the documented way to migrate an existing call site, and it **silently overwrites `pricing_version` with 2**, so args that deliberately set 1 change version when you pass them through it. To stay on version 1, pin `ic-cdk-management-canister` 0.1, or call `aaaaa-aa` directly and price with `ic_cdk::api::cost_http_request`, which is still present in 0.20.3.\n\n14. **Setting `pricing_version` by hand and funding it with the wrong cost function.** The field and the attachment have to agree. Set `pricing_version = 2` while attaching a `cost_http_request` (version 1) amount and the call is *accepted*, because the up-front check is only the base fee — it then runs on a smaller per-node allowance than version 2 intended and can fail partway. The reverse, a version 1 call funded with a `cost_http_request_v2` amount, is rejected outright with `http_request request sent with \u003CX> cycles, but \u003CY> cycles are required.`, because version 1 wants the whole `max_response_bytes` up front. Let the builder set both: in either language it pairs version 2 with `cost_http_request_v2`, and overwrites `pricing_version` even when you start from arguments you assembled yourself (`httpRequestFromArgs`, `HttpRequest::from_args`), so the field and the amount cannot drift apart. Note also that the replica *filters* an unrecognised version to version 1 with no error, so a bogus value fails as a funding mismatch rather than as a validation error.\n\n15. **Hardcoding `total_requests` for a flexible outcall.** `total_requests` must not exceed the subnet size, which differs per subnet (13 or 34 on mainnet). Derive it:\n    ```rust\n    \u002F\u002F WRONG, even when you want five nodes: 5 may exceed the subnet size.\n    let replication = ReplicationCounts { total_requests: 5, min_responses: 3, max_responses: 5 };\n\n    \u002F\u002F RIGHT: ask for five, but never more than the subnet has.\n    let total_requests = subnet_self_node_count().min(5);\n    let min_responses = total_requests \u002F 2 + 1;\n    ```\n\n    Also handle **any** count between `min_responses` and `max_responses` in the success arm — fewer than `max_responses` is a normal success, not a degraded one — and check each response's `status` separately, because no node had to agree with any other. See `references\u002Fflexible-outcalls.md`.\n\n## Outcall modes\n\nReplicated and non-replicated are selected by `is_replicated`; flexible is a separate method.\n\n| | Replicated (default) | Non-replicated (`is_replicated = false`) | Flexible (`flexible_http_request`) |\n|---|---|---|---|\n| Who sends it | All N nodes | One node chosen by the system | A committee of `total_requests` nodes |\n| What you get | One agreed response | That node's response | Between `min_responses` and `max_responses` responses |\n| Transform | Required in practice | Optional, often still worth it | Optional, often still worth it |\n| Pricing | Version 1 or 2 | Version 1 or 2 | Always version 2 |\n| Extra methods | `GET`, `HEAD`, `POST` | plus `PUT`, `DELETE`, `PATCH` | plus those when `total_requests`, `min_responses` and `max_responses` are all equal |\n| Risk | N simultaneous requests trip API rate limits | That node could observe or modify the response | Reconciling the responses is your job |\n\nUse replicated when you need the integrity guarantee consensus gives, non-replicated for rate-limited APIs and non-idempotent POSTs, and flexible when the data changes faster than nodes could ever agree on it.\n\n## Implementation\n\n### Motoko\n\nImport both the builder and the types from the `ic` mops package: `import Call \"mo:ic\u002FCall\"` and `import IC \"mo:ic\u002FTypes\"`. `Call.httpRequest(url)` starts a request and `.send()` computes and attaches the required cycles.\n\n**This is pricing version 2**, on `ic` 6.x. On `ic` 4.x or 5.x the entry point is `Call.httpRequest(args)` taking the whole record, and it prices version 1; see the support matrix.\n\n```motoko\nimport Blob \"mo:core\u002FBlob\";\nimport Nat \"mo:core\u002FNat\";\nimport Text \"mo:core\u002FText\";\nimport Call \"mo:ic\u002FCall\";\nimport IC \"mo:ic\u002FTypes\";\n\npersistent actor {\n\n  \u002F\u002F Transform function: strips headers so all replicas see the same response for consensus.\n  \u002F\u002F MUST be a `shared query` function.\n  public query func transform({\n    context : Blob;\n    response : IC.HttpRequestResult;\n  }) : async IC.HttpRequestResult {\n    {\n      response with headers = []; \u002F\u002F Strip headers -- they often contain non-deterministic values\n    };\n  };\n\n  \u002F\u002F GET request: fetch a JSON API\n  public func getIcpPriceUsd() : async Text {\n    let url = \"https:\u002F\u002Fapi.coingecko.com\u002Fapi\u002Fv3\u002Fsimple\u002Fprice?ids=internet-computer&vs_currencies=usd\";\n\n    \u002F\u002F send() computes and attaches the cycles the call may need.\n    let response = await Call.httpRequest(url)\n      \u002F\u002F Bounds the response; a larger one fails the call. The cap covers\n      \u002F\u002F headers and body together, and the transform cannot bring an\n      \u002F\u002F oversized response back under it. Under version 2 it no longer sets\n      \u002F\u002F the price, because only the bytes that arrive are charged.\n      .withMaxResponseBytes(10_000)\n      .withHeader(\"User-Agent\", \"ic-canister\")\n      .withTransform({ function = transform; context = Blob.fromArray([]) })\n      \u002F\u002F Unset expectations reserve the worst case: 60 seconds of round trip,\n      \u002F\u002F and a transform running to the full query instruction limit.\n      \u002F\u002F Declaring what the call expects holds far fewer cycles while it runs;\n      \u002F\u002F the charge is unchanged, because only what is used is billed.\n      .withExpectedRoundtripTimeMs(10_000)\n      .withExpectedTransformInstructions(1_000_000)\n      .send();\n\n    switch (Text.decodeUtf8(response.body)) {\n      case (?text) { text };\n      case (null) { \"Response is not valid UTF-8\" };\n    };\n  };\n\n  \u002F\u002F POST transform: also discards the body, because httpbin.org echoes the\n  \u002F\u002F sender's IP in \"origin\", which differs across replicas.\n  public query func transformPost({\n    context : Blob;\n    response : IC.HttpRequestResult;\n  }) : async IC.HttpRequestResult {\n    {\n      response with\n      headers = [];\n      body = Blob.fromArray([]);\n    };\n  };\n\n  \u002F\u002F POST request: send JSON data\n  public func postData(jsonPayload : Text) : async Text {\n    let url = \"https:\u002F\u002Fhttpbin.org\u002Fpost\";\n\n    \u002F\u002F send() computes and attaches the cycles the call may need.\n    let response = await Call.httpRequest(url)\n      .withMethod(#post)\n      .withMaxResponseBytes(50_000)\n      .withHeader(\"Content-Type\", \"application\u002Fjson\")\n      .withHeader(\"User-Agent\", \"ic-canister\")\n      \u002F\u002F Idempotency key: prevents duplicate processing if multiple replicas hit the endpoint\n      .withHeader(\"Idempotency-Key\", \"unique-request-id-12345\")\n      .withBody(Text.encodeUtf8(jsonPayload))\n      .withTransform({ function = transformPost; context = Blob.fromArray([]) })\n      .withExpectedRoundtripTimeMs(10_000)\n      .withExpectedTransformInstructions(1_000_000)\n      .send();\n\n    if (response.status == 200) {\n      \"POST successful (status 200)\";\n    } else {\n      \"POST failed with status \" # Nat.toText(response.status);\n    };\n  };\n};\n```\n\n### Rust\n\n```toml\n# Cargo.toml\n[package]\nname = \"https_outcalls_backend\"\nversion = \"0.1.0\"\nedition = \"2024\"\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\nic-cdk = \"0.20\"\nic-cdk-management-canister = \"0.2\"\ncandid = \"0.10\"\nserde = { version = \"1\", features = [\"derive\"] }\nserde_json = \"1\"\n```\n\n```rust\nuse ic_cdk_management_canister::{\n    transform_context_from_query, HttpMethod, HttpRequest, HttpRequestResult, TransformArgs,\n};\nuse ic_cdk::{query, update};\n\n\u002F\u002F\u002F Transform function: strips non-deterministic headers so all replicas agree.\n\u002F\u002F\u002F MUST be a #[query] function.\n#[query(hidden = true)]\nfn transform(args: TransformArgs) -> HttpRequestResult {\n    HttpRequestResult {\n        status: args.response.status,\n        body: args.response.body,\n        headers: vec![], \u002F\u002F Strip all headers for consensus\n        \u002F\u002F If you need specific headers, filter them here:\n        \u002F\u002F headers: args.response.headers.into_iter()\n        \u002F\u002F     .filter(|h| h.name.to_lowercase() == \"content-type\")\n        \u002F\u002F     .collect(),\n    }\n}\n\n\u002F\u002F\u002F GET request: Fetch JSON from an external API\n#[update]\nasync fn fetch_price() -> String {\n    let url = \"https:\u002F\u002Fapi.coingecko.com\u002Fapi\u002Fv3\u002Fsimple\u002Fprice?ids=internet-computer&vs_currencies=usd\";\n\n    \u002F\u002F The builder always selects pricing version 2. `.send()` prices the call\n    \u002F\u002F with ic0.cost_http_request_v2 and attaches that amount.\n    let request = HttpRequest::new(url)\n        .with_method(HttpMethod::GET)\n        \u002F\u002F Budget for response headers + body: the cap covers both, and the\n        \u002F\u002F transform cannot bring an oversized response back under it.\n        .with_max_response_bytes(10_000)\n        .with_header(\"User-Agent\", \"ic-canister\")\n        .with_transform(transform_context_from_query(\"transform\".to_string(), vec![]))\n        \u002F\u002F Unset expectations reserve the worst case: a 60s round trip and a\n        \u002F\u002F transform running to the full 5-billion-instruction query limit.\n        \u002F\u002F Declaring them holds far fewer cycles. See pitfall 12.\n        .with_expected_roundtrip_time_ms(10_000)\n        .with_expected_transform_instructions(1_000_000);\n\n    match request.send().await {\n        Ok(response) => {\n            let body = String::from_utf8(response.body)\n                .unwrap_or_else(|_| \"Invalid UTF-8 in response\".to_string());\n\n            if response.status != candid::Nat::from(200u64) {\n                return format!(\"HTTP error: status {}\", response.status);\n            }\n\n            body\n        }\n        Err(err) => {\n            format!(\"HTTP outcall failed: {:?}\", err)\n        }\n    }\n}\n\n\u002F\u002F\u002F POST transform: strips headers AND body, because httpbin.org echoes the\n\u002F\u002F\u002F sender's IP in \"origin\", which differs across replicas.\n#[query(hidden = true)]\nfn transform_post(args: TransformArgs) -> HttpRequestResult {\n    HttpRequestResult {\n        status: args.response.status,\n        body: vec![],\n        headers: vec![],\n    }\n}\n\n\u002F\u002F\u002F POST request: Send JSON data to an external API\n#[update]\nasync fn post_data(json_payload: String) -> String {\n    let url = \"https:\u002F\u002Fhttpbin.org\u002Fpost\";\n\n    let request = HttpRequest::new(url)\n        .with_method(HttpMethod::POST)\n        .with_max_response_bytes(50_000)\n        .with_header(\"Content-Type\", \"application\u002Fjson\")\n        .with_header(\"User-Agent\", \"ic-canister\")\n        \u002F\u002F Idempotency key: prevents duplicate processing across replicas\n        .with_header(\"Idempotency-Key\", \"unique-request-id-12345\")\n        .with_body(json_payload.into_bytes())\n        .with_transform(transform_context_from_query(\"transform_post\".to_string(), vec![]))\n        .with_expected_roundtrip_time_ms(10_000)\n        .with_expected_transform_instructions(1_000_000);\n    \u002F\u002F Replicated, so every node POSTs: hence the idempotency key above, and\n    \u002F\u002F `transform_post`. `.non_replicated()` would send it once instead and make\n    \u002F\u002F both unnecessary, at the cost of trusting that one node (pitfall 8).\n\n    match request.send().await {\n        Ok(response) => {\n            if response.status == candid::Nat::from(200u64) {\n                \"POST successful (status 200)\".to_string()\n            } else {\n                format!(\"POST failed with status {}\", response.status)\n            }\n        }\n        Err(err) => {\n            format!(\"HTTP outcall failed: {:?}\", err)\n        }\n    }\n}\n```\n\n### Cycle Cost Estimation\n\nThere are two pricing versions. Version 1 is the replica default and is deprecated; version 2 (\"pay-as-you-go\") charges for the resources a call consumes instead of the bytes it reserves. **The current builders in both languages are version 2 only**, so a project on `ic-cdk-management-canister` 0.2 or `ic` 6.x is already on it. Version 1, which follows, is still the replica default and is what older dependencies attach; for version 2 in full, including the reservation mechanics and its distinct failure modes, read `references\u002Fpricing-version-2.md`.\n\nThe `ic0.cost_http_request` system API computes the exact cycle cost at runtime, so canisters do not need to hard-code the formula. `Call.httpRequest` on `ic` 4.x or 5.x (Motoko) calls it internally and attaches the required cycles automatically; on the Rust side the same is true of the free `http_request` in `ic-cdk-management-canister` 0.1, or `ic_cdk::management_canister::http_request` on ic-cdk 0.19. For manual use: in Motoko, `Prim.costHttpRequest(requestSize, maxResponseBytes)` (via `import Prim \"mo:⛔\"`); in Rust, `ic_cdk::api::cost_http_request(request_size, max_res_bytes)`.\n\n`request_size` is the sum of byte lengths of the URL, all header names and values, the body, the transform function name, and the transform context.\n\nFor reference, the underlying formula on a 13-node subnet (n = 13) is:\n\n```text\nBase cost:                      49_140_000 cycles  (= (3_000_000 + 60_000*13) * 13)\n+ per request byte:              5_200 cycles      (= 400 * 13)\n+ per max_response_bytes byte:  10_400 cycles      (= 800 * 13)\n\nIMPORTANT: Under version 1 the charge is against max_response_bytes, NOT the\nactual response size. Under version 2 it is the reverse: the charge follows\nthe bytes that arrive, the round-trip time and the transform instructions.\nOmitting max_response_bytes assumes the 2MB maximum (2_000_000 bytes) and costs\n49_140_000 + 10_400 * 2_000_000 = 20_849_140_000 cycles (~20.85B), plus the\nper-request-byte term.\n```\n\nUnder version 1, unused cycles are refunded when the call returns, so over-budgeting is **safe but not free**. Under version 2 it is not merely wasteful: the attachment beyond the base fee is the per-node allowance, which sets each node's response deadline and transform instruction limit, so a margin is permission to spend rather than idle reserve. Version 2 refunds also arrive asynchronously, with a node that never reported returning its allowance about a minute after the response. Attached cycles leave the canister's spendable balance for the *duration* of the call, so a hand-attached margin directly caps how many outcalls the canister can have in flight before it runs out of balance. For a canister making one outcall per user action, that margin is a concurrency limit. This is why both wrappers attach the exact computed amount rather than a round number — as the `ic` mops package puts it: *\"Only minimal amount of cycles are added to the call. This helps the canister to make more calls in parallel without running out of cycles.\"*\n\nDo not hand-attach a buffer \"to be safe\". Call the wrapper, or compute the exact cost with `Prim.costHttpRequest` \u002F `ic_cdk::api::cost_http_request` and attach that. Under version 1, the way to lower the cost of an outcall is a tighter `max_response_bytes`, never a larger attachment. Under version 2 the price follows what the call consumes; the round trip and the transform are governed by their `with_expected_*` setters, and size is still governed by `max_response_bytes`, which now sizes the reservation rather than the price.\n\nThis formula applies on a normal **Application subnet**. It does not apply on a **cloud engine** (`CloudEngine` subnet): there, `ic0.cost_http_request` returns 0 regardless of `request_size` or `max_response_bytes`, because engines run under a free cost schedule. Load the `cloud-engine-canisters` skill for the engine's call rules before writing or debugging outcall code that will run there.\n\n## Deploy & Test\n\n### Local Deployment\n\n```bash\nicp network start -d\nicp deploy backend\n```\n\nNote: HTTPS outcalls work on the local replica. icp-cli proxies the requests through the local HTTP gateway. Flexible outcalls run locally too, but every response comes from the same single node, so reconciliation never sees disagreement there.\n\n### Mainnet Deployment\n\n```bash\nicp canister status backend -e ic && icp deploy -e ic backend   # check balance, then deploy\n```\n\n## Verify It Works\n\n```bash\n# 1. GET outcall. Expected: '(\"{\\\"internet-computer\\\":{\\\"usd\\\":12.34}}\")', price varies.\nicp canister call backend getIcpPriceUsd '()'   # Motoko example above\nicp canister call backend fetch_price '()'      # Rust example above\n\n# 2. POST outcall. Expected: httpbin.org echoing your data back.\nicp canister call backend postData '(\"{\\\"test\\\": \\\"hello\\\"}\")'    # Motoko\nicp canister call backend post_data '(\"{\\\"test\\\": \\\"hello\\\"}\")'   # Rust\n\n# 4. Balance should have decreased. Under version 2 it keeps settling for about\n#    a minute afterwards as per-node refunds arrive, so re-read before judging.\nicp canister status backend\n\n# 5. Error handling: add a function calling a non-existent domain and verify it\n#    returns an error rather than trapping.\n```\n\n### Debugging Outcall Failures\n\nIf an outcall fails:\n\n```bash\n# Check the replica log for detailed error messages\n# Local: icp output shows errors inline\n# Mainnet: check the canister logs\n\n# Exact reject messages from the replica (match on these, not on paraphrases):\n#\n# \"Timeout expired\"                                                     [SysFatal]\n#     The remote server did not respond within 30s.\n#\n# \"Canister http request timed out\"                                  [SysTransient]\n#     The subnet did not produce a response within 60s. Normally retryable.\n#     Under version 2 it can also mean under-funding, but only when a node went\n#     silent AND the remaining contributors cannot fund delivery without it;\n#     then a retry just times out again. See references\u002Fpricing-version-2.md.\n#\n# \"Deadline Exceeded\"                                                [SysTransient]\n#     The adapter did not answer the replica within its 60s deadline. Rarer\n#     than the two above: the adapter's own 30s timeout usually fires first.\n#\n# \"No consensus could be reached. Replicas had different responses.\n#  Details: request_id: \u003Cid>, hashes: \u003C...>\"                         [SysTransient]\n#     Transform is missing or not stripping enough non-determinism.\n#\n# There are THREE distinct size-limit messages, all [SysFatal], because response\n# headers are counted against max_response_bytes BEFORE the body is read:\n#\n# \"Header size exceeds specified response size limit \u003CN>\"\n#     The response headers ALONE exceeded max_response_bytes.\n#\n# \"Http body exceeds size limit of \u003CN> bytes.\"\n#     The body exceeded the allowance REMAINING after header bytes were\n#     subtracted. Note the message prints the full cap \u003CN>, not the remainder,\n#     so the body that failed can be well under \u003CN>. Do not read this message\n#     as \"my body is too big\" -- it means headers + body are too big.\n#\n# \"Transformed http response exceeds limit: \u003CN>\"\n#     The Candid-encoded output of your transform exceeded max_response_bytes.\n#\n#     Raising max_response_bytes fixes all three. Stripping headers in the\n#     transform fixes ONLY this last one: the first two are checked before\n#     the transform runs.\n#\n# \"http_request request sent with \u003CX> cycles, but \u003CY> cycles are required.\"\n#                                                              [CanisterReject]\n#     Attached less than the computed cost. Use the wrapper rather than a\n#     hand-picked number. Version 1 only: version 2 checks just the base fee up\n#     front and fails later instead, as one of the next two.\n#\n# \"Insufficient cycles\"                                        [CanisterReject]\n#     Version 2: a node exhausted its own per-node allowance. Ordinary response\n#     content, so it reaches the canister only if a quorum produce the same\n#     reject; otherwise it becomes a divergence.\n#\n# \"Out of cycles: \u003Ck> of the assigned replicas reported a collective spend of\n#  \u003Cn> cycles, leaving \u003Cm> cycles of the attached payment (after base fee\n#  deduction). Delivering a response would cost at least \u003Cmin> cycles.\"\n#                                                              [CanisterReject]\n#     Version 2 pooled shortfall: the nodes agreed, but what they left unspent\n#     does not cover putting the response in a block. Raise the reservation.\n```\n\n### Transform Debugging\n\nIf you get \"no consensus could be reached\" errors, your transform function is not making responses identical. Common culprits:\n\n1. **Response headers differ** -- strip ALL headers in the transform\n2. **JSON field ordering differs** -- parse and re-serialize the JSON in the transform\n3. **Timestamps in response body** -- extract only the fields you need\n\nFor a typed JSON parser and a transform that normalizes field ordering, see `references\u002Fjson-responses.md`.\n\n## Additional References\n\n- **`references\u002Fpricing-version-2.md`** — the pay-as-you-go model: what it charges, the four expectations and their defaults, why they fund one pooled per-node budget, the reject messages for an under-funded call, and what to check before migrating.\n- **`references\u002Fjson-responses.md`** — parsing a typed JSON body with `serde_json`, and a transform that re-serializes to normalize field ordering for consensus.\n- **`references\u002Fflexible-outcalls.md`** — `flexible_http_request` in full: replication counts derived from `subnet_self_node_count`, a compiling example, reconciliation strategies and their threat model, the four `global_error` values, the two separate size limits, why a local run never shows disagreement, and whether a transform pays for itself.\n- Load `cloud-engine-canisters` for canisters running on a cloud engine, including why outcall cost drops to 0 there and why outcalls must never be routed through the engine's console proxy.\n",{"name":5,"license":15,"metadata":48,"description":7,"compatibility":51},{"title":49,"category":50},"HTTPS Outcalls","Integration","icp-cli >= 0.2.2; pricing version 2 and flexible outcalls need ic-cdk >= 0.20.3 with ic-cdk-management-canister >= 0.2.0 (Rust), or the ic mops package >= 6.0.0 with moc >= 2.1.0 (Motoko)","main","4aff87e77e63c1886a978b82902814d015f55a4f","skills\u002Fchains\u002Fdfinity-official-https-outcalls",17,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia","https:\u002F\u002Feth2030.com","Hardcore infra. Building @alt-research and applied cryptography.","2026-11-14 02:27:51",{"source":8,"skillName":5,"commands":62,"prompt":72,"zipAvailable":73},[63,66,69],{"tool":64,"command":65},"npx","npx skills add jiayaoqijia\u002Fcryptoskill --skill https-outcalls",{"tool":67,"command":68},"bunx","bunx skills add jiayaoqijia\u002Fcryptoskill --skill https-outcalls",{"tool":70,"command":71},"pnpm","pnpm dlx skills add jiayaoqijia\u002Fcryptoskill --skill https-outcalls","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fchains\u002Fdfinity-official-https-outcalls\u002FSKILL.md",true,[75,79,85,89,93,97],{"path":76,"resourceType":76,"resourceTypeDesc":77,"sizeBytes":78,"sourceHtmlUrl":16},"LICENSE","许可证",10770,{"path":80,"resourceType":81,"resourceTypeDesc":82,"sizeBytes":83,"sourceHtmlUrl":84},"references\u002Fflexible-outcalls.md","REFERENCE","参考文档",12531,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fchains\u002Fdfinity-official-https-outcalls\u002Freferences\u002Fflexible-outcalls.md",{"path":86,"resourceType":81,"resourceTypeDesc":82,"sizeBytes":87,"sourceHtmlUrl":88},"references\u002Fjson-responses.md",2066,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fchains\u002Fdfinity-official-https-outcalls\u002Freferences\u002Fjson-responses.md",{"path":90,"resourceType":81,"resourceTypeDesc":82,"sizeBytes":91,"sourceHtmlUrl":92},"references\u002Fpricing-version-2.md",18251,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fchains\u002Fdfinity-official-https-outcalls\u002Freferences\u002Fpricing-version-2.md",{"path":94,"resourceType":81,"resourceTypeDesc":82,"sizeBytes":95,"sourceHtmlUrl":96},"SOURCE.md",332,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fchains\u002Fdfinity-official-https-outcalls\u002FSOURCE.md",{"path":98,"resourceType":81,"resourceTypeDesc":82,"sizeBytes":99,"sourceHtmlUrl":100},"TRUST.auto.yaml",2197,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fchains\u002Fdfinity-official-https-outcalls\u002FTRUST.auto.yaml",[102,131,152,169,182,192],{"skillNo":103,"name":104,"nameZh":6,"description":105,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":106,"licenseFileUrl":107,"sourceHtmlUrl":108,"categories":109,"keywords":110,"publishedAt":43},"01M712C436EQYE43342Q4E7GG1","recipe-performance","Diagnose a specific MetaMask user flow's real-app performance and Sentry critical-user-flow telemetry with deterministic mm-harness recipes. Use for CUF boundaries, cold\u002Fwarm\u002Fbackground-resume behavior, latency regressions, lifecycle tags, duplicate or blocking network requests, long tasks, layout\u002Fpaint cost, screen redraws, React rerenders, or browser trace analysis where tests alone cannot prove the real app.","MIT","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fwallets\u002Fmetamask-official-recipe-performance\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fwallets\u002Fmetamask-official-recipe-performance\u002FSKILL.md",[],[111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130],"performance","recipe performance","recipe","mm","sentry","harness","app","deterministic","recipes","flow","cold","critical","analysis","tasks","react","browser","layout","screen","network","metamask",{"skillNo":132,"name":133,"nameZh":6,"description":134,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":135,"sourceHtmlUrl":136,"categories":137,"keywords":138,"publishedAt":43},"01M70XT2F8A5CQZ81JBP98KSW3","mexc-spot-websocket","MEXC Spot WebSocket workflows for public streams, protobuf handling, depth maintenance, listen keys, and private streams without exposing API keys.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-websocket\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-websocket\u002FSKILL.md",[],[139,140,141,33,142,143,144,145,146,147,148,149,150,151],"mexc spot websocket","spot","websocket","workflows","public","listen","streams","depth","keys","for","api keys","maintenance","protobuf",{"skillNo":153,"name":154,"nameZh":6,"description":155,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":156,"sourceHtmlUrl":157,"categories":158,"keywords":159,"publishedAt":43},"01M70XT0MBXN3WZY1BKN0CPNKR","mexc-spot-rest","MEXC Spot REST workflows for public data, signed account requests, orders, wallet\u002Fadmin actions, and secure signing without exposing API keys.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-rest\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-rest\u002FSKILL.md",[],[160,140,161,162,163,164,165,149,148,147,166,167,30,143,168,142,33],"mexc spot rest","REST","admin","signing","Wallet","requests","actions","secure","orders",{"skillNo":170,"name":171,"nameZh":6,"description":172,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":173,"sourceHtmlUrl":174,"categories":175,"keywords":176,"publishedAt":43},"01M70XSYSVZ2N0N91XCAR2CAK0","mexc-futures-websocket","MEXC Futures WebSocket workflows for public streams, private account\u002Forder\u002Fposition streams, ping\u002Fpong, subscriptions, and depth maintenance.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-futures-websocket\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-futures-websocket\u002FSKILL.md",[],[177,178,141,179,180,150,148,181,146,145,143,142],"mexc futures websocket","futures","position","ping","subscriptions",{"skillNo":183,"name":184,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":106,"licenseFileUrl":185,"sourceHtmlUrl":186,"categories":187,"keywords":188,"publishedAt":43},"01M6H0ZWHVE5W04WG2DKQE2SQC","0xklen-version-and-refresh-sanctions-lists","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-version-and-refresh-sanctions-lists\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-version-and-refresh-sanctions-lists\u002FSKILL.md",[],[189,190,191],"refresh","0xklen version and refresh sanctions lists","version",{"skillNo":193,"name":194,"nameZh":6,"description":195,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":106,"licenseFileUrl":196,"sourceHtmlUrl":197,"categories":198,"keywords":199,"publishedAt":43},"01M6H0Z28Q3R8J0S799GJQWXM3","verify-tests-cover-changed-lines","Use when a PR adds code that the existing suite may not exercise. Measures patch coverage — the share of newly changed lines hit by tests — before approving.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-verify-tests-cover-changed-lines\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-verify-tests-cover-changed-lines\u002FSKILL.md",[],[200,201,202,203,204,205,206,207,208,209,210],"tests","cover","verify tests cover changed lines","verify","coverage","pr","exercise","use","patch","code","share"]