Skip to main content

Secrets

Orunbase secrets manage sensitive values at the same three scopes as settings — workspace, project, and environment. The API surface is deliberately asymmetric: secret values are write-only. You supply a value on create and rotate; the platform encrypts it before persistence, and no response, event, or audit payload ever contains the value, its ciphertext, or a hash of it. Everything you can read back is metadata — key, status, version, rotation timestamps.

Encryption at rest

Values are encrypted in the worker before they touch storage, using AES-256-GCM (authenticated encryption) with a random 12-byte IV per value. The 256-bit key is sourced from a deployment environment binding — it is never stored alongside the data. What persists is a versioned ciphertext envelope ({ alg: "AES-256-GCM", v: 1, iv, ct }); plaintext is never written.

Resource shape

{
"secret": {
"id": "sec_9c8d7e6f5a4b",
"orgId": "org_1f2e3d4c5b6a",
"projectId": "prj_5e6f7a8b9c0d",
"environmentId": null,
"scopeKind": "project",
"secretKey": "stripe.webhook_signing",
"displayName": "Stripe webhook signing secret",
"status": "active",
"version": 2,
"rotationPolicy": null,
"lastRotatedAt": "2026-06-20T10:03:11.482Z",
"expiresAt": null,
"createdBy": "usr_2b3c4d5e6f7a",
"createdAt": "2026-05-02T08:41:07.909Z",
"updatedAt": "2026-06-20T10:03:11.482Z"
}
}

Endpoints

All paths are relative to a scope base — /v1/organizations/{orgId}/config, …/projects/{projectId}/config, or …/environments/{envId}/config:

MethodPathDescription
GET/secretsList secret metadata at this scope
GET/secrets?scope=allWorkspace route only: list metadata across every rung the workspace owns
POST/secretsCreate a secret (optional write-only value)
POST/secrets/{secretId}/rotateReplace the value; bumps version, sets lastRotatedAt
DELETE/secrets/{secretId}Revoke — soft delete, sets status: "revoked"

Listing requires organization.config.read (workspace scope) or project.config.read; create, rotate, and revoke require the corresponding …config.write action. ?scope=all is valid only on the organization-scope route — on project or environment scope it is rejected with 422.

Create a secret

curl -X POST "https://api.orunbase.com/v1/organizations/org_1f2e3d4c/projects/prj_5e6f7a8b/config/secrets" \
-H "Authorization: Bearer $ORUN_CLOUD_TOKEN" \
-H "Content-Type: application/json" \
-d '{"secretKey": "stripe.webhook_signing", "displayName": "Stripe webhook signing secret", "value": "whsec_..."}'
import { OrunCloud } from "@saas/sdk";

const client = new OrunCloud({
baseUrl: "https://api.orunbase.com",
auth: { kind: "bearer", token: process.env.ORUN_CLOUD_TOKEN! },
});

const scope = { kind: "project", orgId: "org_1f2e3d4c", projectId: "prj_5e6f7a8b" } as const;

const { secret } = await client.config.createSecretMetadata(scope, {
secretKey: "stripe.webhook_signing",
displayName: "Stripe webhook signing secret",
value: process.env.STRIPE_WEBHOOK_SECRET!,
});

The 201 response carries metadata only. value may be omitted to register metadata first and supply the value on a later rotate.

warning

Once written, a value cannot be read back through any Orunbase API. Keep your own copy in a system of record if you need to reference it — the only recovery path is rotating in a new value.

Rotate and revoke

Rotate replaces the value in place: the metadata row keeps its id and key, version increments, and lastRotatedAt is set. Consumers referencing the secret by id or key pick up the new value without re-wiring.

curl -X POST "https://api.orunbase.com/v1/organizations/org_1f2e3d4c/projects/prj_5e6f7a8b/config/secrets/sec_9c8d7e6f5a4b/rotate" \
-H "Authorization: Bearer $ORUN_CLOUD_TOKEN" \
-H "Content-Type: application/json" \
-d '{"value": "whsec_new..."}'
await client.config.rotateSecret(scope, "sec_9c8d7e6f5a4b", {
value: process.env.STRIPE_WEBHOOK_SECRET_V2!,
});

Revoke is the end of the lifecycle — a soft delete that flips status to revoked while preserving the metadata row for audit:

await client.config.revokeSecret(scope, "sec_9c8d7e6f5a4b");

Create, rotate, and revoke all land in the audit log under the config category — with the value redacted by construction, since it never enters any event payload.

The secret.value.use permission

Reading metadata and writing values are gated by the config permissions above. A separate action, secret.value.use, governs the runtime use of a secret's decrypted value. It is held by the owner, admin, and builder workspace roles but not viewer, so read-only members can see that a secret exists without ever being able to exercise its value.

The surface that consumes it is live: the lease-bound run secrets resolve — the only value-returning machine route on the platform — requires secret.value.use as its first gate, alongside a live job lease as its second.

How CI publishes secrets

CI never writes through this API's create/rotate routes. When a job produces values (Terraform outputs, provisioned resource ids), it publishes them through the lease-bound job output secrets channel, which lands them at the project/environment rung of this store: the first publish of a key creates the secret (as the system actor, source: "static"), and later publishes append versions — a rotate, never an overwrite. Those secrets then look and behave like any other row on this page.

Orunbase secrets vs. platform infrastructure secrets

Two different kinds of secret exist in an Orunbase deployment — keep them separate:

  • Orunbase secrets (this page) are tenant data: values your workspaces, projects, and environments manage through the API — third-party API keys, webhook signing secrets, tokens your runs consume. This now includes deployment wiring: Terraform outputs and wiring documents that a tenant's jobs produce publish into this API at the project/environment rung, through the run-lease channel.
  • Platform infrastructure secrets are configuration of the platform itself — its database credentials, the secret-encryption key, its own provider tokens. These never live in this API; they belong in the operating cloud's secret manager and are injected as worker bindings when you run your own instance. See Self-hosting architecture.