fshy

fshy / Storage

Bluesky / AT Protocol PDS

github.com · Storage · 1 record · JSON

Bluesky / AT Protocol PDS โ€” 5 MiB blob default, GC'd unless referenced Storage

fails: quotaemail?card?Rper-object-cap

github.com/bluesky-social/atproto/blob/main/packages/pds/src · atproto-pds · storage/bluesky-at-protocol-pds-5-mib-blob-default-gc-d-unless-r · JSON · compare

Free tierblobUploadLimit defaults to 5 * 1024 * 1024 = 5 MiB per blob, overridable per-PDS via the BLOB_UPLOAD_LIMIT env var. Per-account: bsky.app's hosted PDS is generous in practice but the protocol layer only guarantees blobs that are REFERENCED by a record.
unit mixed; metered on unstated
Pricenone stated
Per-object cap5 MiB per blob (5242880 bytes)
Retentionnot stated
EgressFree on bsky.app's PDS and on any self-hosted PDS (it serves blobs directly from /xrpc/com.atproto.sync.getBlob). No egress meter.
Identityemail (placeholder) not published
Limits
  • per-object-cap 5 MiB per blob source
per-object-cap 5 MiB default (config.ts: blobUploadLimit: env.blobUploadLimit ?? 5 * 1024 * 1024); blobs unreferenced within a time window (minutes) are deleted; account-level repo size limits are enforced by the AppView/PDS separately.
Bindingper-object-cap the per-object cap sets the shard size and therefore the object count; it bounds the total more tightly than any headline quota
Interfacecli http-xrpc
ToolingXRPC: POST /xrpc/com.atproto.repo.uploadBlob (binary body + Content-Type) -> returns a blob ref; must then be referenced by a record (e.g. app.bsky.feed.post embed) or it is garbage-collected. Client libs: @atproto/api, atproto Python SDK, goat CLI.
Composes withSelf-hosting a PDS removes the 5 MiB default (it is a config value), so a self-hosted PDS is bounded by YOUR disk, not Bluesky's โ€” that makes it a hosting product, not free storage. On bsky.app's PDS the 5 MiB cap plus the reference requirement makes it useless for bulk. The usable margin is only the 'unreferenced blobs are deleted within minutes' semantics working in reverse: referenced blobs are retained indefinitely.
ReliabilityBlob GC is literal and fast โ€” an upload without a referencing record vanishes. That is the single most important fact about this candidate.
NotesblobUploadLimit defaults to 5 * 1024 * 1024 = 5 MiB per blob, overridable per-PDS via the BLOB_UPLOAD_LIMIT env var. Per-account: bsky.app's hosted PDS is generous in practice but the protocol layer only guarantees blobs that are REFERENCED by a record.
Evidence rungreported One page read moves it to documented.
Checked2026-10-21 (inherited); stale on 2026-11-04
Parsed numbers
  • $.limits[0].value limit:per-object-cap 5.0 MiB
Evidence (2)
datesourcewhat it proved
2026-10-21raw.githubusercontent.com/bluesky-social/atproto/main/packagblobUploadLimit: env.blobUploadLimit ?? 5 * 1024 * 1024, // 5mb
2026-10-21raw.githubusercontent.com/bluesky-social/atproto/main/lexico'The blob will be deleted if it is not referenced within a time window (eg, minutes). Blob restrictions (mimetype, size, etc) are enforced when the reference is created. Requires auth, implemented by PDS.'
More fields (3)
{
 "verdict_note": "PLAN.md ยง3.2 rules fails-quota by default, because no row in this set clears 500 GiB per unit for a user.",
 "unit_note": "free_tier.unit is 'mixed' because the headline is denominated in MiB or KiB, which the schema enum does not carry; the verbatim free_tier.stated keeps the true unit. See the storage-dossier handover.",
 "dead_end": {
  "failure_class": "quota",
  "why": "blobUploadLimit defaults to 5 * 1024 * 1024 = 5 MiB per blob, overridable per-PDS via the BLOB_UPLOAD_LIMIT env var. Per-account: bsky.app's hosted PDS is generous in practice but the protocol layer only guarantees blobs that are REFERENCED by a record.",
  "retry_when": null,
  "first_recorded": "2026-10-21"
 }
}
Unknown or withheld values (7)
  • $.extras.dead_end.retry_when process
  • $.free_tier.notes process
  • $.identity_cost.detail defaulted
  • $.identity_cost.kind identity-defaulted
  • $.per_object_cap.notes not-extracted
  • $.retention.notes not-extracted
  • $.retention.stated not-extracted