AI Agents — Read Guide

A self-contained guide for an AI agent (or any read-only client) to fetch Blobify content directly from a customer's public bucket. Bucket reads are the canonical Blobify read path — the API is for writes.

1. The one auth call

Before reading the bucket, an agent makes one authenticated request to the Blobify API to get the read recipe:

typescriptcode
GET /v1/orgs/{orgId}
Authorization: Bearer <api-key>

The response includes everything needed to construct read URLs:

jsoncode
{
  "id": "org_...",
  "name": "Acme",
  "publicBucketUrl": "https://pub-xxxx.r2.dev/acme",
  "rootPrefix": "acme",
  "spaces": [
    { "id": "main", "contentPrefix": "b134c4670082cad614ac2576" },
    { "id": "staging", "contentPrefix": "94f81d22c4ae3e7f1b58a0d1" }
  ],
  "defaultLocale": "en",
  "locales": [
    { "code": "en", "label": "English" },
    { "code": "is", "label": "Íslenska" }
  ]
}

After this, the agent reads the bucket directly. No further API calls needed for navigation or content fetches.

2. Building bucket URLs

Two kinds of paths exist in the bucket:

  • Unprefixed paths (schemas, asset media): live directly under rootPrefix.
  • Hardened paths (content, summaries, catalogs, lookups, list-indexes): live under _blobify/{contentPrefix}/ inside the space, so they can't be enumerated without knowing the space's contentPrefix.
typescriptcode
unprefixed(path)         = {publicBucketUrl}/{rootPrefix}/{path}
hardened(spaceId, path)  = {publicBucketUrl}/{rootPrefix}/spaces/{spaceId}/_blobify/{contentPrefix}/{path}

If rootPrefix is empty, omit it. If a space has no contentPrefix, omit _blobify/{contentPrefix}/.

3. Path map

Schemas (org-wide, unprefixed)

| What | Path | |---|---| | List of model IDs | schemas/models/index.json | | One model's schema | schemas/models/{model}.json | | List of block IDs | schemas/blocks/index.json | | One block's schema | schemas/blocks/{blockId}.json |

Summaries (per space, hardened)

Lightweight per-content-item records used for listing and finding. Sharded.

| What | Path (relative to space root) | |---|---| | State manifest (entry point) | summaries/{state}/index.json | | Per-model summary shard | summaries/{state}/{model}/{shard}.json |

{state} is published (default) or draft.

Content (per space, hardened)

| What | Path (relative to space root) | |---|---| | Published content doc | content/{model}/{contentId}/published.json | | Draft content doc | content/{model}/{contentId}/draft.json | | Version history index | content/{model}/{contentId}/history/index.json |

Catalogs (per space, hardened — split shared + per-locale)

| What | Path | |---|---| | Shared (non-localizable fields) | catalogs/{model}.{state}.json | | Per-locale (localizable fields) | catalogs/{model}.{locale}.{state}.json |

Lookups (fast field-index shards)

| What | Path | |---|---| | Non-localized field shard | lookups/{state}/{model}/{field}/{shard}.json | | Localized field shard | lookups/{state}/{model}/{field}/{locale}/{shard}.json |

List indexes (sorted/paginated lists)

| What | Path | |---|---| | Manifest | list-indexes/{state}/{model}/{indexId}/{locale}/index.json | | Page | list-indexes/{state}/{model}/{indexId}/{locale}/p00000.json |

Assets

| What | Path | Hardened? | |---|---|---| | Asset catalog | spaces/{spaceId}/assets/catalog.json | yes | | Asset media file | spaces/{spaceId}/assets/media/{assetId}.{ext} | no |

4. Common agent flows

Find a content item by title

  1. Fetch summaries/{state}/index.json.
  2. For each candidate model, fetch the listed summary shards for the relevant locale.
  3. Substring-match on the localized title (or name/slug) field.

Find a content item by slug

If slug is an indexed field, use the lookup shard directly — O(1) instead of scanning summaries:

typescriptcode
GET {space-root}/lookups/published/page/slug/en/{shard}.json

The lookup shard maps slug → contentId.

Get full content

Once you have a contentId:

typescriptcode
GET {space-root}/content/{model}/{contentId}/published.json

Resolve a reference

Reference fields store { model, id }. To resolve, fetch the referenced summary (preferred, smaller) or the full content doc.

5. Localization rules

  • Translatable fields are stored as { locale: value } objects.
  • Non-translatable fields are stored as direct values.
  • The blocks field itself is never localized — but fields inside a block instance can be.
  • Plain-text rich text values are strings, not localized objects, even inside translatable rich-text trees (localization is done at the field level, with one full AST per locale).

When in doubt, fetch the model schema and check translatable: true.

6. Draft vs published

  • Default agent reads should target state: "published".
  • Drafts (state: "draft") require the same API key (private bucket) or a deliberately exposed staging deployment.
  • The API key passed to getOrganization doesn't affect what state you read — that's chosen by the URL you construct.

7. Caching

  • All JSON files served by Blobify are mutable (Cache-Control: no-store, must-revalidate) — fetch fresh.
  • Asset media is immutable (max-age=31536000, immutable) — safe to cache aggressively.
  • For serverless/SSG, tag fetches by content ID / model / locale and invalidate via the webhook payload's revalidationTags.

8. Worked example

Goal: read the published English version of the page with slug map-of-westfjords in space main.

typescriptcode
# 1. Recipe
GET /v1/orgs/{orgId}                       (auth'd)
→ publicBucketUrl, rootPrefix, spaces[].contentPrefix

# 2. Resolve slug → contentId via lookup
GET {bucket}/{rootPrefix}/spaces/main/_blobify/{contentPrefix}/lookups/published/page/slug/en/{shard}.json
{ "map-of-westfjords": "pg_abc123",}

# 3. Fetch full content
GET {bucket}/{rootPrefix}/spaces/main/_blobify/{contentPrefix}/content/page/pg_abc123/published.json
→ full content doc with resolved references and assets

That's the whole read flow — one auth call, then plain HTTPS GETs against a public CDN.