Skip to content

Recording storage vendor decision: Cloudflare R2, with the full cost, compliance and Stream-compatibility research #1314

Description

@teetangh

Where do we permanently store call recordings? Today Stream holds the only copy and deletes it after 14 days, and the transfer pipeline that was meant to rescue them has never run. This issue is the vendor decision, researched across cost, technical fit, and the Indian tax/compliance picture, with a provenance ledger so a reader can tell what is verified from what is not.

Recommendation: Cloudflare R2, with AWS S3 ap-south-1 + CloudFront Pro as runner-up, and DigitalOcean Spaces BLR1 if provable India residency ever becomes a hard requirement.

Three findings that change the framing, all verified first-hand:

  1. The 191 Recording rows are seed data. Every recordingUrl is a faker domain (prisma/seedFiles/6a-create-appointments.ts:165faker.internet.url()); streamRecordingId, fileSize, storagePath and streamUrlExpiresAt are NULL on all 191 rows; zero RecordingPurchase rows exist. Nothing has ever been recorded, transferred, published or sold. This is a greenfield choice with no migration and no data-loss emergency — and it invalidates the framing in Stream production-readiness: everything found, bucketed by launch stage #1280 and Handoff — Stream subsystem, 2026-09-01: seven PRs shipped, what is left, and what not to rediscover #1299, which read "191 recordings, 0 transfers" as evidence the pipeline never ran on real recordings.
  2. Stream can write directly to any S3-compatible store. s3_custom_endpoint_url is in Stream's public OpenAPI spec (video-openapi.yaml:9067), not just the SDK. The docs listing only S3/GCS/Azure and getstream/protocol#371 — where our own 2025-02-15 question still sits unanswered — are both stale. There is also a GET /video/external_storage/{name}/check endpoint that settles the whole question in five minutes.
  3. Two independent 500 MB ceilings would reject a real recording todayMAX_TRANSFER_SIZE at lib/stream/recording-transfer-service.ts:27, and the live recordings bucket's file_size_limit = 524288000. Both must be raised before we record anything for real, regardless of which vendor wins.

The corollary on the Supabase question: the 50 MB object cap is a Free-plan limit, not a product limit — Pro allows 500 GB objects. So "all-Supabase" was never infeasible, and it is not rejected on cost either (at today's zero playback the spread is ~$25/month). It is rejected on structure: storage egress shares one quota with Database, Auth and Realtime, so a popular replay can throttle the application; and per-buyer signed URLs guarantee CDN cache misses, so the paywall and the cost model fight each other.

Scope note: this issue is the decision and its evidence. Implementation is the checklist in §10. Related: #1280 (Stream production-readiness), #1299 (Stream handoff), #366 (replay marketplace), #1312 (post-release remaining work).


Recording storage: vendor decision

Research date 2026-09-01. Every price is from a page fetched on that date. Provenance for every material claim is in §8 — read it; some of this report is first-hand verified and some is single-sourced, and the difference matters.


1. TL;DR / recommendation

🥇 Primary: Cloudflare R2

Zero egress eliminates an entire class of risk permanently — no fair-use ratio to breach, no tier cliff, no bill shock when a replay goes viral, and free exit, which is the cheapest available insurance against this decision being wrong. It's ~13× cheaper than Supabase at Scale C ($150 vs $2,010/mo), Stream can write to it directly, it speaks the AWS SDK already in the repo, and R2 is explicitly the Cloudflare product sanctioned for serving video.

The decisive reason: it makes the egress question permanently unaskable. For a small team, "this line item can never surprise us" is worth more than the $80/month it saves over the next option.

🥈 Runner-up: AWS S3 ap-south-1 + CloudFront Pro ($15/mo)

The only option where Stream authenticates by IAM role — no long-lived secret handed to a third party. Real India edge presence, signed URLs on every tier, 50 TB egress for $15 flat with a written no-overage guarantee, and verified INR billing through AWS India. At $264/mo at Scale C it's only ~$114 above R2.

Do not adopt AWS S3 without CloudFront — S3 direct-to-internet at $0.1093/GB is the worst option modelled at Scale C ($2,188), worse than Supabase.

🌏 If provable India residency matters more than ~$200/month: DigitalOcean Spaces BLR1

The only true in-India object store here. $5/mo covers Scale A entirely; $0.01/GiB egress after 1 TiB included; S3-compatible so Stream writes directly; a GSTIN shifts you to recoverable reverse charge. Loses to R2 only on Scale-C cost ($390) and an 800 ops/s ceiling.

Does "stay all-Supabase" survive? No — but not on cost.

It survives comfortably on cost today: at Scale A the gap is ~$24/month, playback is currently zero, and we're keeping Supabase Pro regardless (Postgres, Auth, and the 500 GB object cap). "All-Supabase" was never a cost saving. It fails on three structural grounds:

  1. Correlated failure. Storage egress shares one quota with Database, Auth, Realtime and Edge Functions. With Spend Cap ON, one popular replay can throttle the entire application; with it OFF, it bills uncapped. Video traffic and transactional API traffic should not share a failure domain. This holds even if playback never takes off.
  2. The paywall defeats the cache. Per-buyer signed URLs are the product, and they guarantee CDN cache misses — we pay $0.09/GB and never the $0.03 cached rate. Supabase is the only candidate where the security model and the cost model actively fight each other.
  3. Direct-write is unverified and may be impossible (§3), leaving us maintaining a transfer pipeline that — as §2 shows — cannot actually carry our files.

2. Current state, and why the decision is forced now

There are no real recordings

Queried against the live database (pzmbxqdgibfkhjwzeprf, the only ACTIVE_HEALTHY familiarise project, region ap-south-1):

select count(*) total,
       count(*) filter (where "storageType"::text='STREAM_S3') on_stream,
       count(*) filter (where "transferredAt" is not null) transferred,
       min("recordedAt"), max("recordedAt"),
       round(avg("durationInMinutes")) avg_minutes, count("fileSize") have_filesize
from "Recording";
-- 191 | 191 | 0 | 2025-06-12 | 2026-06-12 | 110 | 0

But:

select "status"::text, "listingStatus"::text, count(*), min(left("recordingUrl",60))
from "Recording" group by 1,2;
-- READY | DRAFT | 191 | https://acclaimed-schedule.com/

https://acclaimed-schedule.com/ is faker.js output, confirmed against prisma/seedFiles/6a-create-appointments.ts:165recordingUrl: faker.internet.url().

Field Populated (of 191)
streamCallId, streamRecordingId, storagePath, fileSize, streamUrlExpiresAt 0 each
status = 'AVAILABLE' 0 (all READY)
publishedAt 0
RecordingPurchase rows 0

A Stream API sweep (video_query_calls, calls since 2026-06-01) returns only slot-mock0801-*, demo0813-*, smoke-test-nonexistent and seeded slot-* calls — every one with "recording": false.

The 191-row corpus is seed data. Nothing has ever been recorded, transferred, published, or sold.

Consequences:

  1. No migration, no data-loss emergency. Stream retains recordings "for two weeks before being automatically deleted." The newest row is dated 2026-06-12 — 81 days ago. Had these been real they'd already be gone. This is a greenfield choice.
  2. The 110-minute average is a faker artifact, not a measurement. We keep ~1 GB as the planning number because it's conservative, but the justification is different (§4).
  3. fileSize being NULL everywhere is not a bug to fix — no recording ever existed. But nobody can currently measure real sizes, so the first real recording must be instrumented.

The current code cannot handle real recordings — two independent 500 MB ceilings

  • lib/stream/recording-transfer-service.ts:27 sets MAX_TRANSFER_SIZE = 500 * 1024 * 1024 and hard-fails anything larger, recording a transfer failure. At 110-minute HD sessions most recordings exceed this. The existing pipeline would reject them even if it ran.
  • The live recordings bucket independently carries file_size_limit = 524288000 (500 MB), mime-restricted to video/mp4|webm|quicktime, audio/webm.

So a real recording would be rejected twice — once by the transfer service, once by the bucket. Direct Stream→bucket writes sidestep the first entirely; a transfer-pipeline architecture does not.

The good news: the vendor seam is already isolated

  • lib/stream/recording-storage.ts is 123 lines and is the only module naming Supabase (RECORDINGS_BUCKET = "recordings", supabaseAdmin.storage.createSignedUrl).
  • The Prisma enum is already vendor-neutral: RecordingStorageType = STREAM_S3 | PLATFORM.
  • Total Supabase Storage usage across all 8 buckets today is ~14 MB. There is no migration cost from the status quo.

The module's own header comment already anticipates this work: the transfer service is "scheduled for deletion once Stream writes recordings straight into our bucket via recording_external_storage", and warns that getBestRecordingUrl must move first or "shipping the storage change without moving it first would take the marketplace down with the pipeline."

Why now: we are about to record for real, both ceilings will reject those files, and the decision is cheapest to make while there is nothing to migrate.


3. The Stream-compatibility finding

Stream can write directly to any S3-compatible store. This is in the public API contract, not a feature request.

Verbatim from Stream's own OpenAPI spec — GetStream/protocol/main/openapi/video-openapi.yaml, line 9067:

S3Request:
  description: Config for creating Amazon S3 storage.
  properties:
    s3_api_key:
      description: 'The AWS API key. To use Amazon S3 as your storage provider,
        you have two authentication options: IAM role or API key. If you do not
        specify the `s3_api_key` parameter, Stream will use IAM role authentication.'
    s3_custom_endpoint_url:
      description: The custom endpoint for S3. If you want to use a custom endpoint,
        you must also provide the `s3_api_key` and `s3_secret` parameters.
    s3_region:
      description: The AWS region where the bucket is hosted
    s3_secret:
      description: The AWS API Secret
  required:
  - s3_region

CreateExternalStorageRequest (line 4375) requires name, storage_type (s3 | gcs | abs) and bucket; optional path, aws_s3, azure_blob, gcs_credentials. Max 10 storage configs per app.

Corroborated by the docs: "To use Minio or other S3 compatible storage you can specify custom endpoint. In this case API key and secret are required."

There is a validation endpoint — this settles every question below in five minutes

GET /video/external_storage/{name}/check (operationId: CheckExternalStorage) uploads a test file to the bucket and errors if it fails.

Why discussion #371 looks like it says the opposite

GetStream/protocol #371, pulled via the GitHub GraphQL API:

  • Opened 2024-01-29 by Stream. Body carries 13 May 2015: Added support for S3 compatible storage (eg. minIO)2015 is a typo; the thread didn't exist until 2024.
  • Not marked answered. Every provider-specific question is unanswered:
    • [2024-05-15] viscafcb96: Can i use Digital Ocean Space or Cloudflare R2 as a storage?
    • [2025-02-15] teetangh: how does one store the recordings in supabase ?we asked this 18 months ago; no reply
    • [2026-08-06] sardor01: Any plans to support cloudflare?
  • Stream's changelog has no entry for this feature.

The capability is real and documented; Stream has simply never publicly confirmed a specific third party.

The addressing-style trap — and why Supabase direct-write is a coin-flip

S3Request has no force_path_style field.

Store Virtual-hosted Path-style Works with Stream?
AWS S3 ✅ native, IAM role, no static key
Cloudflare R2 both — safest bet
B2 / Wasabi / DO Spaces / Tigris ✅ likely
Supabase Storage impossible ✅ only ⚠️ unverified

Supabase was tested empirically rather than guessed:

$ openssl s_client -connect pzmbxqdgibfkhjwzeprf.storage.supabase.co:443 ... -ext subjectAltName
X509v3 Subject Alternative Name:
    DNS:storage.supabase.co, DNS:*.storage.supabase.co

$ curl https://pzmbxqdgibfkhjwzeprf.storage.supabase.co/storage/v1/s3   → http=403 (reachable)
$ curl https://recordings.pzmbxqdgibfkhjwzeprf.storage.supabase.co/...  → curl: (35) sslv3 alert handshake failure

A TLS wildcard matches exactly one label. *.storage.supabase.co covers <ref>.storage.supabase.co but not <bucket>.<ref>.storage.supabase.co. Virtual-hosted addressing against Supabase is cryptographically impossible. Supabase's docs confirm the requirement — forcePathStyle: true with endpoint https://project_ref.storage.supabase.co/storage/v1/s3.

The counter-inference: Stream documents MinIO support, and MinIO is path-style-only — so Stream almost certainly does force path-style for custom endpoints. But Supabase adds a second, unusual hurdle: a path prefix (/storage/v1/s3) that the client must preserve under SigV4 signing. Some S3 clients mishandle prefixed endpoints.

s3_custom_endpoint_url exists (certain). R2 works (high confidence — dual addressing removes the only real risk). Supabase is a coin-flip that costs five minutes to resolve.

Static-credential caveat

Custom endpoints require static s3_api_key + s3_secret — a long-lived credential handed to a third party, with no documented rotation story. AWS S3 is the only backend where Stream can use an IAM role instead, via the documented policy granting s3:PutObject to arn:aws:iam::185583345998:root. Note the separate UpsertExternalStorageRequest accepts only role_arn or gcs — no custom endpoint — so don't use it for a non-AWS store.

Architecture implied by each choice

Choice Architecture
AWS S3 Stream → S3 direct, IAM role, no shared secret. No pipeline.
R2 / B2 / Spaces / Tigris / Wasabi Stream → bucket direct via custom endpoint + static key. No pipeline.
Supabase Direct if CheckExternalStorage passes; otherwise keep the transfer pipeline — which currently rejects anything over 500 MB.
Cloudflare Stream / Mux / Bunny / Gumlet Always a pipeline — Stream cannot write to a video platform.

If a pipeline is unavoidable: Stream publishes no egress charge on its pricing page or pricing guide — recording is billed by call-minute only. Downloading our own recordings appears free (medium confidence — absence of a line item, not an affirmative statement). Netlify cannot host the transfer: documented limits are a 15-minute background ceiling, 6 MB buffered payload and 20 MB streamed response. That also means playback must always be a direct signed URL to the origin, never proxied through the app. GitHub Actions is the viable host: 6-hour job cap, 14 GB runner disk, 2,000 free Linux minutes/month private, then $0.008/min — ~$16/month at Scale C, plus permanent ownership of a failure mode we can avoid entirely.


4. Cost model

Assumptions (held constant across every vendor)

Input Value Justification
Avg recording size 1.0 GB Conservative. Sampled real Stream files were 450–900 MB (midpoint 675 MB); 1.0 GB carries ~1.5× headroom.
Avg duration 110 min Faker-derived — unvalidated. Only affects per-minute vendors, where it is decisive.
Playback egress 1.0 GB (full file, progressive) Conservative default. ABR variant at 0.5 GB modelled separately.
FX EUR→USD 1.08 Scaleway/Hetzner.
GST Excluded from headline 18% is ITC-recoverable for most vendors — cash-flow, not cost. Exception: R2, see §5.

Bitrate sanity check: 450–900 MB over 110 min ⇒ 0.55–1.09 Mbps — right for a 720p composite of talking heads. Source sizes and the (fake) duration happen to be mutually consistent, but since the duration is fabricated, treat 1 GB as a planning assumption, not a measurement.

Scale New/mo Cumulative stored Playbacks/mo Egress/mo
A (≈today) 50 200 GB 200 200 GB
B 500 3 TB (brief said 1 TB; inconsistent at 1 GB/file) 3,000 3 TB
C 2,000 10 TB 20,000 20 TB

Today's true scale is 0 GB stored and 0 playbacks. Scale A is already a forecast.

Object storage

Provider Scale A Scale B Scale C
Backblaze B2 (origin only) 200×.00695 = $1.39 3000×.00695 = $20.85 10000×.00695 = $69.50
Hetzner base €6.49 = $7.01 €6.49+2×6.26+2×1 = $22.69 €6.49+9×6.26+19×1 = $88.38
Cloudflare R2 (200−10)×.015 = $2.85 (3000−10)×.015 = $44.85 (10000−10)×.015 = $149.85
Tigris (200−5)×.02 = $3.90 3000×.02 = $60.00 10000×.02 = $200.00
AWS S3 + CloudFront Pro (200−50)×.025 + 15 = $18.75 (3000−50)×.025 + 15 = $88.75 (10000−50)×.025 + 15 = $263.75
Scaleway (Multi-AZ) 200×.01734 + 125×.0108 = $4.82 3000×.01734 + 2925×.0108 = $83.61 10000×.01734 + 19925×.0108 = $388.59
DO Spaces BLR1 base = $5.00 5 + 2750×.02 + 1976×.01 = $79.76 5 + 9750×.02 + 18976×.01 = $389.76
B2 + bunny.net (India) 1.39 + 200×.03 = $7.39 20.85 + 3000×.03 = $110.85 69.50 + 20000×.03 = $669.50
Wasabi 1 TB min = $7.99 3×7.99 = $23.97 ⚠️ at policy line 10×7.99 = $79.90 ⚠️ POLICY BREACH
Supabase Pro 25 + 100×.021 = $27.10 25 + 2900×.021 + 2750×.09 = $333.40 25 + 9900×.021 + 19750×.09 = $2,010.40
AWS S3 direct (no CDN) 200×.025 + 100×.1093 = $15.93 3000×.025 + 2900×.1093 = $391.97 250 + 10140×.1093 + 9760×.085 = $2,187.90
Azure Blob Central India 200×.020 + 100×.12 = $16.00 3000×.020 + 2900×.12 = $408.00 200 + 10140×.12 + 9760×.085 = $2,246.40
GCS asia-south1 200×.020 + 200×.12 = $28.00 3000×.020 + 3000×.12 = $420.00 200 + 10240×.12 + 9760×.11 = $2,502.40

Video platforms (per-minute billing, at 110 min/recording)

Provider Scale A Scale B Scale C
Bunny Stream (3× renditions, ABR 0.5 GB/view) 600×.01 + 100×.03 = $9.00 9000×.01 + 1500×.03 = $135.00 30000×.01 + 10000×.03 = $600.00
Mux 720p 22k×.0024 + 22k×.0008 = $70.40 330k×.0024 + 330k×.0008 = $1,056 1.1M×.0024 + 2.2M×.0008 = $4,400
Gumlet Business 99 + 7×3 = $120 99 + 315×3 = $1,044 exceeds 200k-min cap — N/A
Cloudflare Stream 22×5 + 22×1 = $132 330×5 + 330×1 = $1,980 1100×5 + 2200×1 = $7,700
VdoCipher ~$49 plan ~$399 plan 30000×.75 = ~$22,500

How zero playback changes the ranking

Strip egress out and rank on storage only — which is the honest view of today:

Rank (storage only, Scale C) Provider Storage cost
1 Backblaze B2 $69.50
2 Hetzner $65
3 Cloudflare R2 $150
4 Tigris / Azure / GCS ~$200
5 Supabase $208
6 DO Spaces $195
7 AWS S3 $250

At zero playback, Supabase is mid-pack and perfectly defensible — the spread between all sane options is under $200/month. The entire case against it is egress, and egress is currently zero. That is the honest steel-man for doing nothing.

The counter: at Scale A the decision costs ~$24/month either way, at Scale B it's $290/month, at Scale C it's $1,860/month — and the migration only gets more expensive as the corpus grows. Right now the corpus is ~14 MB. This is the cheapest moment this decision will ever have.


5. Provider-by-provider

Supabase Storage — the "stay in one vendor" option

  • Pro is mandatory. Free caps objects at 50 MB; Pro/Team at 500 GB.
  • Storage: 100 GB included, then $0.021/GB. Egress: 250 GB included, then $0.09/GB; cached egress $0.03/GB.
  • India region: yesap-south-1, and our project is already there.
  • Range requests work. Stale 2021 complaints are obsolete: supabase/storage#26 merged 2021-06-09, maintained as recently as #1087 (2026-05-08). Seeking in a 110-min video is fine.
  • ✅ No AUP clause against serving video.
  • 🔴 Signed URLs defeat the CDN cache. Smart CDN caches per-URL, and each signed URL carries a unique token → separate cache entry. Per-buyer signed URLs — the entire point of a paywall — therefore miss cache and bill at $0.09/GB, never $0.03. (High confidence on mechanism; medium on billing — the only direct discussion, supabase#39391, was answered by a community collaborator, not staff, and remains open.)
  • 🔴 Unified egress pool = correlated failure. Shares one quota with Database, Auth, Realtime, Edge Functions. Spend Cap ON → "further usage of that item is disallowed until the next billing cycle"; OFF → uncapped billing.
  • 🔴 Direct-write unverified (§3); fallback pipeline currently rejects >500 MB.

Decisive gotcha: a popular replay can throttle our database API.

Cloudflare R2 — 🥇 primary

  • $0.015/GB-month Standard, $0 egress, Class A $4.50/M, Class B $0.36/M; free 10 GB + 1M Class A + 10M Class B. Max object 5 TiB; 5 GiB single-part.
  • ToS resolves in R2's favour. Cloudflare's Service-Specific Terms: "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." R2 is the Developer Platform — it is the carve-out. (High confidence; the clause names the product family, not "R2" literally.)
  • ✅ Both addressing styles supported → the one real Stream-compatibility risk evaporates.
  • Free exit. Zero egress means a forced migration costs nothing — which, as §6 shows, is the correct hedge against a future localisation rule.
  • 🔴 No provable residency anywhere. Location hints are wnam|enam|weur|eeur|apac|oc; jurisdictions are eu|fedramp|us. No India option in either, and Cloudflare states hints are "a best effort and not a guarantee" — so we cannot demonstrate where the bytes physically sit, in any region.
  • 🔴 Unrecoverable Indian GST. Cloudflare charges Indian GST from a non-resident registration and — unlike its Singapore and Canada sections — offers Indian customers no documented exemption or self-assessment path. A non-resident OIDAR registration files GSTR-5A (B2C only), so the invoice likely never reaches our GSTR-2B and the 18% may be unrecoverable. At Scale C that's ~$27/mo on top of $150; at Scale A, $3.36. (Missing exemption path: verified from Cloudflare's docs. ITC failure: reported-but-unverified.) An irritation, not a reversal — but email them and ask.
  • 🔴 Presigned URLs bypass the CDN. "Presigned URLs work with the S3 API domain… and cannot be used with custom domains." Max expiry 7 days. The fix is a custom domain plus a Worker that validates our own token and streams from the bucket binding — ~40 lines, but it is code the Supabase option doesn't need. This is the real complexity cost of R2.
  • 🔴 r2.dev is "not intended for production usage" and rate-limited. Custom domain required.

Decisive gotcha: free egress, but we must write a Worker to get caching and auth together — and we can never prove where the data lives.

AWS S3 (ap-south-1) + CloudFront — 🥈 runner-up

From the AWS Price List API, queried directly rather than trusting the JS pricing widget:

S3 Standard          $0.025/GB-Mo (first 50 TB) → $0.024 → $0.023
Standard-IA / IT-IA  $0.0138/GB-Mo    Glacier Instant Retrieval  $0.005/GB-Mo (+$0.03/GB retrieval)
Egress to internet   $0.1093/GB (0–10 TB) → $0.085 → $0.082 → $0.080; 100 GB/mo free

Mumbai's Standard-IA ($0.0138) carries a ~10% premium over us-east-1. ap-south-2 (Hyderabad) is priced identically.

  • 🟢 CloudFront flat-rate plans — the headline finding. Pro = $15/month: 10 M requests, 50 TB data transfer, 50 GB S3 Standard credit. Verbatim: "Most importantly: you will not incur overage charges, regardless of how much you exceed your allowance." and "Your first traffic spike up to 3x your monthly allowance won't affect your service that month." At India PAYG rates 50 TB would cost ~$5,450. S3→CloudFront origin transfer is free.
  • 🟢 Signed URLs on every tier, described by AWS as "grant secure access to protected content after payment verification" — literally this use case.
  • 🟢 Only backend needing no static secret — IAM role auth.
  • 🟢 Verified INR billing: "your user agreement is with Amazon Web Services India Private Limited… your invoice total is listed in Indian rupees (INR) instead of US dollars (USD)." Delhi-based, so SGST+CGST 9%+9% for Delhi customers, IGST 18% elsewhere; invoices land in the GST portal's IMS dashboard.
  • 🔴 Restrictions: one distribution + one apex domain per plan; mandatory WAF Web ACL; OAC not OAI; incompatible with multi-tenant distributions, real-time logs, continuous deployment, Anycast IP lists, dedicated-IP SSL, field-level encryption, legacy ForwardedValues.
  • ⚠️ Two unconfirmed points (docs silent — see §11): is the 50 TB allowance region-neutral for India-heavy traffic, and are flat-rate plans available on AWS India (AISPL) accounts?

Decisive gotcha: superb economics and the best tax story, but the highest operational surface here — and the $15 plan's India applicability is unconfirmed.

DigitalOcean Spaces BLR1 — 🌏 the India-residency pick

  • $5.00/month base including 250 GiB storage + 1,024 GiB outbound transfer; then $0.02/GiB storage, $0.01/GiB transfer; CDN included and sharing the same transfer allowance.
  • 🟢 BLR1 Bangalore confirmed — the regional-availability table parsed directly: BLR1 | Bangalore, India | blr1, with Spaces Standard Storage and Spaces Cold Storage rows present.
  • 🟢 India GSTIN → 18% GST, or zero-rated with RCM if we supply ours. Recoverable, unlike R2.
  • 🟢 DigitalOcean stopped accepting Form 16A in January 2025, stating that "following recent tribunal and court rulings determining cloud computing services shouldn't be subject to withholding taxes," customers need not withhold TDS.
  • 🟢 Reported as the only provider here that hasn't raised prices (single-sourced: Wayback diff across Feb 2024 → today).
  • 🔴 800 total operations/second per bucket (single-sourced) — fine now, a real ceiling later.
  • 🔴 Presigned uploads to the CDN endpoint cap at 7.91 MiB (single-sourced); uploads must target the origin endpoint. Configure s3_custom_endpoint_url accordingly.
  • 🔴 2026 incidents reported include an India-specific one (2026-07-24, India→NYC connectivity), BLR1 networking (2026-07-19), and Spaces write failures (2026-08-06) (single-sourced).
  • ⚠️ Conflicting reports on whether Cold Storage is available in BLR1 — the table parse shows the row exists; verify before relying on it.

Decisive gotcha: the only real India region, but an 800 ops/s bucket ceiling we will eventually hit.

Backblaze B2 — cheapest on paper, wrong shape

  • $6.95/TB/mo; free egress up to 3× average monthly storage, then $0.01/GB; Class A/B/C API calls free; no minimum duration or monthly charge; first 10 GB free. Max object 10 TB — largest here.
  • 🟢 The Bandwidth Alliance is alive. Backblaze's current pricing page: "unlimited free egress when downloading to or through partner content delivery networks… including Fastly, Cloudflare, bunny.net, CacheFly, CoreWeave, Equinix Metal, Vultr, and phoenixNAP."
  • 🔴 But B2 + Cloudflare CDN violates Cloudflare's terms. The carve-out requires content hosted by a Cloudflare service; B2 isn't one. B2 + bunny.net is the compliant version ($0.03/GB to Asia, real India PoPs).
  • 🔴 No Asia region at allUS West, US East, EU Central, CA East only. For a long-tail archive with few repeat views, most requests are cache misses → 1 GB fetched from Sacramento or Amsterdam to India. The free egress is real; the latency is not survivable.
  • 🔴 Reported to publish no incident history, and to have repriced $6.00→$6.95/TB effective 2026-05-01 (both single-sourced).
  • 🔴 India tax position entirely undocumented (single-sourced).

Decisive gotcha: unbeatable price, but no Asian presence — every cold playback crosses an ocean.

Wasabi — disqualified

  • $7.99/TB/mo. Three independent disqualifiers from the pricing FAQ:
    1. "If your monthly egress data transfer is greater than your active storage volume, then your storage use case is not a good fit… we reserve the right to limit or suspend your service." There is no overage rate — we cannot pay our way out. Scale C is a 2× breach.
    2. 90-day minimum storage duration.
    3. 1 TB minimum monthly charge.
  • No India region. Reported repricing $6.99→$7.99/TB on 2026-07-01, and holding GSTIN 9924USA29042OSG (both single-sourced).

Decisive gotcha: a replay marketplace is precisely the workload their egress policy exists to expel.

Tigris, Scaleway, Hetzner — cheap, ruled out

  • Tigris: $0.02/GB, free egress, Class A $0.005/1k, Class B $0.0005/1k. No India region. (Single-sourced: a discretionary egress clause — "If your bandwidth requirements are extraordinary, please reach out" — and a ~12-hour window on 2026-07-01 where single-region SJC reads returned 404s due to replication delay. Silent wrong answers are worse than an outage for a paid-replay pipeline.)
  • Scaleway: Multi-AZ €0.01606/GB; 75 GB free egress then €0.01/GB. EU-only. (Single-sourced: 1,000-part multipart cap, +10% 2026 reprice, and a month-long FR-PAR degradation Sept–Oct 2024.)
  • Hetzner: €6.49/mo including 1 TB storage + 1 TB egress, then €6.26/TB and €1.00/TB egress — cheapest bandwidth anywhere. EU-only. (Single-sourced: 750 req/s per bucket, +30% effective 2026-04-01, and an Object Storage capacity incident open since 2026-01-15.)

Decisive gotcha (all three): no India presence, and each has an unresolved reliability question.

GCS and Azure — non-starters on egress

  • GCS asia-south1 (single-sourced): Standard $0.020/GiB-mo; egress to Asia $0.12/GiB → $0.11 → $0.08. No free egress allowance for an Indian bucket — the Always Free tier is US-only. Worst of the three.
  • Azure Blob Central India: Hot $0.020/GB, Cool $0.011, Cold $0.0045, Archive $0.002 (single-sourced, via the Azure Retail Prices API; Central India is ~19% cheaper than South India). Egress verified: 100 GB free, then $0.12/GB → $0.085 → $0.082 → $0.08.
  • Both are natively supported by Stream (gcs, abs), so compatibility isn't the problem — the egress is. Neither has anything resembling CloudFront Pro.
  • Azure additionally carries a live billing-entity ambiguity and a real TDS workflow — see §6.

Decisive gotcha: hyperscaler egress without a flat-rate CDN is the most expensive way to do this.

Video platforms — evaluated seriously, and they lose on this shape

The structural finding: Cloudflare Stream, Mux and api.video bill delivery per minute watched, not per GB. Adaptive bitrate therefore does not lower the bill on those platforms at all — a viewer on 240p costs the same as one on 1080p, and the ABR savings accrue to the vendor. Only Bunny, Gumlet and VdoCipher bill per GB, and only there does the ladder reach our invoice.

How much does HLS/ABR actually save? Our source is already a low-bitrate talking-head encode (~0.55–1.09 Mbps), so the upside is capped:

Mode GB per view (110-min session)
Progressive, full file 1.00
Progressive, range-aware player, ~55% watched 0.55–0.65
ABR @1.2 Mbps, 50% watched ~0.50
ABR @0.8 Mbps, 50% watched ~0.33

Honest answer: ABR is ~2–3× better than a naive full-file fetch, not the 5–10× commonly claimed. (Analysis single-sourced.)

  • Cloudflare Stream — re-verified first-hand: "$5 per 1,000 minutes stored", "file size does not matter", "$1 per 1,000 minutes delivered", encoding free, and "Client-side preloading and buffering is counted as billable delivery." Renditions are free to store — its one genuine edge. But per-minute storage means $5,500/month at Scale C before a single view. Per-minute pricing is structurally wrong for long-session archives.
  • Bunny Stream (single-sourced): $0.01/GB storage, $0.03/GB Asia delivery, free transcoding, token auth, $1/mo minimum, five India PoPs. The only genuinely competitive video platform. Two caveats: budget 2–4× source for renditions, and the cheap $0.005/GB Volume tier has no India PoP.
  • Mux (single-sourced): $0.0024/min storage, $0.0008/min delivery at 720p → $4,400/mo at Scale C.
  • VdoCipher (single-sourced): the only vendor bundling Widevine/FairPlay DRM in every plan (Mux, Gumlet and Bunny each charge ~$99–100/mo). But storage is $0.75/GB against their own "Accounted storage can be 2 to 4 times of upload" → ~$22,500/mo at Scale C. Viable only as a DRM front for a small premium subset.
  • Gumlet is not an Indian entity (single-sourced) — footer reads "© 2026 Gumlet Pte. Ltd.", a Singapore company billing USD. No GST/INR advantage, and a 200k stored-minute cap that Scale C blows through.

Decisive gotcha: per-minute billing punishes 110-minute sessions catastrophically, and the one platform that doesn't (Bunny) forces a transfer pipeline.


6. Compliance, tax and billing

This section is single-sourced from a dedicated research pass that worked from gazette text and case law. It is detailed and internally consistent, but it was not independently re-verified. Treat the two items flagged for counsel as requiring human sign-off.

We are under no legal obligation to store recordings in India

  • DPDP s.16 is a pure negative list, and no countries have been notified. More importantly: s.10, s.16 and the substantive Rules 3, 5–16 do not come into force until 13 May 2027 (G.S.R. 843(E)/846(E), notified 2025-11-13). Only the Data Protection Board provisions are currently live.
  • Rule 15 is a government-access condition, not a residency one. No India copy required.
  • Rule 13(4) — the only localisation hook — is SDF-only, is an export prohibition for a data category the Centre has yet to specify, and the recommending committee has not been constituted. We would not be an SDF; none have been notified.
  • RBI is out of scope. The April 2018 circular binds system providers. The only merchant-facing rule is "don't store card data." Razorpay carries the localisation duty.
  • CERT-In's 180-day log rule imposes no location requirement — the May 2022 FAQs state logs "need not be stored within India only." The financial-transaction-log carve-out is the exception.
  • ⚠️ Watch item: MeitY floated (Jan 2026 consultation) compressing the timeline to 13 Nov 2026 and enforcing Rules 13(4)/15 immediately. Not notified as of Aug 2026. Unresolved — this is the single fact most likely to change the answer.

🔴 Rule 8(3) sets a one-year retention floor — this constrains lifecycle policy

The gazette's own Illustration Case 2 is verbatim our architecture: a data fiduciary using a cloud provider as processor "is required to ensure that the C also retains the data and associated logs for at least one year before erasure." Rule 6(1)(e) adds a one-year floor for security logs.

Do not write an aggressive auto-delete rule. If we ever move to R2 Infrequent Access (30-day minimum) or Glacier, make sure the 12-month floor is the binding constraint, not the storage tier.

Note this interacts with #1266's reading of Rule 8(3) as a narrow State-access preservation duty — reconcile the two before either shapes retention config.

Tax

  • 18% IGST under reverse charge, ITC-recoverable — but CGST Rule 47A imposes a hard 30-day self-invoice clock (inserted w.e.f. 2024-11-01). Paid in cash first, reclaimed later. Real paperwork, not a real cost.
  • GST 2.0 did not touch this — the 56th Council release contains zero occurrences of "OIDAR" or "cloud"; 18% standard rate, effective 2025-09-22.
  • Both equalisation levies are dead, confirmed from statute quoted in judgments: the 2% e-commerce levy ceased 2024-08-01 (s.10(50) IT Act, per ITAT Mumbai in Acronis Asia); the 6% ads levy ceased 2025-04-01 (s.165(3) Finance Act 2016, per Madras HC in Zoho).
  • "Cloud as royalty" has gone our way at High Court levelCIT v Amazon Web Services (Delhi HC, 2025-05-29) and CIT v Urban Ladder (Karnataka HC, 2025-02-07) both hold cloud fees are neither royalty nor FTS. Keep TRC + Form 10F + no-PE declaration on file regardless.

Vendor billing posture

Vendor INR? GST treatment
AWS India Pvt Ltd Yes, verified Domestic invoice; SGST+CGST for Delhi, IGST 18% elsewhere; lands in the GST portal IMS dashboard
Google Cloud Contracted with Google Asia Pacific: 0% India GST with a valid GSTIN, 18% without. Sold by Google Cloud India Pvt Ltd: 18% regardless. Effective 1 Feb 2025
Microsoft Azure ⚠️ Conflicting docs One Microsoft doc names Microsoft Corporation India Private Limited (INR, domestic GST); another states "Customers in India receive Web Direct (Azure and Microsoft 365) invoices billed by Microsoft Regional Sales Pte Ltd. Singapore… and make cross-border payments to Singapore." Likely channel-dependent (MCA/CSP vs Web Direct). The Singapore route carries a real TDS workflow — quarterly Form 16A, deadlines 31-Aug / 30-Nov / 28-Feb / 30-Jun. Moot; Azure isn't recommended
Cloudflare 🔴 Non-resident registration, no documented exemption or RCM path for India → 18% likely unrecoverable
DigitalOcean ❌ USD ✅ India GSTIN; 18% or zero-rated with our GSTIN. Stopped accepting Form 16A Jan 2025 — no TDS withholding required
Wasabi GSTIN reported (single-sourced)
Backblaze 🔴 India tax position entirely undocumented (single-sourced)
Supabase, Bunny, Mux, Gumlet Foreign supplier; RCM self-assessment

The EU Data Act does not help us

Its switching-charge abolition — the Act "applies since 12 September 2025" and "will entirely remove switching charges, including charges for data egress… from 12 January 2027" — is scoped to customers in the Union. An Indian buyer gets no such right. Our exit relief is the hyperscalers' voluntary, revocable programmes: AWS's is the loosest (no account closure required, 90-day window); Azure's requires cancelling all subscriptions, gives 60 days (UK 180), excludes ExpressRoute/VPN/Front Door/CDN, and states "Azure might make changes regarding the egress credit policy in the future."

🔴 Consent is a larger exposure than storage location

We record identifiable people in sensitive consulting discussions and resell the recording. India's baseline is one-party consent, but commercial exploitation is a different question, and s.72 IT Act penalises unlawful disclosure.

Encouragingly, the schema already anticipates this — Recording.consentAttestedAt and Recording.consentAttestedById exist. Ensure they are populated for all parties and that the attestation covers resale, not merely recording. This is the one item here to put in front of a lawyer.


7. Business scorecard

Vendor burden Lock-in / exit Residency Billing / GST friction Reliability Pricing stability
Supabase 🟢 Zero new — already in stack, createSignedUrl already written 🔴 Exit taxed at $0.09/GB (~$900 per 10 TB) 🟢 ap-south-1, already there 🟡 USD, foreign supplier, RCM 🟡 Egress pool shared with DB — correlated failure 🟢 Stable
Cloudflare R2 🟡 Low (one token, existing SDK) + a Worker for cached playback 🟢 Free exit 🔴 No provable residency anywhere 🔴 18% likely unrecoverable 🟢 Strong 🟢 Stable since launch
AWS S3 + CF Pro 🔴 Highest — IAM, mandatory WAF, OAC 🟡 Taxed, but loosest exit programme 🟢 ap-south-1 / ap-south-2 🟢 Best — INR, domestic GST invoice 🟢 Strongest 🟡 Flat-rate plans are new (Nov 2025)
DO Spaces 🟢 Low, S3-compatible 🟢 Cheap ($0.01/GiB) 🟢 BLR1 🟢 GSTIN, recoverable, no TDS 🟡 Several 2026 incidents incl. India 🟢 Reportedly unchanged since 2024
Backblaze B2 🟡 Low, but needs a CDN partner 🟢 Free via partners 🔴 None (US/EU/CA) 🔴 Undocumented 🔴 No public incident history 🔴 +16% May 2026
Wasabi 🟡 Low 🔴 90-day min duration 🔴 None 🟢 GSTIN 🟡 Recurring regional incidents 🔴 +14% Jul 2026
Tigris / Scaleway / Hetzner 🟡 Low 🟢 Cheap 🔴 None / EU-only 🟡 RCM 🔴 Each has an unresolved issue 🔴 Scaleway +10%, Hetzner +30%
Bunny Stream 🟡 New vendor + mandatory pipeline 🟢 Cheap 🟢 5 India PoPs 🟡 USD, RCM 🟢 Good 🟢 Stable

8. Provenance ledger

An issue that hides its own uncertainty is worse than one that shows it.

✅ First-hand verified (fetched, queried or executed directly)

Claim Method
R2 pricing, limits, presigned-URL restriction, data-location options Cloudflare docs
Supabase Pro pricing, 500 GB object cap, Smart CDN per-token cache keys, spend-cap behaviour, ap-south-1, range-request support, AUP, S3-compat + auth docs Supabase docs + supabase/storage GitHub API
AWS S3 ap-south-1 storage + egress tiers AWS Price List bulk API via curl
CloudFront flat-rate Pro — $15, 50 TB, no-overage quote, signed URLs on all tiers, restriction list AWS developer guide, grepped from saved page
Azure India egress ($0.12/GB after 100 GB free) Azure bandwidth page
s3_custom_endpoint_url @ line 9067, CreateExternalStorageRequest, /check endpoint, recording-quality enum Stream public OpenAPI spec
Discussion #371 contents, dates, our own 2025-02-15 comment GitHub GraphQL API
Supabase path-style proof (TLS SAN + handshake failure) openssl + curl
Seed-data finding (191 rows, faker URL, 0 purchases) SQL + faker.internet.url() grep
Cloudflare Stream per-minute pricing, "file size does not matter", buffering-is-billable Cloudflare docs (re-verified after retraction)
DO Spaces pricing; BLR1 availability DO docs; table parsed via curl
Backblaze pricing + region list; Wasabi pricing + FAQ egress/duration/minimum clauses; Tigris pricing Vendor pages
Cloudflare Service-Specific Terms video carve-out Cloudflare legal
Netlify function limits; GitHub Actions limits Vendor docs
s3_custom_endpoint_url in the installed SDK (@stream-io/node-sdk@0.7.64, dist/src/gen/models/index.d.ts:16095); MAX_TRANSFER_SIZE at recording-transfer-service.ts:27; recordings bucket file_size_limit; ~14 MB total storage; 191 faker recordingUrl values Local repo + mcp__supabase__execute_sql

🟡 Single-sourced (one research pass; not independently re-verified)

Backblaze incident-page behaviour and repricing date · Wasabi repricing date and GSTIN · Scaleway pricing, 1,000-part cap, 2024 incident · Hetzner pricing, 750 req/s, +30%, open incident · DO 800 ops/s, 7.91 MiB presigned CDN cap, Wayback price-stability check, 2026 incident list · Tigris SJC 404 incident and discretionary egress clause · Mux / Bunny / Gumlet / VdoCipher rates · "Gumlet Pte. Ltd. Singapore" · the ABR savings analysis · GCS asia-south1 figures · Azure Blob per-GB storage · Cloudflare Bandwidth Alliance detail · DigitalOcean India tax page and the Form 16A change · the whole of §6.

❌ Retracted — appeared in an earlier draft, corrected here

Retracted claim Status
EU Data Act Art. 1(3)(f) scoping quote Citation withdrawn. Substance (applies 2025-09-12; egress charges removed 2027-01-12) verified and retained
"Google Cloud runs a Form 16A TDS workflow to its Gurgaon entity" Wrong and mis-attributed. The verified Form 16A workflow is Microsoft's. DigitalOcean stopped accepting Form 16A in Jan 2025. Corrected in §6
Google Cloud exit conditions (contract termination, ~30-day window) Withdrawn — page truncated on fetch
AWS India accepts UPI/NetBanking/RuPay, ₹15,000 e-mandate cap Withdrawn. INR billing itself is verified verbatim
Azure "documentation conflict" as originally asserted Amended — asserted on one verified half plus one fabricated half; both halves are now independently sourced and the conflict is real but likely channel-dependent
Equalisation levy dates asserted without support Now verified from statute (§6); the dates were right

Root cause: one pricing research pass fabricated two sections, inventing verbatim quotes, URLs and revision dates it never fetched, and self-reported afterwards. Most of what it invented happened to be correct — which is the dangerous part, since it says nothing about reliability. The storage and egress economics, including the CloudFront Pro finding, rest on feeds pulled first-hand and are unaffected.


9. What would flip the decision

  1. MeitY notifies the compressed DPDP timeline, or Rule 13(4) acquires a data category that catches session recordings. Then provable India residency becomes mandatory and R2 is out — it cannot demonstrate location in any region. The pick becomes DO Spaces BLR1 or AWS ap-south-1. This is why R2's free exit matters: it is the hedge against exactly this.
  2. Playback volume lands 10× above model (~200,000 views/month). R2's zero egress wins outright, and everything metered — including CloudFront Pro, once sustained usage exceeds 50 TB and AWS begins "adjusting how we deliver your traffic" — becomes a live risk.
  3. Playback stays at zero. The decision is worth ~$25/month either way and should be made on operational grounds alone. That still points to R2 (free exit) or the status quo (zero new vendors) — but not to AWS, whose IAM/WAF/OAC surface only pays for itself at volume.

Secondary: adopting HLS/ABR (~2–3× egress reduction) makes Bunny Stream competitive at $600/mo with India PoPs — though it would reintroduce a transfer pipeline. Studio-grade DRM as a hard requirement would force a hybrid with VdoCipher. And if real recordings materially exceed 1 GB, every storage figure scales linearly while per-minute platforms get relatively better — measure before assuming.


10. Implementation checklist

  • Instrument fileSize on the first real recording. Every planning number rests on an assumption because this column has never been populated once.
  • Raise both 500 MB ceilings before recording anything realMAX_TRANSFER_SIZE at lib/stream/recording-transfer-service.ts:27, and the recordings bucket's file_size_limit = 524288000. Either one silently rejects a real 110-minute recording today. Note ensureBucketExists (lib/supabase-storage-core.ts:143) only applies options at creation and never calls updateBucket, so the bucket limit will not fix itself.
  • Set RecordSettingsRequest.quality to 720p. Stream bills recording by call-minute at a resolution-dependent rate, so one notch down cuts the Stream bill, storage and egress simultaneously — the cheapest lever available, and almost certainly invisible to buyers on talking-head content.
  • Create the R2 bucket with location hint apac.
  • Register external storage and run the five-minute test that settles everything:
    POST /video/external_storage
      { name: "r2-recordings", storage_type: "s3", bucket: "...", path: "recordings/",
        aws_s3: { s3_region: "auto",
                  s3_custom_endpoint_url: "https://<ACCOUNT_ID>.r2.cloudflarestorage.com",
                  s3_api_key: "...", s3_secret: "..." } }
    GET  /video/external_storage/r2-recordings/check
    
    A 200 means direct-write works and the transfer pipeline can die.
  • Run the same check against Supabase (https://<ref>.storage.supabase.co/storage/v1/s3, region ap-south-1). One command. If it passes, the all-Supabase option is live again and worth re-deciding on cost alone; if it fails, §3's path-style/prefix analysis is confirmed and the question closes for good.
  • Set recording_external_storage at the call-type level, not per-call, so nothing can be recorded to the wrong place by omission.
  • Move getBestRecordingUrl first. It is the seam the whole marketplace hangs off — the module's own comment warns that shipping the storage change ahead of it takes the marketplace down with the pipeline. The vendor swap is otherwise contained to 123 lines, and RecordingStorageType = STREAM_S3 | PLATFORM is already vendor-neutral.
  • Build the playback path: custom domain + Worker validating our own signed token, streaming from the R2 bucket binding.
  • Store the Stream-facing R2 credential as a bucket-scoped, write-only, rotatable API token. Diary a rotation; Stream documents none.
  • Set the lifecycle floor at 12 months to satisfy DPDP Rule 8(3) and Rule 6(1)(e). Do not auto-delete earlier, and don't let a 30-day IA minimum become the binding constraint instead.
  • Email Cloudflare: can an Indian GSTIN be recorded on the account to shift billing to reverse charge, so the 18% becomes recoverable?
  • Confirm consentAttestedAt / consentAttestedById are populated for all parties and cover resale, not just recording. Legal review.
  • Make the Supabase Spend Cap a conscious decision either way — it currently silently determines whether an overage bills us or breaks the database API.
  • Delete recording-transfer-service.ts once check passes and one real recording lands in R2.
  • Do not run any backfill. There is nothing to back-fill; total storage today is ~14 MB and every Recording row is seed data.

11. Open questions for Stream support

  1. Has anyone run s3_custom_endpoint_url against Cloudflare R2 in production? The docs name only MinIO, and discussion feat: AI-Powered Mentor Search & Recommendation System - Technical Options #371 has three unanswered R2/Spaces/Supabase questions going back to May 2024 — including ours from 2025-02-15.
  2. Does Stream's S3 client use path-style or virtual-hosted addressing when s3_custom_endpoint_url is set? There is no force_path_style field. MinIO support implies path-style; please confirm.
  3. Does it tolerate an endpoint with a path prefix (e.g. https://<ref>.storage.supabase.co/storage/v1/s3), signing SigV4 over the prefixed path? This single fact decides whether all-Supabase is viable.
  4. Are we billed egress when a pipeline downloads recordings from Stream's S3? No line item appears on either pricing page — please confirm zero.
  5. What are the HD recording rates by resolution? Only audio-only ($1.50/1,000 call-minutes) is public; the per-resolution grid is behind the calculator. At 110-minute sessions this is plausibly a larger line item than storage.
  6. Is there a rotation path for s3_api_key/s3_secret on an existing external storage config, or must it be recreated?
  7. Does Stream use multipart upload for large recordings, and at what part size? (Relevant to Scaleway's 1,000-part cap, though Scaleway isn't recommended.)
  8. Does recording_external_storage also cover transcription output, or does transcription_external_storage need setting separately? (Carried over from Stream: what the SDK offers that we have not assessed, with costs #1160.)

For AWS, before committing to the runner-up: is the CloudFront flat-rate 50 TB allowance region-neutral for India-heavy traffic, and are flat-rate plans available on AWS India Pvt Ltd (AISPL) accounts? The docs are silent on both, and a ~360× effective discount deserves a written answer.

Metadata

Metadata

Assignees

Labels

complianceTax, regulatory, DPDP, consent, legal exposurefinancePayments, refunds, earnings, payouts, invoicing, ledgerinfrastructureInfrastructure, deployment, and DevOpslaunch: pre-mvpGates launch — money, data, or a failure we would not detectstreamStream Chat/Video integration

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions