ข้ามไปยังเนื้อหาหลัก
BlueForge

API reference

The integration API

Register what you deploy, from any CI, and read the catalog back as JSON. Base URL https://blueforge.studio. Requests and responses are JSON unless noted.

Overview

EndpointAuthPurpose
POST /api/deploymentsOrg key or deploy tokenRegister a deployment and its sidecar
DELETE /api/deployments/:repo/:appDeploy tokenRetire a registration
GET /api/deploymentsPublicList registrations
GET /api/productsPublicThe merged catalog
GET /api/products/:id/assetsPublicAsset manifest for one product
GET /api/products/:id/assets/:kindPublicRendered image bytes
PUT / DELETE …/assets/:kindDeploy tokenUpload or remove an asset
GET /api/searchPublicCatalog search
GET /api/blogPublicPublished posts
/feed.xml, /sitemap.xmlPublicRSS and sitemap

Errors are JSON with an error string; validation failures (422) add an issues array with the path of each problem.

Authentication

Org API key (your CI)

Send Authorization: Bearer <key> with an API key minted for your organization on forge-auth. The site resolves the key with forge-auth and takes the organization it returns as the tenant for the write. The tenant always comes from the key, never from the request body — a tenant_id field in the body is ignored.

An org admin mints the key on forge-auth with POST /api/v1/keys/org, authenticated with their own key:

bash
curl -X POST https://auth.blueforge.studio/api/v1/keys/org \
  -H "Authorization: Bearer $YOUR_FORGE_AUTH_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "organizationId": "<your-org-uuid>", "name": "ci-register", "expiresInDays": 90 }'
# 201 { key, keyId, name, scopes, organizationId, expiresAt } — the key is shown once

The caller must be an owner or admin of the organization. expiresInDays is 1–90; omit it for a key that does not expire. forge-auth answers 409 ORGS_DISABLED where organizations are not enabled.

Deploy token (the platform)

X-Forge-Deploy-Token is the shared token forge-control uses for platform deploys. It writes as the platform tenant, and it is the only credential the retire and asset-upload routes accept today. When an Authorization: Bearer header is present it always wins; the two are never combined.

401 or 403

StatusBodyMeaning
401{ "error": "unauthorized" }The key is unknown, revoked, expired, not bound to an organization, or could not be resolved — or the deploy token is wrong or missing.
403{ "error": "tenant-mismatch", "keyOrgId", "rowOrgId" }The key is valid, but this (repo, app) is already registered to a different organization. The first organization to register a pair owns it.
503{ "error": "deployments endpoint not configured" }Deploy-token path only: the server has no token configured.

Register a deployment

POST

/api/deployments

Bearer org key · or X-Forge-Deploy-Token

Records that an app is live: upserts the (repo, app) row the catalog reads and appends to the deploy log. Call it after a successful deploy. Treat a failure as a warning — the catalog should never be able to fail your release.

FieldTypeNotes
repostring, requiredLowercase a-z, 0-9, hyphens; 2–80 characters.
appstring, requiredLowercase a-z, 0-9, . _ -; up to 80 characters.
urlURLWhere the app answers.
rolestringe.g. site or app; up to 50 characters.
commitstringCommit sha, up to 64 characters.
deployment_idstringYour deploy's id. One of deployment_id or commit is required.
deployed_atISO datetimeDefaults to now.
imagestringContainer image reference, optional.
sourceenumforge-control (default), manual, backfill.
productobjectThe contents of blueforge.product.yml — the whole file ({ version, products }, first entry used) or one product's fields.
featuresobjectThe contents of blueforge.features.yml, or one row; the row whose product matches is merged in, and top-level promotions are kept.
bash
curl -X POST https://blueforge.studio/api/deployments \
  -H "Authorization: Bearer $FORGE_ORG_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "repo": "acme-store",
    "app": "site",
    "url": "https://acme.example.com",
    "role": "site",
    "commit": "'"$(git rev-parse HEAD)"'",
    "deployment_id": "'"$GITHUB_RUN_ID"'",
    "source": "manual",
    "product": { "version": "2.0.0", "products": [ { "id": "acme-store", "name": "Acme Store", "apps": ["site"] } ] },
    "features": { "version": "2.0.0", "features": [ { "product": "acme-store", "features": ["Stock synced across channels"] } ] }
  }'
StatusMeaning
201{ ok: true, deployment: { repo, app, url, productId, logged: true } } — recorded.
200Same body with logged: false: this deployment_id was already recorded (a retry); nothing new was written.
400The body is not valid JSON.
401 / 403See Authentication.
422{ error: "validation failed", issues }.
503The registry database is not configured.

A registration updates the pre-rendered pages straight away (for a client organization, only its own site). When the product's primary app registers without declaring assets, the site then reads the app's live <head> for a logo and social card — see Asset sync.

From GitHub Actions

An example step: convert the sidecars to JSON, build the body, post it.

.github/workflows/deploy.yml
# .github/workflows/deploy.yml (after your own deploy step)
- name: Register with BlueForge
  env:
    FORGE_ORG_KEY: ${{ secrets.FORGE_ORG_KEY }}
  run: |
    PRODUCT=$(npx --yes yaml@2 --json < blueforge.product.yml)
    FEATURES=$(npx --yes yaml@2 --json < blueforge.features.yml)
    jq -n --arg sha "$GITHUB_SHA" --arg run "$GITHUB_RUN_ID" \
          --argjson product "$PRODUCT" --argjson features "$FEATURES" \
      '{repo:"acme-store", app:"site", url:"https://acme.example.com",
        commit:$sha, deployment_id:$run, source:"manual",
        product:$product[0], features:$features[0]}' \
    | curl -fsS -X POST https://blueforge.studio/api/deployments \
        -H "Authorization: Bearer $FORGE_ORG_KEY" \
        -H "Content-Type: application/json" --data-binary @-

Retire a registration

DELETE

/api/deployments/:repo/:app

X-Forge-Deploy-Token

Removes the app from the registry so the site stops linking to it, and logs the reason. Only for a real removal: the next successful registration of the same pair brings it back. Body: { "reason": "…" } (3–500 characters, required).

bash
curl -X DELETE https://blueforge.studio/api/deployments/acme-store/site \
  -H "X-Forge-Deploy-Token: $FORGE_DEPLOY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Site shut down" }'

200 { ok: true, retired: { … } }; 404 when no such registration exists; 400 without a JSON body; 422 for an invalid reason; 401 for a bad token. This route does not accept org keys yet — ask us to retire an app registered from your CI.

List registrations

GET

/api/deployments

Public · CORS *

Every registered (repo, app). Optional ?tenant=<org-uuid> limits the list to one organization; a value that is not a UUID is a 400. Never cached.

bash
curl "https://blueforge.studio/api/deployments?tenant=<org-uuid>"
# { configured, count, deployments: [ { repo, app, productId, url, role, commit,
#   deployedAt, hasProduct, updatedAt, tenantId } ] }

The catalog

GET

/api/products

Public · CORS *

The merged catalog exactly as the site renders it — registry rows and sidecar copy, with links, status and image URLs resolved.

QueryMeaning
tenantA forge-auth organization UUID. Returns only that organization's registered products — nothing else is mixed in. Not a UUID → 400. Omitted → the BlueForge catalog.
bash
curl "https://blueforge.studio/api/products?tenant=<org-uuid>"
# { generatedAt, count, degraded, sources: { registry, catalog }, products: [ … ] }

Each product carries id, name, tagline, description, status, ecosystems, features, categories, tags, pricing, links, assets, source (deploy or catalog), deployedAt, commit and repo, among others.

Degraded answers

When the registry cannot be read the response says degraded: true, sets the header x-catalog-degraded: registry-unavailable and is sent with no-store. Do not treat a degraded answer as the complete catalog. Healthy answers are cacheable for 60 seconds.

Product assets

GET

/api/products/:id/assets

Public · CORS *

The manifest: { product, assets: [ { kind, slot, sourceSha256, source, variants: { "<variant>": { url, width, height, bytes } } } ] }. Compare sourceSha256 with your file to decide whether to upload.

GET

/api/products/:id/assets/:kind

Public

The rendered bytes. kind is logo, og, cover, hero or screenshot; ?v= picks the variant (defaults: logo 512, og 1200x630, others 1024); ?slot= picks a screenshot (0–99). Supports If-None-Match. When ?s= carries the asset's source sha the response is cached as immutable.

PUT

/api/products/:id/assets/:kind

X-Forge-Deploy-Token

Uploads one original image (raw body, Content-Type: image/*, up to 10 MB); the site renders every size itself. 201 when renditions were written, 200 when the stored original already had this sha. Headers, statuses and the sync loop are on Asset sync. DELETE …/:kind?slot=N removes one (404 when there was nothing to remove).

Blog, feed and sitemap

GET

/api/blog

Public

{ posts: [ … ] } — every published post. On a promotion site's host, that site's posts.

GET

/feed.xml

Public

RSS 2.0 for the blog (cached for an hour at the edge). A promotion site serves its own feed on its host.

GET

/sitemap.xml

Public

Every public page, product page and post. A promotion site serves its own sitemap, on its own origin, listing only its pages.

Site forms

Two public endpoints back the forms on this site. They are rate limited per IP and are not an integration surface, but they are here for completeness.

POST

/api/contact

Public · 10 requests/minute

{ email, message, firstName?, subject? } — message 10–5000 characters; subject is a short lowercase slug naming where the visitor came from (anything else is dropped, not rejected). The message is saved first, then a notification is sent. 200 on success (with queued: true when only the save succeeded), 422 for invalid input, 429 when rate limited, 500 when it could not be saved.

POST

/api/send

Public · 5 requests/hour

Newsletter or waitlist signup: { email, firstName?, intent?, source?, locale? }. intent is newsletter or waitlist, source a short lowercase slug naming where the signup happened, locale a two-letter language code passed on to the newsletter service; malformed values are dropped, not rejected. 200 { ok: true, subscriberId }, or { ok: true, queued: true } when the signup was saved but delivery to the newsletter service is pending; 422, 429 and 500 as above.

ติดตามข่าวสาร

บันทึกการอัปเดตและสิทธิ์เข้าใช้ก่อนใคร

ส่งอีเมลหนึ่งฉบับเมื่อมีอะไรใหม่ปล่อยออกมา: ฟีเจอร์ใหม่ของแพลตฟอร์ม แอปใหม่ และคำเชิญร่วมงานเปิดตัว เข้าร่วมรายชื่อรอเพื่อรับสิทธิ์ใช้งานก่อนใครสำหรับการสมัครด้วยตนเองและเว็บไซต์โปรโมชัน

ไม่มีสแปม ยกเลิกการรับได้ด้วยคลิกเดียว