Skip to main content
BlueForge

Asset sync

Brand imagery, synced from your repo

Your logo, social card, cover and screenshots live in your repo next to the copy. After a deploy only the files whose sha256 changed are uploaded, and the site renders every size it needs from the original.

Declare assets

In blueforge.product.yml, under the product's assets key. Paths are relative to the repo root. Declare what exists rather than inventing a file to fill a key — missing kinds fall back (below).

blueforge.product.yml (excerpt)
    assets:
      logo: public/brand/logo-512.webp      # square mark, transparent preferred
      og: public/brand/og-1200x630.webp     # social card
      cover: public/brand/cover.webp        # wide hero for the product page
      screenshots:                          # gallery, in this order
        - path: public/screens/orders.webp
          caption: Orders from intake to delivery
          alt: The orders dashboard

Any format the image pipeline decodes is accepted — png, jpeg, webp, gif, avif, svg, tiff — up to 10 MB per file. The site stores webp renditions.

Kinds and renditions

KindRenditions (variant names)Used for
logo512, 128 — square, contained on transparentCards, product page, search
og1200x630, 512 (512×320 card thumb)og:image / twitter:image on the product page
cover2048 (2048×1280), 1200x630, 1024 (1024×640), 512 (512×320)Product page hero and listing cards; social card when there is no og
hero1024 (1024×640), 512 (512×320)Used as the cover only when no cover exists
screenshot1024 (1024×640), 512 (512×320), per slotProduct page gallery and /apps thumbnails

The sync protocol

On a platform deploy, forge-control runs this after the deploy succeeds. It is idempotent:

  1. GET /api/products/:id/assets (public) returns the manifest, with sourceSha256 per (kind, slot).
  2. For each declared file, compute its sha256. If the manifest already holds that sha for the same kind and slot, skip it.
  3. Otherwise PUT /api/products/:id/assets/:kind (add ?slot=N for screenshots) with the raw file as the body.
  4. Screenshots are positional — slot 0, 1, 2 … in declared order. DELETE …/screenshot?slot=N removes one.
bash
SHA=$(shasum -a 256 public/brand/logo-512.webp | cut -d' ' -f1)

curl -X PUT "https://blueforge.studio/api/products/acme-store/assets/logo" \
  -H "X-Forge-Deploy-Token: $FORGE_DEPLOY_TOKEN" \
  -H "Content-Type: image/webp" \
  -H "X-Asset-Sha256: $SHA" \
  -H "X-Asset-Source: sidecar" \
  --data-binary @public/brand/logo-512.webp
HeaderMeaning
X-Forge-Deploy-TokenRequired.
Content-TypeRequired, image/*.
X-Asset-Sha256Optional; the upload is refused if it does not match the body.
X-Asset-Sourcesidecar, capture, manual (default) or discovered.
X-Asset-Source-UrlOptional; where the original came from.
StatusMeaning
201Renditions written.
200The stored original already had this sha — nothing to do.
400Empty body, sha mismatch, invalid product id or slot, or a slot on a non-screenshot kind.
401Bad or missing token.
404Unknown kind.
413Larger than 10 MB.
415Not an image, or not decodable.

Uploading from your own CI

The upload and delete routes accept only the platform deploy token today, not org API keys. If you register from your own CI, leave assets out of the sidecar you send and let discovery (below) pick up the mark and social card from your live site — or talk to us about uploads.

Where the site reads from

For each kind the site takes the first that exists:

  1. an uploaded asset (from a sidecar sync, a screenshot capture, or a manual upload);
  2. an asset discovered from the product's live site;
  3. an image bundled with the BlueForge catalog, if the product has one.

The cover has its own chain: uploaded cover, then hero, then screenshot slot 0. So a hero only ever appears where a cover is missing.

Stored objects are content-addressed: a replaced file gets a new URL (it carries the source sha), which is why image responses can be cached for a year without going stale.

Discovery from your site

When a product's primary app registers and its sidecar declares no assets, the site reads that app's live page <head> for its icon and og:image after answering the registration, and stores what it finds. It only fills gaps: a kind already uploaded from a sidecar or capture is left alone, and a repo that later declares assets takes over on its next platform deploy.

The approval gate

The gate covers imagery, not the YAML (the YAML is validated by the schema). It answers one question per upload: may this rendition be shown?

  • A vision model can review each upload and record an advisory verdict.
  • A high-confidence approval can become a standing decision automatically. Rejection always needs a person.
  • With the gate on, an image nobody has approved is answered 404 to the public, as if it did not exist.
  • A replaced image is versioned, not deleted: the previously approved version keeps showing while the new one waits, and rolling back is restoring that version.

These stages are switched on independently and in that order, so whether your upload shows immediately depends on which stages are live.

Stay in the loop

Release notes and early access

One email when something ships: new platform features, new apps and launch invites. Join the waitlist to get early access to self-serve sign-up and promotion sites.

No spam. Unsubscribe with one click.