Documentation

How Blobify works

Blobify stores content as JSON in an S3-compatible bucket owned by your organization. The API handles authenticated writes and management operations. Websites, static builds, and generated clients read published JSON directly from the bucket.

Public and private storage scopes

The bucket has two regions. Published, delivery-ready artifacts live at the bucket root on stable, public paths that generated clients read directly. Authoring artifacts (drafts, edit history, full schemas, draft-state derived files, and asset authoring metadata) live under a single reserved scope:

{rootPrefix}/{privateStoragePrefix}/...

privateStoragePrefix is an unguessable pv_... value generated when the organization is created, and the value itself is the folder segment at the organization root. It is returned only in authenticated organization context, so the dashboard and authenticated tools can reach authoring artifacts while public readers work from the root. By default this is capability protection: the scope sits at an unguessable prefix and bucket listing is not exposed, which guards against discovery rather than enforcing authorization. One bucket-level rule turns it into enforced access control (see your storage settings and Security and access).

The write and read paths

Saving a draft writes one mutable source document in the private scope:

{privateStoragePrefix}/spaces/{spaceId}/content/{model}/{contentId}/draft.json

Publishing writes a public snapshot at the bucket root containing the locales that are live:

spaces/{spaceId}/content/{model}/{contentId}/published.json

The source documents are written during the API request. Blobify then queues a reconcile job that updates derived summaries and indexes. Lifecycle webhooks are sent after the worker has converged those derived files, so a receiver can fetch the new output immediately.

The dashboard follows the same storage contract. It reads the summary manifest and the immutable shard versions named by that manifest instead of guessing object names.

Draft and published data

Draft documents contain all editable locales and audit events with actors. Published documents contain only published locales and strip actor identities from public audit timestamps.

This separation keeps unpublished work in the private scope while giving a website a stable snapshot at the public root. See Content JSON for the exact document shapes.

Summaries and indexes

Blobify creates three kinds of derived read output:

OutputUse it for
Summary shardsBroad model lists and build-time enumeration
Field indexesExact value lookups such as slug to content ID
List indexesOrdered, paginated feeds such as latest articles

Summary shards are immutable, versioned files. A state manifest at summaries/{state}/index.json identifies the current file for every non-empty shard. Field indexes are value-keyed. List indexes use a manifest whose pages array points to immutable files such as g3/p00000.json.

Published-state summaries, field index pointer objects, and list indexes live at the public root. Their draft-state counterparts live in the private scope.

Read Summaries and indexes before constructing any bucket URL yourself. The generated TypeScript client already implements these read rules.

Routing

Routing is generated separately from content and stored at the organization level. The published table lives at the bucket root; the draft table lives in the private scope:

routing/published.json{privateStoragePrefix}/routing/draft.json

A model route can turn semantic fields such as slug into a URL without storing that URL in the content document. Routing can therefore be rebuilt or changed without rewriting every entry.

Assets

Each asset has an authoring metadata document, a media file, and a catalog entry. Content stores a semantic reference such as { "type": "asset", "assetId": "img_456" }. Generated clients resolve that reference against the catalog and the configured delivery URL.

Media files live at the bucket root under spaces/{spaceId}/assets/media/ at unguessable capability URLs, and they are not moved by a content prefix. Asset authoring metadata lives in the private scope at spaces/{spaceId}/assets/{assetId}.json.

The catalog exists in two forms at the same logical path. The public delivery projection at spaces/{spaceId}/assets/catalog.json lists only assets referenced by published content plus assets confirmed through listing-enabled dropzones, and carries a delivery-only field set: id, url, filename, contentType, and dimensions and alt text when present. The full authoring catalog, with every asset and its complete metadata, lives in the private scope for the dashboard and draft preview clients.

Content prefixes

A space can set contentPrefix to make predictable JSON keys harder to discover. Blobify keeps logical paths stable but rewrites physical keys for content, summaries, field indexes, list indexes, and archived content:

text
code
Logical:  spaces/main/content/article/art_123/published.json
Physical: spaces/main/cp_site_a1/content/article/art_123/published.json

The prefix appears as a typed cp_... segment in the public tree only; the private scope never carries a content prefix. The generated client and dashboard apply this mapping automatically. Asset media files remain unprefixed. The asset catalog follows the prefix because it is JSON derived data.

Archive-first deletion

Deleting a live entry moves its current draft and published files into the archive, which lives in the private scope:

{privateStoragePrefix}/spaces/{spaceId}/archive/{model}/{contentId}/

The entry can then be restored. A permanent delete is the second action on an already archived entry. Archive and restore also reconcile summaries, field indexes, and list indexes so normal readers do not return archived content.

Permissions

Organization members and API keys use the same role hierarchy and space scopes. A key can be limited to named spaces, so automation behaves like a service member instead of using a separate permission model. See Security and access for the role boundaries.