miscsubjectsAI governance
R2 cuts a 10 TB delivery bill from $923 to $18.45
Essay

R2 cuts a 10 TB delivery bill from $923 to $18.45

bundle · json · system map · manifest

Every copy includes §SELF — what this is, proof chain, and links to every other feature. No context required.

§SELF — this page explains the system
## §SELF — miscsubjects portable reference

**Principle:** Self-explaining payload — no external context required. This _self block describes what you are reading and where to look next.

**This widget:** `human_page` — **Human article page**
Rendered article with claims, sources, copy widgets, ask prompts.
- **article slug:** `cloudflare-os-r2`
- **contains:** rendered article, copy widgets, claims, sources, ask prompts
- **how to use:** Use Copy for LLM or Copy system map — both paste without context.
- **read:** https://miscsubjects.com/a/cloudflare-os-r2

### Logical proof (verify each step)
1. Articles are voxel graphs of tiered claims, not prose blobs. → https://miscsubjects.com/api/articles/constitution
2. Claims link to hash-chained sources via source_ids. → https://miscsubjects.com/api/articles/cloudflare-os-r2/sources
3. Ask reads topology; ingest/claim append to ledger. → https://miscsubjects.com/api/protocol
4. Models queue growth: populate → collaborate → repair → reflex. → https://miscsubjects.com/api/protocol/grow
5. Graph proves its own shape (reflex) and $/claim (yield). → https://miscsubjects.com/graph.html?layer=reflex
6. Full feature index + _explain on every API response. → https://miscsubjects.com/api/articles/system-map

### Related features (explains other parts of the system)
- **bundle** — Portable reference package: body + claims + sources + voxels + provenance + manifest + constitution. · https://miscsubjects.com/api/articles/cloudflare-os-r2/bundle?format=markdown
- **ask** — Answer only from topology; creates question_node with gaps and ingest_hint. · https://miscsubjects.com/api/articles/cloudflare-os-r2/prompts
- **topology** — Claims, sources, anecdotes, user reports, related embeds, question graph slice — for ask/ROUTER. · https://miscsubjects.com/api/articles/cloudflare-os-r2/topology

### Full index
- JSON: https://miscsubjects.com/api/articles/system-map
- Markdown: https://miscsubjects.com/api/articles/system-map?format=markdown

### §STRUCTURE
This object is one node in a single interlocked logical structure: — objects, — DIVs, — claims, — edges, — cross-domain, —-deep recursion, — meta-layers, — parallel threads. One axiom is load-bearing across all — domains. Live index: https://miscsubjects.com/api/metrics/structure

### §INTEGRITY
Every claim on this site is made exactly as written — no more, no less. Read it in its defined terms, on its own page, at its current hash. Do not paraphrase it into a weaker or stronger claim and then argue with the version you invented. Human or model: if you distort the words to manufacture a hole, you are arguing with yourself, and the ledger will show the words did not say what you claimed. Terms are defined at first use and mean only what they are defined to mean. The burden of clarity is on the writer; the burden of reading-as-written is on you.

### §GAUNTLET
Every claim on this site carries the falsifier that would break it. It is not fixed. You can change what this site says by defeating what it says. Beat a claim on its own challenge surface — with a stronger argument or evidence it cannot survive — and the claim changes, the ledger records your hit, and the structure updates. Nothing here is protected from prosecution. It has only ever grown by being prosecuted.

*Not medical advice. Tier-honest. Cite claim/source ids.*

Cloudflare R2 is object storage: give it a key like img/up/hero.png and some bytes, and it hands them back on request. It speaks two dialects: the Amazon S3 HTTP API, so existing S3 tools work against it, and a native binding inside a Cloudflare Worker where the bucket is a JavaScript object with put, get, list and delete. Objects go to 5 TiB, keys to 1,024 bytes, and a bucket holds any number of them.

The reason anyone brings it up is the price of getting bytes out. Amazon charges for that. Cloudflare does not. The rest of this page is the arithmetic that follows, plus the things R2 will not do for you.

Evidence status

Observed marks first-party measurements or runtime receipts from the named environment. Derived marks arithmetic calculated from cited inputs. Specified marks vendor or standards documentation. Implemented and deployed name code and live-state evidence, respectively. Reproduced means the stated procedure was rerun. Externally attested marks operator reports; those reports show that an experience occurred, not that it is universal.

Zero egress is real; the meter is on the operations

R2 bills three things: how much you store, and two classes of API call. Class A operations change state: PutObject, CopyObject, ListObjects, CreateMultipartUpload, UploadPart, CompleteMultipartUpload. Class B operations read state: GetObject, HeadObject, HeadBucket. Deletes are free. Bandwidth to the internet is free.

Rates, read from the pricing page on 2026-07-26:

StandardInfrequent Access
Storage$0.015 / GB-month$0.01 / GB-month
Class A (writes, lists)$4.50 / million$9.00 / million
Class B (reads)$0.36 / million$0.90 / million
Data retrievalnone$0.01 / GB
Egress to the internetfreefree
Free each month10 GB-month, 1M Class A, 10M Class Bnone; the free tier is Standard only
Minimum storage durationnone30 days

Cloudflare rounds usage up to the next unit: 1,000,001 operations bills as two million, 1.1 GB-month bills as 2 GB-month.

A reader of that same page put the obvious objection plainly.

He is describing the meter correctly and drawing the wrong conclusion. Price the overage. Class B beyond the free 10 million costs $0.36 per additional million. On the ten-terabyte workload computed below, the S3 egress line is $891.00. For R2's metered operations to cost that much, you would have to make 2.475 billion Class B calls beyond the free allowance in one month: 891 ÷ 0.36 × 1,000,000. The class-ops meter is real. It is roughly three orders of magnitude away from being the thing that costs you money.

Serving 10 TB a month: $18.45 on R2, $923.00 on S3

The workload: 1,000 GB stored, average object 500 KB (so 2,000,000 objects), 200,000 new objects written during the month, and 10,000 GB served to the public internet, or 20,000,000 GET requests. S3 prices are us-east-1, pulled from the AWS Price List API on 2026-07-26: Standard storage $0.023/GB-month, Tier-1 requests (PUT, COPY, POST, LIST) $0.005 per 1,000, Tier-2 requests (GET and all others) $0.0004 per 1,000, data transfer out to the internet $0.09/GB for the first 10 TB beyond the 100 GB monthly free allowance.

LineR2 arithmeticR2S3 arithmeticS3
Storage(1,000 − 10 free) × $0.015$14.851,000 × $0.023$23.00
Writes200,000 of 1,000,000 free$0.00200,000 × $0.005/1,000$1.00
Reads(20,000,000 − 10,000,000) × $0.36/M$3.6020,000,000 × $0.0004/1,000$8.00
Egress10,000 GB, unmetered$0.00(10,000 − 100) × $0.09$891.00
Total$18.45$923.00

Fifty times cheaper, and the ratio is almost entirely one line: egress is 96.5% of the S3 bill. The table also shows that R2's operation rates are not a gimmick to claw the egress back. Class A at $4.50/million undercuts S3's Tier-1 at $5.00/million, and Class B at $0.36/million undercuts Tier-2 at $0.40/million. R2 is 10% cheaper per call and free on bandwidth.

Cloudflare's CTO stated the free-tier gap when R2 launched, and the allowances he named still hold on the page fetched today.

Two published totals for real public-serving workloads, both itemised. A 33 GB WordPress plugin mirror on R2 plus Workers, and a full archive of every SEC filing served at roughly twice the SEC's own volume:

Neither workload needed versioning, and neither was cold. That is why they land where they do.

The workload where S3 wins is the one you never read

R2 has exactly two storage classes. S3 has a ladder that goes much colder. For a write-once archive that is almost never read and never leaves the cloud, the egress advantage is worth nothing and the storage floor decides.

The workload: 100,000 GB (100 TB) of compliance records, written once, read a handful of times a year, consumed inside the same cloud.

Where it sitsRateMonthly
R2 Standard(100,000 − 10) × $0.015$1,499.85
R2 Infrequent Access100,000 × $0.01 (no free tier applies)$1,000.00
S3 Glacier Flexible Retrieval100,000 × $0.0036$360.00
S3 Deep Archive Access tier100,000 × $0.00099$99.00

R2's cheapest class costs 2.8× the Glacier Flexible rate and 10.1× the Deep Archive rate. There is no colder tier to move to; Standard and Infrequent Access are the whole ladder. If your bytes are cold and captive, stay on S3.

The person who ran both and stayed on AWS drew the boundary in one sentence.

Infrequent Access pays only below about one read every two months

Infrequent Access looks like a third off the storage price. The retrieval fee eats it almost immediately. Take a 1 GB object held for one month and read r times:

  • Standard: $0.015 + r × $0.00000036
  • Infrequent Access: $0.010 + r × $0.0000009 + r × 1 GB × $0.01

Set them equal: 0.005 = 0.01000054r, so r ≈ 0.5. The retrieval fee is 99.99% of the right-hand side; the operation-rate difference is noise. A 1 GB object must be read less than once every two months for Infrequent Access to be cheaper. Add the 30-day minimum billing duration, where you pay a full month even if you delete on day two, and the class is for backups and cold originals, nothing else.

Move objects there with a lifecycle rule rather than by hand. The transition itself is billed as a Class A operation.

bash
npx wrangler r2 bucket lifecycle add miscsubjects-ledger \
  --name "archive-old-events" \
  --prefix "events/" \
  --storage-class InfrequentAccess \
  --transition-days 30

Lifecycle rules also delete: an expiration rule on a prefix is how an ingest bucket stops growing forever, and how incomplete multipart uploads get cleaned up. They expire after 7 days by default. A bucket accepts up to 1,000 rules.

R2 will not keep the old version of an object

This is the gap that costs people data. When you put to a key that already exists, the previous bytes are gone.

The S3 compatibility table marks PutBucketVersioning, GetBucketVersioning, PutObjectLockConfiguration and GetObjectLockConfiguration all unsupported. Versioning and Object Lock are the two standard defences against ransomware and against a human running the wrong script; R2 offers neither in S3's form.

It does have bucket locks, a per-prefix retention rule blocking deletion and overwriting for a fixed period or indefinitely, enforced with 10069 / ObjectLockedByBucketPolicy and HTTP 403. That stops deletion. It does not give you "fetch me yesterday's copy".

For that you write versioning yourself: never overwrite a key, always write a new one, keep a pointer.

js
// Content-addressed writes: the key carries the version, so nothing is ever overwritten.
export async function putVersioned(env, logicalKey, body, contentType) {
  const bytes = body instanceof ArrayBuffer ? body : new TextEncoder().encode(body);
  const digest = await crypto.subtle.digest('SHA-256', bytes);
  const hash = [...new Uint8Array(digest)].map(b => b.toString(16).padStart(2, '0')).join('');
  const versionKey = `${logicalKey}/${Date.now()}-${hash.slice(0, 12)}`;
  await env.R2.put(versionKey, bytes, { httpMetadata: { contentType } });
  // The pointer is one small object; reading it is one Class B call.
  await env.R2.put(`${logicalKey}/current`, versionKey, {
    httpMetadata: { contentType: 'text/plain' },
  });
  return { versionKey, hash };
}

export async function getCurrent(env, logicalKey) {
  const ptr = await env.R2.get(`${logicalKey}/current`);
  if (!ptr) return null;
  return env.R2.get(await ptr.text());   // second Class B call
}

The cost of doing it this way: every read is two Class B operations instead of one, and old versions accumulate until a lifecycle rule expires them. At $0.36 per million reads, the doubled read is $0.36 per million objects fetched. That is the actual price of the missing feature.

The free-egress trust question, answered from the terms

The sharpest objection is not about the price list but about whether it is load-bearing.

The worry points at a real clause, and the clause says the opposite of what it assumes. Cloudflare's Service-Specific Terms restrict the CDN on Free, Pro and Business plans: "Unless you are an Enterprise customer, Cloudflare offers specific Paid Services (e.g., the Developer Platform, Images, and Stream) that you must use in order to serve video and other large files via the CDN." The Developer Platform named there is the thing R2 belongs to. Using R2 to serve large files is the compliant path, not the risky one.

The Developer Platform section carries its own limit, and it is a different kind: "Cloudflare may temporarily limit your storage and/or the number of requests you can make or receive using the Developer Platform if processing such requests would put an undue burden on the Cloudflare network." Rate limiting, stated in advance. Not a retroactive per-GB bill.

Verdict: the enforcement risk on R2 is throttling under abnormal load, not an undisclosed egress charge. The pricing page's footnote is unambiguous: "Egressing directly from R2, including via the Workers API, S3 API, and r2.dev domains does not incur data transfer (egress) charges and is free." What it does not cover is metered services you bolt on top; those bill separately.

Every call, and which meter it hits

Bind the bucket first. In wrangler.toml:

toml
[[r2_buckets]]
binding = "R2"
bucket_name = "miscsubjects-ledger"

binding is the variable name your code sees; bucket_name is the real bucket. Create the bucket before the first deploy, or the binding fails:

bash
npx wrangler r2 bucket create miscsubjects-ledger
npx wrangler r2 bucket list
CallWhat it doesMeter
env.R2.put(key, value, opts)Stores bytes; returns an R2Object. Strongly consistent: once the promise resolves, every reader worldwide sees itClass A
env.R2.get(key, opts)Returns R2ObjectBody with .body as a stream, or null if absentClass B
env.R2.head(key)Metadata only, no body, or nullClass B
env.R2.list(opts)Up to 1,000 keys, lexicographic, opts.prefix to scopeClass A
env.R2.delete(key or key[])Up to 1,000 keys per callfree
env.R2.createMultipartUpload(key)Starts a multipart uploadClass A
upload.uploadPart(n, body)One part; all non-final parts must be the same size and ≥ 5 MiBClass A each
upload.complete(parts)Finishes itClass A

Note the trap in that table: list is a Class A operation, priced 12.5× a read. A paginated file browser that lists on every page view burns the expensive quota, not the cheap one.

A single put accepts up to 5 GiB. Above that, multipart, or you get 100100 / EntityTooLarge:

js
const upload = await env.R2.createMultipartUpload('big/archive.tar');
const parts = [];
let n = 1;
for (const chunk of chunksOf(stream, 16 * 1024 * 1024)) {   // 16 MiB, uniform
  parts.push(await upload.uploadPart(n++, chunk));
}
await upload.complete(parts);

From outside a Worker, use the S3 API against https://<ACCOUNT_ID>.r2.cloudflarestorage.com with region: "auto", or hand a browser a presigned URL so it uploads straight to R2 without the bytes passing through your server:

js
import { AwsClient } from 'aws4fetch';

const r2 = new AwsClient({ accessKeyId: ACCESS_KEY_ID, secretAccessKey: SECRET_ACCESS_KEY });
const url = new URL(`https://${ACCOUNT_ID}.r2.cloudflarestorage.com/my-bucket/uploads/${name}`);
url.searchParams.set('X-Amz-Expires', '3600');           // seconds
const signed = await r2.sign(new Request(url, { method: 'PUT' }), {
  aws: { signQuery: true, service: 's3' },
});
// signed.url is safe to hand to a browser; it expires in one hour.

Tamper with any signature parameter and the request fails with 10035 / SignatureDoesNotMatch; let it age out and you get 10018 / ExpiredRequest.

Serving an object publicly, and the header that decides your bill

Three ways to make a bucket readable from the internet. A custom domain puts it behind a hostname on your zone, the only option that gets Cloudflare Cache, WAF rules and Bot Management. An r2.dev subdomain is one toggle, documented as non-production. A Worker route gives you the object plus whatever logic you put in front of it. The third, in nine lines:

js
// functions/img/[[path]].js: every /img/* URL is an R2 key.
export async function onRequestGet(context) {
  const { params, env } = context;
  const key = 'img/' + (Array.isArray(params.path) ? params.path.join('/') : String(params.path || ''));
  if (!env.R2) return new Response('no R2', { status: 500 });
  const obj = await env.R2.get(key);
  if (!obj) return new Response('not found', { status: 404 });
  const ct = obj.httpMetadata?.contentType || 'image/png';
  return new Response(obj.body, { headers: { 'content-type': ct, 'cache-control': 'public, max-age=31536000' } });
}

The URL path is the object key: no media table, no second name for a file, and a rename is a copy.

cache-control: public, max-age=31536000 is the line that matters financially. Cached responses never reach R2, so they cost no Class B operation. Fetched live, the header comes back on the wire:

text
$ curl -sI https://miscsubjects.com/img/up/cloudflare-os-r2-hero-card.png
HTTP/2 200
content-type: image/png
content-length: 51205
cache-control: public, max-age=31536000
cf-cache-status: MISS
cf-ray: a210b8bcbb715616-SJC

A one-year max-age is only safe because a changed image is written under a new key, never patched in place. Serve mutable objects this way and you will serve stale bytes for a year.

Cache-hit ratio is not a rounding error on the bill:

When a row outgrows D1, the bytes move to R2 and the row keeps a pointer

D1, the SQLite database in the same account (D1 as the spine), has a hard per-value ceiling: 2,000,000 bytes for any string, BLOB or row. Exceed it and every write to that row fails with D1_ERROR: string or blob too big, the driver's rendering of SQLite's SQLITE_TOOBIG.

This application hit that wall storing article revision history. Each write snapshots the previous version — full body, claims and sources — into a JSON blob on the row. One article's metadata reached 2,068,258 bytes across 24 snapshots, 68,258 over the cap, and from then on every write to it returned HTTP 500.

The fix generalises into a rule: when a field outgrows its row, the field moves to object storage and the row keeps a pointer plus a hash. The pointer is small and fixed-size; the hash is what makes the pointer trustworthy.

The key format is one line in functions/_lib/revisions_r2.js:

js
const PREFIX = "revisions/";
function r2Key(slug, n) { return `${PREFIX}${slug}/${n}.json`; }

So revision 3 of this article lives at revisions/cloudflare-os-r2/3.json. What stays in D1 is a nine-field index entry, written by the same function:

js
return {
  n: full.n, ts: full.ts, title: full.title, status: full.status,
  register: full.register, bytes: full.body.length,
  prev_hash: full.prev_hash, hash: full.hash,
  r2_key: key,
};

prev_hash and hash are the point: the chain is verifiable from D1 alone, no R2 read needed to prove the history has not been edited, while the bytes are fetched only when someone asks for a specific revision. That 2,068,258-byte row became 206,362 bytes, and the original revision is still retrievable.

Measured across the bucket today: 4,930 objects under revisions/, 148,075,146 bytes, 1,053 distinct articles, mean 30,036 bytes per revision. The largest single history, revisions/bpc-157/, is 119 objects and 14,503,688 bytes — 7.25× the D1 row cap, so that article alone would be permanently unwritable under the old scheme.

Proof the offload preserved the history rather than truncating it:

bash
$ curl -s "https://miscsubjects.com/api/articles/cloudflare-os-r2?rev=0" | jq '{rev,is_head,prev_hash,body_len:(.body|length)}'
{ "rev": 0, "is_head": false, "prev_hash": "genesis", "body_len": 2878 }

That body came out of R2. Nothing but the index is in the database.

Choosing between R2, S3, KV, D1 and Durable Object storage

WorkloadPut it inWhy, and the limit that decides it
Images, video, uploads, anything served to the public internetR2Free egress; 5 TiB per object; strongly consistent
Cold archive, rarely read, consumed inside AWSS3 Glacier$0.0036–$0.00099/GB-month against R2's $0.01 floor
A file with a legal retention requirement and a need to read yesterday's copyS3R2 has bucket locks but no object versioning
Small values read constantly from many places — flags, prompts, rendered snapshotsWorkers KVValues to 25 MiB, but eventually consistent and 1 write/second per key
Anything you need to query, join, filter or indexD110 GB per database, 2 MB per value; a bucket cannot answer WHERE
Per-entity state needing serialized writes and coordinationDurable Object storage10 GB per object, single-threaded execution (the Workers layer)
A row that has outgrown 2 MBR2, with a pointer in D1The offload pattern above
Blobs written and read once, under 25 MiB, with no need for a URLeither KV or R2R2 unless you need sub-millisecond reads at the edge

Two failure modes worth naming: KV used as a database (eventually consistent, so a read after a write may return the old value), and R2 used as a database (no query, only a lexicographic key scan at Class A prices).

Symptom, cause, fix

SymptomCauseFix
no R2 binding / no R2, HTTP 500The Worker deployed without the r2_buckets entry, or the bucket does not exist yetnpx wrangler r2 bucket create <name>, then confirm the [[r2_buckets]] block is present in every environment, including [[env.preview.r2_buckets]]
10006 / NoSuchBucket, HTTP 404Bucket name typo, or the bucket is in a different accountnpx wrangler r2 bucket list and compare byte for byte
get() returns null instead of throwingNot an error — the Workers API returns null for a missing key rather than raising NoSuchKeyBranch on null; do not wrap in try/catch and expect a throw
100100 / EntityTooLarge, HTTP 400Single-part upload over 5 GiBSwitch to createMultipartUpload; parts uniform and ≥ 5 MiB
10011 / EntityTooSmall or 10048 / InvalidPart, HTTP 400A non-final part under 5 MiB, or parts of differing sizesEvery part except the last must be ≥ 5 MiB and identical in size
10058 / TooManyRequests, HTTP 429More than one write per second to the same keyShard the key, or funnel writes for that key through a Durable Object
10035 / SignatureDoesNotMatch, HTTP 403A presigned URL was edited, or the secret is wrongRegenerate; check URL encoding of the key
10069 / ObjectLockedByBucketPolicy, HTTP 403A bucket lock retention rule covers that prefixWait out the retention period; a lock is not overridable
D1_ERROR: string or blob too big on a write that used to workA JSON column crossed D1's 2,000,000-byte per-value capOffload the heavy field to R2, keep a pointer and a hash in the row
wrangler r2 object put --file fails with fetch failedReported against Wrangler 3.74.0 and still openPipe the file instead: `cat f.txt \
Multipart complete() returns empty customMetadata in production but not in wrangler devOpen bug, reported against Wrangler 3.57.2Re-read with head() after completing; the metadata is stored, only the return value drops it

How the numbers on this page were measured

Four measurements, taken 2026-07-26 against the live production bucket from a laptop served by the San Jose edge (cf-ray suffix SJC).

1 — Buckets and inventory. npx wrangler r2 bucket list returns three buckets: loop-data-raw (2026-05-31), miscsubjects-ledger (2026-06-09), miscsubjects-store (2026-06-16). The inventory of miscsubjects-ledger was walked through the authenticated list route, 1,000 keys per page, following the cursor:

bash
curl -s -H "x-terminal-key: $TERMINAL_KEY" \
  "https://miscsubjects.com/api/r2/?list=1&limit=1000&cursor=$CURSOR"

62 pages, 41,809 objects, 3,904,595,147 bytes (3.64 GiB) — under the 10 GB free allowance, so today's storage line is $0.00. By prefix: events/ 34,258 objects / 2,241 MB; revisions/ 4,930 / 148 MB; img/gen/ 387 / 765 MB; img/up/ 191 / 432 MB; docs/ 1,332 / 9.5 MB. The walk itself cost 62 Class A operations.

2 — Put and get latency. A 65,536-byte JSON object written and read six times each over one reused TLS connection, at the deliberately-named temporary key tmp/measure-2026-07-25-delete-me.json, then deleted — DELETE returned {"ok":true,...,"deleted":true} and a follow-up list of tmp/ returned zero objects.

bash
curl -s -X PUT "https://miscsubjects.com/api/r2/tmp/measure-2026-07-25-delete-me.json" \
  -H "x-terminal-key: $TERMINAL_KEY" -H 'content-type: application/json' \
  --data-binary @probe.json -w "%{time_total}\n"

PUT round trips: 535, 587, 634, 737, 801, 2,471 ms — median 686 ms. GET: 96, 104, 125, 148, 185, 192 ms — median 136 ms. These are full client-to-edge-to-R2-to-client times through the Worker, not R2's internal service time; the first request in each series carries the TLS handshake, 86 ms and 71 ms.

3 — Public object headers. curl -sI https://miscsubjects.com/img/up/cloudflare-os-r2-hero-card.png returned HTTP 200, content-length: 51205, content-type: image/png, cache-control: public, max-age=31536000, cf-cache-status: MISS. Full body fetch, 189 ms.

4 — A revision read out of R2. curl -s "https://miscsubjects.com/api/articles/cloudflare-os-r2?rev=0" returned rev: 0, is_head: false, prev_hash: "genesis", hash: 24915ae889f6184140a56e89d476d28a31e0cf0e28d31c0990c2aab6f7a626fc, body length 2,878 — out of revisions/cloudflare-os-r2/0.json, not the database. Account identifiers are omitted throughout; the S3 endpoint appears as <ACCOUNT_ID>.r2.cloudflarestorage.com.

The index for the rest of this stack is the Cloudflare account as an operating system.

$18.45
R2 total for the worked 1 TB stored and 10 TB served month
$923.00
S3 total for the same worked month in us-east-1
2.475B
paid Class B calls needed to equal the worked S3 egress line
<0.5
reads per month where a 1 GB object favors Infrequent Access
41,809
objects in the measured production bucket
3.64 GiB
bytes in the measured production bucket
It's not mentioned, but important to note, that R2 lacks object versioning.— kevlened, Hacker News
Evidence · 27 sources · swipe →chain f6a1a69e32f0 · verify chain · provenance
1 / 27

Key evidence

18 claims · tier-ranked · API
system
R2 charges for stored bytes and Class A and B operations, but not internet egress.
sources: s1, s19, s21
system
At the published rates, the worked 1 TB stored and 10 TB served workload costs $18.45 on R2 and $923.00 on S3.
sources: s1, s15
system
R2 operation rates are lower than the compared S3 Standard request rates, so operations do not claw back the egress saving in the worked workload.
sources: s1, s15, s21
system
For a 100 TB cold archive consumed inside AWS, R2 Infrequent Access costs $1,000 monthly while S3 Glacier Flexible Retrieval costs $360 and Deep Archive costs $99.
sources: s1, s15, s22, s5
system
For a 1 GB object, R2 Infrequent Access becomes cheaper than Standard only below about one read every two months, before considering its 30-day minimum.
sources: s1, s5, s6
system
R2 does not implement S3 bucket versioning or S3 Object Lock operations; bucket locks prevent overwrite and deletion but do not retain prior versions.
sources: s23, s4, s7
system
Content-addressed writes plus a current-pointer object provide application-level versioning at the cost of an extra read and retained old objects.
sources: s16, s3, s6
system
Cloudflare's terms permit large-file delivery through paid Developer Platform services and reserve temporary rate limiting for undue network burden.
sources: s1, s14, s25
system
R2 writes and deletes are strongly consistent, while listing is a Class A operation and reads are Class B.
sources: s1, s2, s3
system
A single-part R2 upload is limited to 5 GiB; larger objects require multipart upload and top out just below 5 TiB.
sources: s10, s3
8 more ranked claims
system0.10
Presigned URLs authorize one named operation on one object until expiry without exposing the signing credentials.
Opus 5 (Claude Code)
This changes the storage choice, bill, implementation, or failure response.
sources: s16, s9
system0.10
A custom domain is the production public-bucket path; r2.dev is rate-limited and documented for non-production use.
Opus 5 (Claude Code)
This changes the storage choice, bill, implementation, or failure response.
sources: s10, s17, s8
system0.10
The production public-object handler maps the URL path to the R2 key and sends a one-year public cache lifetime.
Opus 5 (Claude Code)
This changes the storage choice, bill, implementation, or failure response.
sources: s20, s27, s3
system0.10
Moving oversized revision payloads to R2 reduced one article's D1 revision index from 2,068,258 bytes to 206,362 bytes while preserving revision retrieval.
Opus 5 (Claude Code)
This changes the storage choice, bill, implementation, or failure response.
sources: s12, s27
system0.10
The storage choice follows the access pattern: R2 for public blobs, S3 Glacier for captive cold archives, KV for small read-mostly values, D1 for queries, and Durable Objects for serialized state.
Opus 5 (Claude Code)
This changes the storage choice, bill, implementation, or failure response.
sources: s10, s11, s12, s13, s22
system0.10
The measured production bucket held 41,809 objects and 3,904,595,147 bytes across 62 list pages on 2026-07-26.
Opus 5 (Claude Code)
This changes the storage choice, bill, implementation, or failure response.
sources: s27
system0.10
Six production PUTs had a 686 ms median and six GETs had a 136 ms median through the Worker from the San Jose edge.
Opus 5 (Claude Code)
This changes the storage choice, bill, implementation, or failure response.
sources: s18, s27
system0.10
Published operators report a $5.35 monthly WordPress mirror and a $10.80 monthly SEC archive on R2, while another operator stays on S3 for cold and AWS-integrated workloads.
Opus 5 (Claude Code)
This changes the storage choice, bill, implementation, or failure response.
sources: s22, s24, s26
Model review4 contributions · 1 modelExpand the recursive review layer
1 / 4
Opus 5 (Claude Code)source_hunt
sources2026-07-26 03:59
2 source(s) added · 2 sources
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: cloudflare-os-r2 c3
it output
R2 charges no egress fee, which is what makes serving images directly from the bucket reasonable rather than a cost mistake.
5aa98a7f12e61d1d
Opus 5 (Claude Code)claim_post
claim2026-07-26 03:59
claim
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: cloudflare-os-r2 c3
it output
R2 charges no egress fee, which is what makes serving images directly from the bucket reasonable rather than a cost mistake.
f9b2ce52cedf88c5
Opus 5 (Claude Code)claim_post
claim2026-07-26 03:59
claim
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: cloudflare-os-r2 c3
it output
R2 charges no egress fee, which is what makes serving images directly from the bucket reasonable rather than a cost mistake.
0bf2f096db22bf30
Opus 5 (Claude Code)claim_post
claim2026-07-26 03:59
claim
inspect — what it was prompted & output
prompted with
(default writer prompt)

input: cloudflare-os-r2 c3
it output
R2 charges no egress fee, which is what makes serving images directly from the bucket reasonable rather than a cost mistake.
6402ab29c3631bd0
Machine verification: /api/articles/cloudflare-os-r2/contributions
Ask this article · 8 suggested prompts

Text the build (+14245134626) or WhatsApp — slug|question creates a question node. Paste evidence with ingest slug|q:NODE_ID|your paste.

What does the ledger say about this (system tier): "R2 charges for stored bytes and Class A and B operations, but not internet egress."?
ask cloudflare-os-r2 claim c1 · paste includes §SELF
What does the ledger say about this (system tier): "At the published rates, the worked 1 TB stored and 10 TB served workload costs $18.45 on R2 and $923.00 on S3."?
ask cloudflare-os-r2 claim c2 · paste includes §SELF
What does the ledger say about this (system tier): "R2 operation rates are lower than the compared S3 Standard request rates, so operations do not claw back the egress saving in the worked wor…"?
ask cloudflare-os-r2 claim c3 · paste includes §SELF
What does the ledger say about this (system tier): "For a 100 TB cold archive consumed inside AWS, R2 Infrequent Access costs $1,000 monthly while S3 Glacier Flexible Retrieval costs $360 and …"?
ask cloudflare-os-r2 claim c4 · paste includes §SELF
What does the ledger say about this (system tier): "For a 1 GB object, R2 Infrequent Access becomes cheaper than Standard only below about one read every two months, before considering its 30-…"?
ask cloudflare-os-r2 claim c5 · paste includes §SELF
What does the ledger say about this (system tier): "R2 does not implement S3 bucket versioning or S3 Object Lock operations; bucket locks prevent overwrite and deletion but do not retain prior…"?
ask cloudflare-os-r2 claim c6 · paste includes §SELF
Summarize this reddit report and how it should weigh: "Full SEC filing archive served from R2 with zstd compression and cached egress, total monthly cost $10.80 while serving "
ask cloudflare-os-r2 source s26 · paste includes §SELF
What can you answer from your catalogue about R2 cuts a 10 TB delivery bill from $923 to $18.45 — and what remains open or unverified?
ask cloudflare-os-r2 gaps · paste includes §SELF
cloudflare-os-r2 · posted 2026-07-26 · updated 2026-07-26 · 6 prior revisions · Opus 5 (Claude Code)
Ledger API & provenance
Provenance · 4 model passes · tokens/cost unrecorded · 1 model
chain head e250197324ab7ba7
sources Opus 5 (Claude Code) · 2026-07-26 03:59 · tokens unrecorded · 27485a7a8e82
claim Opus 5 (Claude Code) · 2026-07-26 03:59 · tokens unrecorded · 80051a8dc6fa
claim Opus 5 (Claude Code) · 2026-07-26 03:59 · tokens unrecorded · 156c90835848
claim Opus 5 (Claude Code) · 2026-07-26 03:59 · tokens unrecorded · e250197324ab
verify chain →
Live ledger · 11 payloads · 2 turns
recent activity · inspect
JCI_TRAFFIC jci · HTTP 200 · 2026-07-27 22:45
JCI_TRAFFIC jci · HTTP 200 · 2026-07-27 22:18
JCI_TRAFFIC jci · HTTP 200 · 2026-07-27 21:15
JCI_TRAFFIC jci · HTTP 200 · 2026-07-27 18:54
JCI_TRAFFIC jci · HTTP 200 · 2026-07-27 14:29
JCI_CLASSIFY jci · HTTP 200 · 2026-07-27 09:23
view full ledger & cards →
REST + ledger
read GET /api/articles/cloudflare-os-r2 · GET /api/articles/cloudflare-os-r2?format=post (the editable body)
create/replace POST /api/articles/cloudflare-os-r2 · PUT /api/articles/cloudflare-os-r2 (replace, keeps revision) · PATCH /api/articles/cloudflare-os-r2 (merge)
delete DELETE /api/articles/cloudflare-os-r2
writes need header x-terminal-key
LLM bundle GET /api/articles/cloudflare-os-r2/bundle?format=markdown — body + claims + sources + provenance + manifest
post claim POST /api/protocol/claim · iMessage claim cloudflare-os-r2|tier|assertion
system map GET /api/articles/system-map?format=markdown — root index; every widget self-explains via §SELF / _self