# Sync to Vercel

A Vercel build reads environment variables that Vercel already holds — there is
no earlier point at which seekrit could inject them. So seekrit stays the source
of truth and pushes each secret into the project as an `encrypted` environment
variable, for the deployment targets the binding names.

| At a glance | |
| --- | --- |
| **What seekrit writes** | Project environment variables, `type: encrypted` |
| **Addressed by** | Vercel project id (`prj_…`) or name, plus one or more targets |
| **Connection carries** | The API token, and a team id for team-owned projects |
| **Token permission** | A token with access to the project — Vercel has no per-resource scope |
| **Takes effect** | Next build. Vercel injects at build and run time from its own copy |
| **Value visibility** | Encrypted at rest by Vercel, and readable by anyone who can read the project's env vars |

New to sync? Read [Third-party sync](/docs/guides/third-party-sync) first — the
decryption grant, name mapping, deletions, and failure handling are the same on
every destination.

## 1. Create a Vercel token

In Vercel, go to **Settings → Tokens** and create a token with access to the
project you want to sync into. If the project belongs to a Vercel Team, note the
team id (`team_…`) — a personal-scope token can't write to a team project, and
Vercel reports that as a bare 403.

Give the token an expiry you will actually notice, and name it after the
connection. Vercel tokens carry their creator's access, so the least-privilege
setup is a machine user added to only the projects it syncs.

## 2. Add the connection

In the dashboard, open **Sync → Add connection**, choose Vercel, paste the token,
and set the team id if you have one. The token is encrypted in your browser
before it is sent.

Or from the CLI. The token is read from stdin — never a flag, so it can't end up
in your shell history — and is wrapped to the connection's public key before it
is sent:

```bash
printf '%s' "$VERCEL_TOKEN" | seekrit sync connect \
  --name acme-production --team-id team_abc
```

Vercel is the default provider, so `--provider vercel` can be left off. The team
id is sent as `?teamId=` on every request the connection makes; omit it for a
personal account.

Check it can reach the project before you bind anything to it:

```bash
seekrit sync verify acme-production --project prj_abc
```

Verify fetches the project (`GET /v9/projects/{id}`), which proves three things
at once: the token is valid, it reaches this project, and the team id is right
or not needed.

## 3. Bind an environment

Pick the application environment to sync and the Vercel project and targets
(`production`, `preview`, `development`). This is the step that shows the
decryption disclosure and creates the key grant, so it has to be done by someone
who can already read the environment.

```bash
seekrit sync enable \
  --connection acme-production \
  --app storefront --env production \
  --project prj_abc \
  --target production \
  --acknowledge-decryption
```

`--target` takes a comma-separated list and defaults to `production`. A binding
writes each variable to every target it names, which is how one seekrit
environment feeds both production and preview from a single binding.

seekrit pushes once immediately, then on every change.

### Preview deployments and one branch

`--git-branch` restricts `preview` writes to a single git branch. Vercel only
honors it when `preview` is among the targets, so seekrit sends it only then —
a production-only binding that carried a branch would be rejected outright.

```bash
seekrit sync enable --connection acme-production \
  --app storefront --env staging \
  --project prj_abc --target preview --git-branch staging \
  --acknowledge-decryption
```

This pairs naturally with [branch environments](/docs/guides/branches): point a
seekrit branch environment at a Vercel `preview` target with its git branch set,
and each PR gets its own overlay downstream. When the branch expires its binding
goes with it, and seekrit stops pushing — the values already on Vercel stay
where they are.

## How a push behaves

- **One request per name**, `POST /v10/projects/{id}/env?upsert=true`. Vercel's
  upsert is create-or-replace, so a run never needs to read the project's
  current values first.
- **Values are stored as `encrypted`.** Vercel can read them — that is inherent
  to pushing anywhere, and is exactly what the sync key grant authorizes.
- **A removal costs a listing.** Deleting needs Vercel's internal env-var id,
  so a run with removals lists the project's variables once, then deletes each.
  Only records whose targets **overlap this binding's** are touched, so two
  bindings can point at one project with different targets and neither will
  delete the other's variables. A variable already gone counts as deleted rather
  than failing forever.
- **A run is capped at 500 operations.** Beyond that it reports that it has more
  to do and the engine re-runs immediately, so a very large first sync is slower
  rather than silently partial.
- **A 429 stops the whole run.** seekrit honors Vercel's `Retry-After` rather
  than its own backoff curve; what already landed is reported, and the rest
  retries.

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| `403` on every name | The project is team-owned and the connection has no team id | Add `--team-id team_…`, or re-create the connection with it |
| `403` after it was working | The token expired, or its owner lost access to the project | Mint a new token and re-create the connection |
| `404` on the project | Wrong project id, or the project was deleted or renamed | Check `prj_…` in the project's Settings; `seekrit sync verify` catches this before a binding exists |
| `400` mentioning `gitBranch` | A branch was set on a binding whose targets don't include `preview` | Add `preview` to `--target`, or drop `--git-branch` |
| `429`, run reported partial | Vercel rate-limited the account | Nothing to do — seekrit backs off and retries the rest |
| Values pushed, site unchanged | Vercel injects env vars at build time | Redeploy. seekrit does not trigger Vercel builds |

## See also

- [Third-party sync](/docs/guides/third-party-sync) — the shared model, naming and filtering, deletions
- [Branch configs](/docs/guides/branches) — per-PR environments that pair with Vercel previews
- [CLI reference](/docs/reference/cli#third-party-sync) — every `seekrit sync` flag
