Documentation menu

Providers

Vercel

Uses the Ahena CLI (beta). Install it with npm install -g @ahena/cli, or prefix commands with npx @ahena/cli. See Installation. The dashboard covers projects, connections, Doctor, the Stack Graph, plans and approvals without it.

Overview

Ahena checks that your Vercel project is set up the way ahena.config.ts says: the right environment variables in each environment, your domains attached, verified and pointing at Vercel, and a healthy latest production deployment. It creates and updates plain environment variables and adds domains after you approve them. Ahena never deploys, redeploys, reads build logs or decrypts an environment variable. Your app is deployed and served by Vercel directly; Ahena is never in the request path.

Package: @ahena/provider-vercel · Category: deployment (config key deployment) Capabilities: projects, env-vars, sensitive-env-vars, domains, dns-requirements, deployment-status

Maturity: verified against a fake of the API; not yet verified against the real service.

Connection

Field Secret
VERCEL_TOKEN yes A Vercel access token, scoped to the team that owns the project.
VERCEL_TEAM_ID no The team id (team_…). Leave it out for a project in a personal account. Every request carries it as ?teamId=.
VERCEL_PROJECT no Project name or id. Offered from the token at connect; deployment.project in ahena.config.ts takes precedence.
VERCEL_TOKEN=… ahena connect vercel -e production --set VERCEL_TEAM_ID=team_… --set VERCEL_PROJECT=leo

Network: only api.vercel.com (enforced).

Permissions

Vercel access tokens have no fine-grained permissions to choose or to list: a token reaches either your whole account or one team. Ahena's permission check is therefore authentication-only (permissionCheck: "authentication-only"). At connect, validate() reads the token's own metadata (GET /v5/user/tokens/current) and reports its scope (account or team:<id>), refuses a token scoped to a different team than VERCEL_TEAM_ID, refuses a token Vercel has flagged as leaked, and, when a project is set, reads it once: a token scoped to the wrong team gets 403 or 404 there. A token that can read but not write (for example a team member role that can't manage environment variables) only shows as a 403 at apply time.

Least privilege: create the token scoped to the project's team, not "Full Account", and give it an expiration. Ahena never needs to create projects, deploy, or manage billing or members.

Capabilities

Capability Access Verification Changes What Ahena does
projects read-only Framework, root directory, Node.js version, git link, production aliases.
env-vars writable read-back CONFIRMATION_REQUIRED Creates plain variables per target and updates a value the target owns alone.
sensitive-env-vars manual Checks presence by name; gives the exact command to set it from the Ahena secret.
domains writable read-back CONFIRMATION_REQUIRED, SAFE Adds a domain to the project; asks Vercel to re-check ownership (SAFE).
dns-requirements manual Lists the TXT, A or CNAME records Vercel asks for.
deployment-status read-only State (READY/ERROR/BUILDING…) and URL of the latest production deployment.

ahena lock records each variable's key, targets and type (never values), the project's domains and whether they're verified, and the framework.

Environments and Vercel targets

Each Ahena environment maps to one Vercel target:

Ahena environment kind Vercel target
production production
preview, staging preview
development, local development

deployment.target overrides this per environment, with production, preview, development or the slug of a custom environment of the project (for example target: { staging: "staging" }). Custom environments are looked up in the project; variables for them are created with customEnvironmentIds and domains with customEnvironmentId. Branch-specific preview variables (gitBranch) are ignored when matching.

Configure

deployment: {
  provider: "vercel",
  project: "leo",
  target: { staging: "staging" },                     // optional
  domains: { production: ["leo.example.com"] },
  env: {
    NEXT_PUBLIC_SITE_URL: { production: "https://leo.example.com", preview: "https://preview.leo.example.com" },
    STRIPE_SECRET_KEY: { secret: true },              // value: the Ahena secret STRIPE_SECRET_KEY
    DATABASE_URL: { secret: true, from: "NEON_DATABASE_URL" },
  },
},

Any value can be keyed by environment slug. Only names of secrets go in config: a variable whose name looks like a secret (…KEY, …TOKEN, …SECRET, …PASSWORD, DATABASE_URL, …) with a literal value is refused.

Change Classification Notes
Create a plain environment variable for the target CONFIRMATION_REQUIRED Production included. Takes effect on the next deployment of that target; Ahena doesn't redeploy.
Update a plain variable's value CONFIRMATION_REQUIRED Only when the variable belongs to this target alone. Next deployment, as above.
Add a domain to the project CONFIRMATION_REQUIRED Production or a custom environment only. DNS records are manual (Doctor lists them).
Ask Vercel to re-check a domain's ownership SAFE Changes nothing but the verification state.

Idempotent: before every write Ahena lists the project's variables or domains again (following pagination.next with until to the end) and writes only what's still missing, so re-applying a plan creates nothing twice. Created variables carry the comment Managed by Ahena. Vercel's upsert is never used.

Verification: after apply, Ahena re-plans. A plain variable is read back and compared. Encrypted and sensitive values aren't readable without decrypting, which Ahena never does, so for those the re-read confirms the variable exists for the target (existence only). This also covers teams with the "Enforce Sensitive Environment Variables" policy, where Vercel stores a plain variable as sensitive.

Refused (never planned):

  • Deleting projects, domains or environment variables, and removing a production domain.
  • Changing framework, build, install or output settings, root directory or Node.js version.
  • Decrypting variables (decrypt=true is never sent), or putting a secret value in a plan.
  • Changing a variable that's shared with other targets (Doctor gives the manual steps).
  • Turning a sensitive or encrypted variable into a plain one.
  • Adding a domain to preview or development, or moving a domain from another project (Vercel answers 409).
  • Creating, redeploying, promoting or rolling back deployments.

Limitations

  • Sensitive variables are set by you. Ahena keeps secret values in its own store and a provider never receives them, so Ahena can't write them to Vercel. Doctor checks they exist (by name) and gives the exact command. Values are never compared.
  • Values of encrypted and sensitive variables are never compared: only presence.
  • A variable shared across targets (for example production and preview in one entry) isn't changed.
  • Shared (team-level) environment variables, project settings, deployment protection, git settings, redirects and rewrites aren't managed.
  • GET /v10/projects/{idOrName}/env doesn't document until; Ahena follows pagination.next with until as Vercel documents for all paginated listings, and stops with an error rather than assume a variable is missing if a listing doesn't end.
  • Vercel doesn't publish token permissions, so a token that can read but not write is only caught at apply.

Manual steps

  1. Set each secret variable from its Ahena secret (Doctor prints this per variable): ahena env reveal production STRIPE_SECRET_KEY --raw | vercel env add STRIPE_SECRET_KEY production --sensitive (development targets can't hold sensitive variables: drop --sensitive there).
  2. Add the DNS records Doctor lists for each domain (TXT for ownership, then A for an apex or CNAME for a subdomain), at your DNS provider.
  3. Redeploy after environment variables change: Vercel applies them at build time. Doctor reports variables changed after the live production deployment.

Doctor checks

Id Severity Meaning
vercel.token PASS / FAIL Token accepted (403 invalidToken or 401 → rejected).
vercel.token.leaked FAIL Vercel flagged the token as leaked.
vercel.token.expiry WARNING Token expires within 14 days.
vercel.team PASS / FAIL VERCEL_TEAM_ID reachable; token scoped to another team.
vercel.project PASS / INFO / FAIL Project reachable (403/404: wrong team or name); INFO when none is declared.
vercel.project.git INFO Linked git repository and production branch, or none.
vercel.target FAIL deployment.target names no standard target or custom environment.
vercel.env.<key> PASS / WARNING / FAIL Present for the target; set only in another target ("in preview but not production" and the reverse); plain value differs; secret missing (MANUAL command) or stored as plain.
vercel.env.plain_secrets WARNING Other variables that look like secrets are stored as plain.
vercel.env.redeploy INFO Declared variables changed after the latest production deployment: redeploy (MANUAL).
vercel.domain.<name> PASS / FAIL Attached and verified; not attached (fixable); unverified with the TXT records.
vercel.domain.<name>.dns PASS / FAIL Vercel's domain configuration: misconfigured DNS with the A or CNAME records to add.
vercel.deployment.production PASS / INFO / WARNING / FAIL Latest production deployment READY; ERROR (FAIL in production) with its URL and inspector link, no logs; building or none (INFO).

Secret variables are also checked on the Ahena side: each { secret: true } name must exist in the environment's secrets (ahena env set <env> <NAME>).

Set up step by step

  1. Create the token. Vercel dashboard → Account Settings → Tokens → Create. Name it ahena, choose the team that owns the project as its scope, and set an expiration. Copy the team id from Team Settings → General (team_…).

  2. Minimum permissions. Vercel tokens have no finer scopes than account or team; scope it to the one team. Your role in that team must allow managing environment variables and domains (Owner or Member).

  3. Connect.

    VERCEL_TOKEN=… ahena connect vercel -e production --set VERCEL_TEAM_ID=team_… --set VERCEL_PROJECT=leo

    Repeat with -e preview / -e development for the other environments (the same token is fine).

  4. Verify the connection. ahena inspect vercel -e production shows the project, variable names and targets, domains and the latest production deployment.

  5. Run Doctor. ahena doctor -e production

  6. Plan. ahena plan -e production (or ahena configure vercel -e production to plan and apply this provider only).

  7. Apply an allowed change. ahena apply -e production, or ahena configure vercel -e production; approve the CONFIRMATION_REQUIRED changes (in the dashboard for production). Then set secrets and DNS records by hand as Doctor lists, and redeploy when you're ready.

  8. Verify. Apply re-reads Vercel and reports each change VERIFIED. Run ahena doctor -e production again; ahena plan -e production should show nothing for Vercel.

  9. Troubleshooting.

    Symptom Cause Fix
    "Vercel rejected the access token" Token deleted, expired or mistyped (403 invalidToken) Create a new token and reconnect.
    "scoped to another team" Token's scope isn't VERCEL_TEAM_ID Create a token scoped to that team.
    Project 403 or 404 Wrong team id, project name, or a personal-scope token Check VERCEL_TEAM_ID and deployment.project.
    Domain "already assigned to another Vercel project" (409) The domain is on another project Remove it there yourself; Ahena never moves domains.
    Domain stays unverified TXT record missing Add the record Doctor lists, then ahena configure vercel re-checks.
    Variable changed but the site still uses the old value Variables apply at build time Redeploy in Vercel.
    "differs … and is shared with" One Vercel entry serves several targets Split it in the dashboard (Doctor's steps).
  10. Disconnect and revoke. ahena disconnect vercel -e production removes the token Ahena stored. Delete the token at https://vercel.com/account/settings/tokens.

Disconnect behavior

Removes the connection and the token Ahena stored; Ahena doesn't revoke the token itself (you may use it elsewhere). Delete it at https://vercel.com/account/settings/tokens. Projects, environment variables, domains and deployments are untouched.