BlueForge Docs

Environment Variables

Environment variables are scoped at the app level (deployer role) for runtime configuration or at the project level (admin role) for shared infra secrets. Values are encrypted at rest with audit logging.

Endpoint

App-level env vars (
/api/apps/:appId/env-vars
, deployer role)

MethodPathAuthIdempotent
GET
/api/apps/:appId/env-vars
deployer Beareryes
GET
/api/apps/:appId/env-vars/categories
deployer Beareryes
POST
/api/apps/:appId/env-vars
deployer Bearerno
POST
/api/apps/:appId/env-vars/for-deployment
deployer Beareryes
POST
/api/apps/:appId/env-vars/bulk
deployer Beareryes
PATCH
/api/apps/:appId/env-vars/:id
deployer Bearerno
DELETE
/api/apps/:appId/env-vars/:id
admin Beareryes
DELETE
/api/apps/:appId/env-vars/
admin Beareryes
GET
/api/apps/:appId/env-vars/audit
admin Beareryes

Project-level env vars (
/api/projects/:projectId/env-vars
, admin role)

MethodPathAuthIdempotent
GET
/api/projects/:projectId/env-vars
admin Beareryes
POST
/api/projects/:projectId/env-vars
admin Bearerno
PATCH
/api/projects/:projectId/env-vars/:id
admin Bearerno
DELETE
/api/projects/:projectId/env-vars/:id
admin Beareryes

Parameters

NameTypeRequiredDefaultNotes
appId
stringyes (path)App UUID
projectId
stringyes (path / query)Project UUID
key
stringyes (body)Max 128 chars, matches
^[a-zA-Z0-9_./-]{1,128}$
value
stringyes (body)Max 8KB; encrypted at rest (AES-256)
target
stringno
"production"
Deployment target scope
branch
stringnull
null
Branch-specific override
category
stringno
null
Must match
^[a-z0-9][a-z0-9-]{0,49}$
isPreview
booleanyes (for-deployment)Whether this is a preview deployment
since
queryno (audit)ISO 8601 or
Nd
shorthand (e.g.
7d
)
limit
queryno (audit)Max audit log rows to return
actor
queryno (audit)Filter audit log by actor identity
action
queryno (audit)Filter audit log by action name

Response

// GET /api/apps/:appId/env-vars — list app env vars
Array<{
  id: string;
  appId: string;
  key: string;
  value: string;           // decrypted
  target: string | null;
  branch: string | null;
  category: string | null;
  createdAt: string;
  updatedAt: string;
}>

// GET /api/apps/:appId/env-vars/categories
Array<{
  category: string;
  count: number;
}>

// POST /api/apps/:appId/env-vars (201)
{
  id: string;
  appId: string;
  key: string;
  target: string | null;
  branch: string | null;
  category: string | null;
  createdAt: string;
}

// POST /api/apps/:appId/env-vars/for-deployment
{
  envVars: Record<string, string>;   // resolved key → value map
}

// POST /api/apps/:appId/env-vars/bulk (201)
{
  upserted: number;
  errors: Array<{ key: string; error: string }>;
}

// GET /api/projects/:projectId/env-vars — list project env vars
Array<{
  id: string;
  projectId: string;
  key: string;
  value: string;
  target: string | null;
  branch: string | null;
  createdAt: string;
}>

// GET /api/apps/:appId/env-vars/audit
{
  rows: Array<{
    createdAt: string;
    actor: string;
    action: string;
    key: string;
    target: string | null;
    branch: string | null;
    beforeFp: string;     // fingerprint of previous value
    afterFp: string;      // fingerprint of new value
  }>;
  total: number;
}

// DELETE single — 204 No Content
// DELETE bulk — { deleted: number }

// Error responses
{ error: "Missing appId" }                                       // 400
{ error: "key and value are required" }                          // 400 (create)
{ error: "category must be lowercase alphanumeric + hyphens, max 50 chars" } // 400
{ error: "max 100 entries per request" }                          // 400 (bulk)
{ error: "value too large for key ${key}" }                       // 400 (bulk)
{ error: "duplicate key ${key} in payload" }                      // 400 (bulk)
{ error: "Env var not found" }                                   // 404

Examples

# List app env vars filtered by category
curl -H "Authorization: Bearer $BF_DEPLOYER_KEY" \
  "https://api.blueforge.studio/api/apps/app_xxx/env-vars?category=database"

# List categories for an app
curl -H "Authorization: Bearer $BF_DEPLOYER_KEY" \
  https://api.blueforge.studio/api/apps/app_xxx/env-vars/categories

# Create an app env var
curl -X POST https://api.blueforge.studio/api/apps/app_xxx/env-vars \
  -H "Authorization: Bearer $BF_DEPLOYER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"key": "DATABASE_URL", "value": "postgres://...", "category": "database"}'

# Resolve env vars for a deployment
curl -X POST https://api.blueforge.studio/api/apps/app_xxx/env-vars/for-deployment \
  -H "Authorization: Bearer $BF_DEPLOYER_KEY" \
  -H "Content-Type: application/json" \
  -d '{"branch": "main", "isPreview": false}'

# Bulk upsert (max 100 entries)
curl -X POST https://api.blueforge.studio/api/apps/app_xxx/env-vars/bulk \
  -H "Authorization: Bearer $BF_DEPLOYER_KEY" \
  -H "Content-Type: application/json" \
  -d '[{"key": "API_KEY", "value": "sk-...", "target": "production"}]'

# Query audit log
curl -H "Authorization: Bearer $BF_ADMIN_KEY" \
  "https://api.blueforge.studio/api/apps/app_xxx/env-vars/audit?since=7d&limit=50"

# Create a project-level env var (admin-only)
curl -X POST https://api.blueforge.studio/api/projects/prj_xxx/env-vars \
  -H "Authorization: Bearer $BF_ADMIN_KEY" \
  -H "Content-Type: application/json" \
  -d '{"key": "SHARED_SECRET", "value": "s3cr3t", "target": "production"}'
import { Client } from "@blueforge/client"

const client = new Client({ apiKey: process.env.BF_DEPLOYER_KEY })

// List env vars for an app
const envVars = await client.get(`/api/apps/${appId}/env-vars`)

// Create env var
const envVar = await client.post(`/api/apps/${appId}/env-vars`, {
  key: "DATABASE_URL",
  value: "postgresql://...",
  category: "database",
})

// Resolve for deployment
const resolved = await client.post(
  `/api/apps/${appId}/env-vars/for-deployment`,
  { branch: "main", isPreview: false }
)

// Bulk upsert
const result = await client.post(`/api/apps/${appId}/env-vars/bulk`, [
  { key: "API_KEY", value: "sk-...", target: "production" },
  { key: "LOG_LEVEL", value: "debug", target: "staging" },
])

Pitfalls

  • All values are encrypted at rest using AES-256. The response body contains decrypted plaintext — secure the transport (TLS) and do not log the response.
  • Bulk upsert is limited to 100 entries per request. Keys are capped at 128 characters, values at 8KB.
  • Category names must match
    ^[a-z0-9][a-z0-9-]{0,49}$
    — lowercase alphanumeric plus hyphens, max 50 chars.
  • Bulk and single DELETE operations are admin-only at the app level (inline
    requireRole('admin')
    upgrades the mount-level
    deployer
    gate).
  • Audit logs capture fingerprint hashes of before/after values, not the raw values. You can detect that a value changed but cannot reconstruct the old value.
  • The bulk DELETE endpoint accepts
    target
    and
    branch
    in the body but currently deletes ALL rows matching the key regardless of target/branch. Call once-per-row if finer targeting is needed.
  • The
    for-deployment
    endpoint resolves env vars by matching branch + isPreview flags. Preview deployments may resolve different values than production.

See also

Tested against

  • @blueforge/hosting-api: 2.4.1
  • Last verified: 2026-07-15