Object storage · presigned in one call

The upload never touches a server of ours. Neither does the bill.

Ask for a presigned URL and hand it to the browser. The file goes straight to R2 — not through your worker, not through ours. Past the included 5 GB the extra draws from prepaid credits you loaded on purpose, not an invoice you find out about later.

5 GB included · $15/mo or $150/yr — two months free · 14-day free trial, cancel any time.
Storage is beta and we say so on the tin.
parsons · storagebkt_7c3a91 · avatars
browser
parsons storage
presign only
the file itself · browser → object store, direct
photo.jpg · 4.18 MB
→ {accountId}.r2.cloudflarestorage.com
not in that path: your worker · our workers · a key in the client
bytes through a Parsons server0
bytes to the object store4,182,304
what that changed on your bill$0.00
The fear, named first

Your object-storage bill is the part you can't predict.

Storage is rarely the only meter. There is one on the bytes you keep, usually one on the requests you make, and on the biggest platforms one on the bytes going out. You can price the first. The other two arrive after the month is already over.

reconstruction · a publicly reported S3 egress incident, June 2020not a Parsons customer
stored objects — the line you budgeted for$23
one file went viral · 30.6 TB out of the bucket, every gigabyte billed+ $2,634
the month, as it actually arrived$2,657
parsons storage · basesame month
$15.00
The file going viral does not appear on this statement, because there is no line for it to appear on. Egress is not billed. Class A and Class B operations are counted so you can see them, and never billed. Past 5 GB, stored bytes draw from prepaid credits at $0.025/GB-month — money you put there first, never a line you discover.
from $23 to $2,657
a public write-up · 30.6 TB of S3 egress over two days, June 2020
a $1,300+ bill on an empty bucket
~100M PUT requests in a day · an open-source tool’s default bucket name, 2024
four price tiers to the internet alone
AWS S3 data transfer out, published list price, checked August 2026 · before CloudFront, cross-region and acceleration are counted separately

These are reconstructions of publicly reported incidents, not Parsons customers. The 2020 figure is a bill that was published in full; the 2024 one is a request-charge incident widely written up at the time. Neither is a claim about any vendor's current rates — for those, see the comparison further down, which uses published list prices checked August 2026.

The second tax

An afternoon on IAM, just to accept a photo.

Bucket policy. Signing principal. Access key. Somewhere safe to keep the access key. A plan for rotating the access key. A CORS document. Then the preflight fails and you debug that instead.

The whole of that is one tool call here, and the reason it works in a browser afterwards is the part nobody mentions: a CORS policy is applied to every bucket at creation — GET, PUT and HEAD, with ETag exposed so your uploader can read it back. A missing CORS document is the single most common reason direct-to-store upload fails elsewhere.

create_bucket · list_buckets · get_bucket
  1. 1create the bucket
  2. 2write the bucket policy JSON
  3. 3write the IAM policy for the signing principal
  4. 4create an access key
  5. 5put the access key somewhere the server can reach
  6. 6plan how you will rotate it
  7. 7write the CORS document
  8. 8apply it, then debug the preflight
  9. 9work out whether you also need a CDN in front
what is left
storage__create_bucket { name: "avatars" }
→ { id: "bkt_7c3a91",
    physicalName: "p-proj_a41f8c2e-avatars" }
✓ CORS applied · GET, PUT, HEAD · ETag exposed
·Names are 3–63 lowercase characters, and every bucket is physically created as p-{projectId}-{name}. Two of your projects cannot collide on a name, because the other project's bucket is not merely forbidden to you — it is not nameable by you.
The turn

We are not a storage company. That is why you can have a ceiling.

Every storage vendor in the table further down earns its revenue on a meter of one kind or another. That is not a criticism of their pricing pages, which are honest — it is a description of their business. A company whose income is the meter cannot sell you a ceiling, because the ceiling is the income.

per gigabyte stored
meter 01
per operation
meter 02
per gigabyte out
meter 03
$15 / account / month
…and the same $15 also bought the auth, the database,
the deploy, the email and the scheduler.

Storage is one line item inside a platform subscription. So the honest thing for us to sell is 5 GB inside the $15, and overage you prepaid on purpose — past the allowance, stored bytes draw from credits at $0.025/GB-month, roughly wholesale-plus-half, and nothing is deleted. A hard safety cap sits far above as a fraud stop, with an ask-us-to-raise-it path in the billing console. Well above that we are not your storage provider, and we say so further down this page.

The mechanism

A presigned URL is a deadline and one key.

Not a credential, not a session, not access to your bucket. It is a link that permits exactly one method, against exactly one object path, until exactly one moment — and then it is a 403.

presign_upload → the URL your browser receivesmethod: PUT
https://{accountId}.r2.cloudflarestorage.com/p-proj_a41f8c2e-avatars/u/91/photo.jpg
  ?X-Amz-Algorithm=AWS4-HMAC-SHA256
  &X-Amz-Expires=900 ← counting down: 14:59
  &X-Amz-SignedHeaders=host ← the whole answer to "what is signed"
  &X-Amz-Signature=
at 00:00, without any call to us:PUT → 403 · signature expired
default expiry
900s
Fifteen minutes. A URL found in a log tomorrow is a URL that stopped working yesterday.
maximum expiry
604,800s
Seven days, and that is the outside edge. You ask for the number; anything longer is refused.
scope
one key
The signature covers a single object path. It cannot be repointed at another file, or at another project's.
credential in the browser
none
The client receives a URL. It never receives an access key, and there is no key for it to leak.

The bytes travel to {accountId}.r2.cloudflarestorage.com directly — not through your worker, and not through ours. We mint the URL and then we are not in the conversation.

The part nobody in this category prints

What that signature covers — and what it does not.

This page used to claim a size ceiling was baked into the signature. It is not, so the claim is gone. Here is the real split, and you should hold the rest of this page to the same standard.

signedenforced by the store
hostThe one header in X-Amz-SignedHeaders. It pins the URL to the object store.
bucket + keyOne physical bucket, one exact object path. Nothing else is reachable with it.
methodA PUT URL uploads. A GET URL downloads. They are separate calls and separate URLs.
expiryThe deadline is inside the signature. It expires with no help from us.
not signedyour client's job
content-lengthpresign_upload hands back the size ceiling you asked for so your client can reject an oversize file before the PUT. The URL itself does not enforce it.
content-typeEchoed back to you, never validated. There is no content-type allowlist.
the bodyThe canonical request is signed with an unsigned payload. What you send is what lands.
The quota is sustained-overage protection, not a byte-exact wall
Usage is reconciled on a 15-minute cron, and the bytes land in the store directly after the presign — so a burst can cross the line between runs. We would rather tell you than have you discover it.
Storage is beta
It is listed as beta in the platform catalog, which every other Parsons surface reads. Hiding that on a page selling you safety would be a strange way to earn it.
Every product in this category has limits like these. Ours are here, above the fold of the sell rather than in a doc you find afterwards. Hold the rest of this page to that standard.
Three doors, one bucket

Your AI, your backend, or no HTTP at all.

The same objects, the same listing, the same quota. Pick the door that matches where the bytes already are.

Eleven named tools on one connectiontool calls · uncapped, unbilled
storage__presign_upload { bucket: "bkt_7c3a91", key: "u/91/photo.jpg" }
→ { url: "https://…&X-Amz-Expires=900", expiresAt: "2026-08-22T14:17:11Z" }

The project is the connection's pinned project — never a tool argument, so it cannot be passed wrong. Each call re-checks that you still own that project. And because MCP tool calls are uncapped and never billed, driving Storage from your assistant costs nothing per call.

works in ClaudeChatGPTCursoranything that speaks MCP

Listing, quota and metering see all three. They are not three systems with a sync problem — they are three doors into the same bucket.

The cross-product edge

The bucket shows up already wired to the app.

Deploy an app with storage provisioned and four things happen in one step: the bucket is created, bound as env.FILES, its id injected as env.PARSONS_BUCKET_ID, and registered into Storage so it appears in list_buckets and in the console alongside the ones you made by hand.

Then the part that matters more: deleting the app releases the binding and keeps the data. And a bucket that is a live app's env.FILES cannot be deleted at all — delete_bucket refuses outright, which is a stronger guard than the must-be-empty rule.

list_buckets returns physicalName — one call maps a bucket to what deploy calls it
provisioning R2p-proj_a41f8c2e-uploads
bindingenv.FILES
injectingenv.PARSONS_BUCKET_ID=bkt_7c3a91
registered in Storagevisible in list_buckets
what delete_app does not do
delete the bucket
Your objects are not collateral damage in a redeploy or a teardown.
what delete_app does
releases the binding
The data stays, listed and downloadable, ready to bind to the next app.
A storage vendor who does not also own your compute cannot ship this step. That is the whole of the advantage, and it is not a large one — it is just genuinely ours.
What happens when you get it wrong

Refusals you can actually read.

For anyone driving storage from an agent, the error message is the product. A round trip through an AI costs real time, so a refusal that does not tell you the fix costs you twice. Pick a bad input and see what comes back.

storage__presign_upload · key sanitisationevery mutating call is audit-logged
key ›
actor user idbucketactiontimestamp→ audit log

Key sanitisation is a rejection, not a silent rewrite: no .., no leading slash, no backslash, no C0 or C1 control characters, 1,024 characters at the outside. A traversal is impossible earlier rather than cleaned up later — and you are told which rule you hit. The fourth chip is the one worth clicking: pass a bucket name where an id is required and the refusal names the correct bkt_ id and tells you to re-send.

The one nobody else will say out loud

A failed card stops you spending. Not reading.

When a subscription lapses, three calls refuse and three deliberately keep working. Your files do not become hostages while you update a card.

tool
active
past due
create_bucket
allowed
refused
presign_upload
allowed
refused
set_public_prefix
allowed
refused
list_objects
allowed
still allowed
presign_download
allowed
still allowed
delete_object
allowed
still allowed
And the same argument from the other end. When an account is deleted, the purge path drains the actual objects and deletes the actual buckets, reports partial failure honestly so it can be retried, and finishes. "We deleted your data" is a sentence we can say because something in the code does it.
The control plane is the tool list

There is no admin UI to learn, because the tool list is the interface.

Storage's write path is eleven MCP tools. Make a bucket, hand out a 24-hour upload link, page a prefix, clear out the test files — from the assistant you already have open. There is a console further down this page, and it is read-only on purpose: nothing you do here has to be done there. MCP servers are table stakes by now; what is not table stakes is that this is the interface rather than a companion to one.

one connection · minted for you at sign-inGET YOURS
create_bucketlist_bucketsget_bucketdelete_bucketpresign_uploadpresign_downloadlist_objectsdelete_objectset_public_prefixwhoamidocs

Docs once: /docs, /docs/quickstart.md and /docs/api.md serve markdown that is byte-identical to what the docs tool returns. What your AI reads and what you read cannot drift.

Make a bucket for customer invoices and give me a 24-hour link to upload this month's.
storage__create_bucket { name: "invoices" }
→ { id: "bkt_2f81aa", physicalName: "p-proj_a41f8c2e-invoices" }
storage__presign_upload { bucket: "bkt_2f81aa", key: "2026-08/inv.pdf", expiresSec: 86400 }
→ { url: "https://…&X-Amz-Expires=86400", expiresAt: "2026-08-23T14:02:11Z" }
Bucket invoices is up. Here's a PUT link for 2026-08/inv.pdf, good until tomorrow at 14:02 UTC.
Now list what's in the avatars bucket.
storage__list_objects { bucket: "avatars" }
→ ✕ refused · "avatars" is a bucket NAME, not a bucket id. Re-send with bucket: "bkt_7c3a91".
storage__list_objects { bucket: "bkt_7c3a91" }
→ { objects: [ …100 keys ], truncated: true, nextCursor: "…" }
412 objects under avatars. Want them by prefix?
The refusal in the middle is the point: it named the right id, so the model fixed itself without asking you anything.
Seeing what you are storing

The bucket list you went looking for.

A recurring complaint about raw object stores is that people cannot see what is in them. Here is the console — the real column names, the real quota tile, and an inline gallery of every image you have stored.

project · side-projectstorage.parsons.ai/appread-only
namephysicalNameobjectCountsizeBytesboundAppIdcreatedAt
avatarsp-proj_a41f8c2e-avatars4121,204,882,4312026-06-02
uploadsp-proj_a41f8c2e-uploads1,2042,776,410,090app_7d1c2026-07-14
invoicesp-proj_a41f8c2e-invoices38142,900,1122026-08-22
storage_gb · reconciled every 15 minutes · account-wide2.06 GB of 5 GB · Base
objects · per project · flat anti-abuse ceiling1,654 of 10,000
image gallery · thumbnails via a session-gated 302 to a 300-second presigned GET
u/91/photo.jpg
u/104/avatar.png
u/12/header.webp
u/77/scan.jpg
u/38/logo.png
u/205/shot.jpg
The console is read-only. It shows, it does not upload and it does not delete — creating buckets and moving files happen through your AI, your backend or your app. It never holds a key and never proxies a byte: each thumbnail is the same presigned-GET mechanism the rest of this page just explained. Buckets a deploy created appear here automatically, alongside the ones you made by hand.
Straight about the edges

Where we lose, printed rather than omitted.

You have already read four comparison tables today and all four were won by the vendor who drew them. Here are the rows where we lose, kept in.

Parsons StorageCloudflare R2Backblaze B2UploadThing
What 10 GB costs you$15/mo + $0.13 of prepaid credits — not sold on its ownfree at 10 GB, then $0.015/GB-monthfree at 10 GB, then $6.95/TB-month2 GB free · $10/mo buys 100 GB
Above your allowanceprepaid credits · $0.025/GB-month, hard safety cap far abovebills you · no ceilingbills you · no ceiling$0.08/GB over included, on the usage-based plan
Egressnot a line itemfreefree up to 3× what you store, then $0.01/GBincluded in the plan · unlimited downloads
Per-operation chargescounted, never billed$4.50/M class A · $0.36/M class B, above a free allowanceclass A, B and C calls freenone published
Petabyte scalenoyesyesyes
S3-compatible APInoyesyesno
Hosted public file servingno — your worker servesyesyesyes
Image transforms & CDNnovia Cloudflareyes
Lifecycle rules & storage classesnoyesyes
Bound natively into your deployed appyes · env.FILESyes, on Workersnono
Readable while your card is past dueyes
Also buys auth, database, deploy, email, scheduleryesnonono
If all you need is a bucket, use Cloudflare R2 or Backblaze B2. They are cheaper per gigabyte than we will ever be — R2 lists $0.015/GB-month, B2 lists $6.95/TB-month, and both hand you the first 10 GB for nothing, against our $15 — and UploadThing will sell you twenty times our included capacity for $10 a month. On storage alone we are not the cheap option, and we are not going to pretend otherwise. Come here when you want the bucket wired to the app, the auth, the database and the AI on one bill — one subscription, unlimited projects, no per-seat line — and when you would rather prepay the overage on purpose than be told the total afterwards.
No hosted public serving, and no files. CDN
set_public_prefix records which prefixes opted in. It does not serve them — your app's worker does, from the bound bucket.
and a caveat you must carry
If you serve user-uploaded text/html or image/svg+xml, force-download or sandbox it. There is no content-type allowlist on the way in, so the safe headers on the way out are yours to apply.
5 GB included is small, and it is account-wide
Past it, overage draws from prepaid credits at $0.025/GB-month. Far above that we are not your storage provider, and no amount of copy changes it.
use instead
R2 or Backblaze B2. Both are built for volumes we are not built for, and both are honest about their pricing.
10,000 objects per project, flat
It is an anti-abuse bound rather than an allowance — ask us to raise it in the billing console if you genuinely need to — and on the REST reconciliation path it is best-effort.
plan around it
Lots of small objects is the shape that hits this first. If your app writes a file per event, this is the number to check before you build on us.
No image transforms, thumbnails, lifecycle rules or content moderation
This is object storage, not a media pipeline. Those are listed as later, and later is not a shipping date.
also true
The console's gallery renders images inline, but that is a viewer — it does not resize, convert or optimise anything for you.

Competitor rows are published list prices and capabilities taken from each vendor’s own pricing page, checked August 2026: developers.cloudflare.com/r2/pricing, backblaze.com/cloud-storage/pricing, uploadthing.com/pricing. Where a vendor does not publish the answer we say so, or leave a dash, rather than filling it in.

The part that is sold nowhere and should be sold everywhere

Unlimited projects. No extra cost.

A project is a hard wall: its own buckets, its own members, its own spend cap, its own MCP connection. One for the business, one for the client, one for the thing you are only half serious about. The price does not move.

personal
1 bucket · 0.2 GB
members 1
acme-storefront
3 buckets · 1.0 GB
members 4
client-work
2 buckets · 0.7 GB
members 2
the-side-thing
1 bucket · 0.1 GB
members 1
$15/month, total.
Not per project. Not per seat.
and the honest correction: the gigabytes are pooled2.06 GB of 5 GB, across all four
A second project does not give you a second 5 GB. Projects cost you organisation, not capacity — which is exactly why they cost nothing extra. Plenty of platforms gate this behind a workspace tier or a per-seat line.
Pricing

One plan. It covers everything.

Storage is not sold on its own. The subscription is the account, and the account is every Parsons product across unlimited projects. Going past an included allowance simply draws from prepaid credits at roughly wholesale-plus-half — the bill starts when real usage does.

Base
$15 / month
or $150 / yeartwo months free, against $180 paid monthly
  • 5 GB of objects — account-wide, not per project
  • 10,000 objects per project — a flat anti-abuse bound
  • Egress not billed — serving what you store is not a line item
  • Class A and Class B operations counted, never billed
  • Presigned upload and download, 900s default, 7 days maximum
  • Buckets bound into deployed apps as env.FILES
  • Every other Parsons product, one login
Start your trial
What the same $15 also coversBase allowances, other products
Authmonthly active users — uncapped
MCP Hubtool calls — uncapped, free
Deploy1,000,000 requests · 1 GB app storage
Database1 GB · 100,000,000 rows read
Email200 sent a day
Scheduler20,000 fires
Tracker10,000 tasks
CRM50,000 contacts
Notifications200 email messages
Radar100,000 checks
Money2 bank connections
Phone120 voice minutes
one account · unlimited projects · no per-seat line$15.00 / month · or $150.00 / year
01 · no meter on top of a meter
Your objects sit in Cloudflare R2, on our account — the same store this page names in the comparison and prints in the URL. What we do not do is put a second meter on top of it. Our margin is the subscription, not your gigabytes, which is exactly why a ceiling is something we can afford to sell you.
02 · what the $15 is actually buying
Not the cheapest bucket — the table above says so in our own hand, and a dedicated uploader will start you free and sell you more gigabytes for less. What the $15 buys is everything attached to the bucket: the same $15 is storage and auth, database, deploy, email, scheduler, CRM, notifications and the rest, across unlimited projects, with no per-seat line and no workspace tier to climb. A CRM on its own runs $48–$99 a month. Per product you can usually find something cheaper. Per stack, nothing is close.
03 · the subscription is not hiding a meter
The genuinely expensive things — AI tokens, voice minutes — are prepaid and capped separately, per project, and they cannot run without money you put there first. Anything carrying its own recurring price, like a phone number, is a separate line you approve before it is added. $15 is the access fee, not a loss-leader with a bill behind it.

14-day free trial on your first subscription · monthly or yearly, chosen at checkout · cancel any time.
Yearly is the same account for two months less: $150 instead of $180.
Nothing here is free beyond the trial: no tier below Base, no per-seat line, no enterprise tier. The trial does take a card — we would rather charge you $15 than bait you onto a meter.

Questions people actually ask

The awkward ones, answered flatly.

Do my files pass through your servers?
No. We mint a presigned URL and the browser talks to the object store directly — not through your worker, and not through ours. We hand out a URL; we never see the bytes. See the URL, dissected.
Can the presigned URL be used to upload something enormous?
The URL does not enforce size. presign_upload hands back the ceiling you asked for so your client can reject an oversize file before the PUT, but the signature covers the host header and nothing else. This page used to claim otherwise and the claim is gone. See what is signed.
What happens when I hit 5 GB?
Nothing is refused and nothing is deleted — the extra draws from prepaid credits at $0.025/GB-month, roughly wholesale-plus-half, money you loaded on purpose. A hard safety cap sits far above as a fraud stop, with an ask-us-to-raise-it path in the billing console. The check is against usage reconciled on a 15-minute cron, so treat it as sustained-overage protection rather than a byte-exact wall.
Can you serve my files publicly?
Not yet. set_public_prefix records which prefixes have opted in; your app's worker serves the bytes from the bound bucket. A hosted CDN mode is designed and not built. If you serve user-uploaded text/html or image/svg+xml, force-download or sandbox it — there is no content-type allowlist on the way in. See where we lose.
Is there an S3-compatible API?
No. Three doors: eleven MCP tools, the /v1/storage/* REST API with a sk_live_… project key, or the native env.FILES binding inside a deployed app. If S3 compatibility is a hard requirement, use R2 or B2. See the three doors.
What if my card fails?
Read-only, not locked out. Creating buckets, presigning uploads and setting public prefixes refuse; listing, downloading and deleting keep working. That is a decision in the code, not an oversight. See the matrix.
What if I delete my account — do the files actually go?
Yes. The purge path drains the objects, deletes the buckets and removes the registry rows, reporting partial failure honestly so it retries until it is finished. It is the one claim in this category worth checking, so we built the thing that makes it true.
Does driving it from Claude or ChatGPT cost extra?
No. MCP tool calls are metered for visibility and are uncapped and unbilled, so there is no per-call cost to using your assistant as the interface. One connection is minted for you at sign-in and pinned to a project — the project is never a tool argument. See the tools.
How many projects can I make?
As many as you like, at no extra cost — each with its own buckets, members and spend cap. The gigabytes are pooled account-wide, so a second project does not give you a second 5 GB. Projects are organisation, not capacity, which is why they add nothing to the bill. See the grid.
Is this really beta?
Yes. Storage is listed as beta in the platform catalog that every other Parsons surface reads, and we are not going to hide that on a page whose whole argument is that we tell you things. What beta means here: the shape is settled and shipped, and the follow-ups — hosted public serving, transforms, lifecycle rules — are not.
Image resizing, thumbnails, lifecycle rules, content moderation?
None of them. This is object storage, not a media pipeline. They are listed as later, and later is not a date. The console renders image thumbnails inline, but that is a viewer, not a transform.

Hand over a link. Not your bucket.

One call gets a URL that can do exactly one thing, for fifteen minutes, and nothing else ever — and the month it happens in still costs $15.

$15/mo or $150/yr · 14-day free trial, cancel any time · Storage is beta.