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 session | API key | |
|---|---|---|
| Created by | culvii login | culvii key create (CLI) or via the Console |
| Stored in | ~/.culvii/config | .env files, secret managers, CI secrets |
| Used by | The CLI (culvii dev, culvii deploy, culvii run, etc.) | The SDK at runtime, via initConfig |
| Identifies | A human (you) | A machine workload (your deployed workflow, your CI pipeline) |
| Lifetime | 30 days, sliding window. Re-auth via browser when expired. | Until rotated or revoked |
| Prefix | n/a | ck_dev_…, ck_sandbox_…, ck_prod_… |
| Scope | All actions you have permission for | A 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.
| Scope | Can do |
|---|---|
admin | Tenant administration, including managing other keys |
developer | Create and update workflow definitions, deploy, debug |
runner | Trigger runs, read run state and logs |
read-only | Read-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'smain.ts.CULVII_API_KEY=…in CI forculvii 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
0600permissions). - 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.