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=trueis 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}/envdoesn't documentuntil; Ahena followspagination.nextwithuntilas 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
- 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--sensitivethere). - 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.
- 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
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_…).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).
Connect.
VERCEL_TOKEN=… ahena connect vercel -e production --set VERCEL_TEAM_ID=team_… --set VERCEL_PROJECT=leoRepeat with
-e preview/-e developmentfor the other environments (the same token is fine).Verify the connection.
ahena inspect vercel -e productionshows the project, variable names and targets, domains and the latest production deployment.Run Doctor.
ahena doctor -e productionPlan.
ahena plan -e production(orahena configure vercel -e productionto plan and apply this provider only).Apply an allowed change.
ahena apply -e production, orahena 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.Verify. Apply re-reads Vercel and reports each change VERIFIED. Run
ahena doctor -e productionagain;ahena plan -e productionshould show nothing for Vercel.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_IDCreate a token scoped to that team. Project 403 or 404 Wrong team id, project name, or a personal-scope token Check VERCEL_TEAM_IDanddeployment.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 vercelre-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). Disconnect and revoke.
ahena disconnect vercel -e productionremoves 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.