Skip to main content

Secrets & Credentials

Workflows and agents need credentials — LLM API keys, database passwords, third-party API tokens, OAuth access tokens. OrcFlows stores all of these encrypted in PostgreSQL and resolves them into workflow expressions and connector calls at execution time. They are never written to logs or execution history in plaintext.

Encryption

  • Both secrets (internal/secrets/pgstore.go) and OAuth connections (internal/oauth/store.go) are encrypted with AES-256-GCM.
  • The encryption key is derived via SHA-256 from JWT_SECRET — there is no separate key to manage, but it does mean rotating JWT_SECRET invalidates stored secrets/tokens (re-enter them after a rotation).
  • A previous design used HashiCorp Vault for secrets; this has been fully replaced by the Postgres-backed store. The vault container may still appear in docker-compose.yml but is unused.

Manual secrets (API keys)

Simple named key/value pairs, scoped to your tenant:

EndpointDescription
GET /api/v1/secretsList secret names (values are never returned).
POST /api/v1/secretsCreate/update a secret — {"name": "STRIPE_API_KEY", "value": "sk_..."}. Admin/Owner only.
DELETE /api/v1/secrets/{name}Remove a secret. Admin/Owner only.

Reference a secret from any step config with {{ secret.NAME }}:

{ "type": "agent", "config": { "api_key": "{{ secret.ANTHROPIC_API_KEY }}" } }

At execution time, the workflow's ExecutionInput.Secrets map is populated by resolving every {{ secret.* }} reference used in the DSL — so only the secrets a workflow actually needs are decrypted for that run.

OAuth connections

For providers with OAuth flows (Google, Notion, Slack — see Configuration), OrcFlows stores a full Connection: access token, refresh token, expiry, scope, and the connected account's email/display name/avatar.

EndpointDescription
GET /api/v1/oauth/statusWhether each provider (google, notion, slack) is configured at the platform level.
GET /api/v1/oauth/{provider}/authorizeStarts the OAuth flow (public — browser redirect, takes a _token query param since redirects can't carry headers).
GET /api/v1/oauth/{provider}/callbackOAuth redirect target (public).
GET /api/v1/oauth/connectionsList this tenant's connected accounts.
DELETE /api/v1/oauth/connections/{id}Disconnect.
GET /api/v1/oauth/connections/{id}/tokenGet a valid access token (auto-refreshes if expired and a refresh_token is present, e.g. Google).
POST /api/v1/oauth/connections/{id}/refreshForce a token refresh.

Connector and agent-tool configs reference an OAuth connection by ID (resolved server-side to a fresh access token) rather than embedding tokens directly.

Manual connections

For connectors that use a static API key/token rather than OAuth (e.g. most of the connector reference), POST /api/v1/connections stores the credential alongside OAuth connections in the same encrypted table, so the credential picker in the UI shows one unified list regardless of auth type.

Credential picker UX

In the canvas, any node field that needs a credential (an API key, an OAuth connection) shows a picker populated from /api/v1/secrets and /api/v1/oauth/connections — you select a stored credential rather than pasting raw values into the workflow definition.

Best practices

  • Use distinct secrets per environment (dev/staging/prod — see Definition.Env) so a leaked staging key can't touch production data.
  • Prefer OAuth connections over long-lived API keys where the provider supports it (Google, Notion, Slack) — tokens can be revoked centrally and (for Google) auto-refresh.
  • Rotate JWT_SECRET only with a plan to re-enter secrets/OAuth connections afterward, since it doubles as the encryption key.

Next