Skip to main content

Authentication

Culvii uses two distinct authentication mechanisms. Knowing which is which avoids almost all the confusion you'll otherwise hit.

The two mechanisms at a glance

OAuth sessionAPI key
Created byculvii loginculvii key create (CLI) or via the Console
Stored in~/.culvii/config.env files, secret managers, CI secrets
Used byThe CLI (culvii dev, culvii deploy, culvii run, etc.)The SDK at runtime, via initConfig
IdentifiesA human (you)A machine workload (your deployed workflow, your CI pipeline)
Lifetime30 days, sliding window. Re-auth via browser when expired.Until rotated or revoked
Prefixn/ack_dev_…, ck_sandbox_…, ck_prod_…
ScopeAll actions you have permission forA scope (admin, developer, runner, read-only) and optionally a single workspace

The short version: OAuth sessions are for humans running CLI commands. API keys are for code running somewhere — your deployed workflow, your CI pipeline, your test runners.

OAuth sessions — culvii login

Run once on each machine you use:

culvii login

The CLI opens a browser window. You sign in with Google through Culvii's identity provider. The browser redirects back to a one-shot loopback server on your machine, the CLI exchanges a code for tokens, and writes them to ~/.culvii/config.

The flow is the standard OAuth 2.0 Authorization Code grant with PKCE — the same shape used by GitHub CLI (gh), Google Cloud CLI (gcloud), and Stripe CLI. Tokens never travel through URLs, only through HTTPS request bodies.

After you've logged in, every CLI command that needs authentication picks up the stored token automatically. You don't pass it explicitly. When the access token expires, the CLI refreshes it transparently.

To log out, culvii logout. This revokes the refresh token and deletes the local config file.

API keys — for the SDK and CI

API keys exist because OAuth sessions don't work for code running unattended. A workflow running in production cannot open a browser and click "sign in." A GitHub Action cannot complete an interactive flow. For those cases, you create an API key.

Creating a key

In the CLI:

culvii key create --name "github-actions-deploy" --scope developer

Or in the Console: Settings → API keys → New key.

Either way, the key secret is shown to you exactly once. After that, the platform stores only a fingerprint. There is no way to recover a lost key — you create a new one.

Key prefixes carry the environment

Every key looks like:

ck_dev_a3f9b8e6c2d4...
ck_sandbox_a3f9b8e6c2d4...
ck_prod_a3f9b8e6c2d4...

The prefix tells the SDK (and a human reading a log) which environment the key belongs to. There is no separate --env argument when initializing the SDK; the key alone determines the target.

Where the key goes

In code, the SDK reads the key from initConfig:

import { initConfig } from '@culvii/kit'

initConfig({
apiKey: process.env.CULVII_API_KEY,
})

Set CULVII_API_KEY in .env.dev, .env.sandbox, or .env.prod — one file per environment. In CI, set it as a GitHub Environment secret (or your CI's equivalent).

Never commit a key to your repository. The platform fingerprints key reuse and will flag obvious mistakes, but the right defense is to keep keys out of source control.

Key scopes

A key carries a scope. The scope decides which actions the key can take.

ScopeCan do
adminTenant administration, including managing other keys
developerCreate and update workflow definitions, deploy, debug
runnerTrigger runs, read run state and logs
read-onlyRead-only access (audit, observability)

Scopes are coarse-grained on purpose. Finer-grained per-resource permissions (the permit() declarative model) are on the roadmap.

Rotating and revoking

culvii key rotate <key-fingerprint> # generates a replacement; old key valid for 24 hours
culvii key revoke <key-fingerprint> # immediate

Rotation creates a new key and gives you a 24-hour overlap window to update consumers. After 24 hours the old key auto-revokes.

Revocation is immediate. Future requests using a revoked key return a specific error (not a generic 401), so the consumer can tell the difference between "wrong key" and "revoked key."

Which mechanism for which command

A reference for when you'd reach for which.

Use the OAuth session (run culvii login first):

  • culvii dev — live development against your dev environment.
  • culvii deploy — when you're a human running a deploy from your laptop.
  • culvii run — interactively triggering a run.
  • culvii logs — tailing logs interactively.
  • culvii key create — minting new keys (you can never use one key to mint another).
  • culvii workspace create — workspace administration.

Use an API key:

  • initConfig({ apiKey }) in your deployed workflow's main.ts.
  • CULVII_API_KEY=… in CI for culvii deploy --yes.
  • Any unattended automation that calls the platform.

Why this split

The split looks like duplication. It isn't. They're different trust models.

OAuth sessions identify a person and the actions they take. They expire, refresh, and revoke easily. They're auditable per-human.

API keys identify a workload — a piece of code running somewhere. They live longer because rotating them mid-run breaks the workload. They scope tighter (per-environment, optionally per-workspace) because their threat model is "what if this leaks." They're auditable per-key.

Forcing humans to use API keys would make audit logs lie about who did what. Forcing code to use OAuth would mean a workflow couldn't run while you slept. Each mechanism fits its use case.

What's coming

  • Per-organization SSO (Okta, Azure AD, Ping, SAML) — replaces the Google sign-in default for tenants that need it.
  • OS keychain storage for the OAuth refresh token (today: file at 0600 permissions).
  • Device authorization grant for headless servers (today: browser-based login only).
  • Service-account / machine identity as a first-class concept distinct from API keys.
  • Per-resource permissions via the permit() declarative model.

All on the roadmap.