# Sync to LangGraph Platform

LangGraph Platform runs your Agent Server for you, which means there is no
container to inject into: the deployment's **secrets** are its environment, and
the control plane is the only way to set them. A binding owns one deployment's
secrets.

| At a glance | |
| --- | --- |
| **What seekrit writes** | A deployment's `secrets`, delivered to the agent container as environment variables |
| **Addressed by** | The deployment UUID |
| **Connection carries** | The region (or a self-hosted control-plane URL), and a workspace ID for org-scoped keys |
| **Token permission** | A LangSmith API key that can update the deployment |
| **Takes effect** | On the **new revision** the write creates — a rebuild and rollout |
| **Value visibility** | Readable through the control plane by anyone with a key for the workspace |

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.

> **Warning:** **A push redeploys the agent.** LangGraph Platform applies a secret change by creating a new revision, and its control plane offers no way to stage a value without shipping it. In-flight runs are interrupted. seekrit keeps that to the minimum it can: it reads the deployment first and **sends nothing when nothing would change**, so the periodic reconcile never rolls your agent on its own. But a rotation does. If the agent runs somewhere you control the process instead, [`seekrit run`](/docs/guides/run) injects the same values with no copy at the platform and no redeploy.

## 1. Create a LangSmith API key

In LangSmith, **Settings → API keys → Create API key**. Prefer a
**workspace-scoped** key (`lsv2_pt_…`): it names its own workspace, so the
connection needs nothing else. An **organization-scoped** key reaches every
workspace in the org, and without a workspace ID the control plane refuses it
with a bare `403`.

```bash
printf '%s' "$LANGSMITH_API_KEY" \
  | seekrit sync connect --name acme-langgraph --provider langgraph-platform
```

That is the whole connection for a US account. Two things can change it:

```bash
# An account in another region — chosen at signup and fixed afterwards.
printf '%s' "$LANGSMITH_API_KEY" \
  | seekrit sync connect --name acme-langgraph --provider langgraph-platform \
      --langgraph-region eu

# An organization-scoped key, which has to say which workspace.
printf '%s' "$LANGSMITH_API_KEY" \
  | seekrit sync connect --name acme-langgraph --provider langgraph-platform \
      --langgraph-tenant 8f1c2b3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d
```

The region matters more than it looks. LangChain runs the control plane on four
hosts, a key minted in one is **not accepted by another**, and the failure is a
plain `401` — so the wrong region here looks exactly like a bad credential:

| `--langgraph-region` | Control-plane host |
| --- | --- |
| `us` (default) | `https://api.host.langchain.com` |
| `eu` | `https://eu.api.host.langchain.com` |
| `apac` | `https://apac.api.host.langchain.com` |
| `aws-us` | `https://aws.api.host.langchain.com` |

An account created at `smith.langchain.com` is `us`.

> **Note:** **Self-hosted LangSmith.** Pass `--base-url` instead of `--langgraph-region`, pointing at the control plane on your own host — it is served under `/api-host`, e.g. `https://langsmith.acme.com/api-host`. It must be `https:`, because that address carries the API key. Setting both a region and a base URL is refused rather than resolved in seekrit's favour.

## 2. Bind an environment

A deployment has **one** set of secrets, so the deployment is the whole
destination — there is no per-revision or per-graph scope, and no equivalent of
Vercel's production/preview split. A deployment that needs different values is a
different binding.

```bash
seekrit sync verify acme-langgraph --provider langgraph-platform \
  --langgraph-deployment 3970e0fe-8564-4903-9a55-c5f8de49fb8b

seekrit sync enable --connection acme-langgraph --provider langgraph-platform \
  --langgraph-deployment 3970e0fe-8564-4903-9a55-c5f8de49fb8b \
  --app storefront-agent --env production --acknowledge-decryption
```

`--langgraph-deployment` is the deployment UUID from its dashboard URL, or the
`id` from `GET /v2/deployments`; `--app` is the seekrit application the
environment belongs to.

Verify checks the deployment through its **revision list** rather than by
fetching the deployment. Both prove the same three things you can get wrong from
the connection dialog — key valid, workspace reachable, deployment real — but a
revision carries only ids, timestamps, status and source, never a value. A
deployment whose first revision is still building answers with an empty list,
which passes: the deployment is there, and that is the question.

## How a push behaves

- **It reads before it writes.** The array sent is the array the deployment ends
  up with, so seekrit reads the current secrets and writes back
  `(existing − removed) ∪ seekrit`. Sending only seekrit's names would delete
  every environment variable on the deployment that seekrit does not manage.
- **Values you set by hand survive.** A `LOG_LEVEL` added in the dashboard, or
  another team's key, is carried through a push exactly as it was read.
- **A run that would change nothing sends nothing.** This is the one that
  matters here: without it, the reconcile timer would build a new revision of
  your agent every few minutes forever.
- **Only `secrets` is ever sent.** A `PATCH` carrying `source_config`,
  `source_revision_config` or `revision_source` would decide which *code* the
  new revision builds. Omitting them lets the control plane fall back to the
  deployment's own source, so a secrets push can never be the thing that ships
  new application code. `secret_references` — Kubernetes Secret references on
  self-hosted installs — is never sent either.
- **A rejected write is blamed on every name in it**, which is honest here: the
  control plane validates the array before applying it, so a rejected `PATCH`
  leaves the deployment exactly as it was and nothing landed.
- **A value the control plane echoes back in an error is scrubbed** before the
  reason is stored on the run row — including a foreign value the array carried
  through.

## Name and value rules

> **Note:** LangGraph Platform will not accept every name seekrit will. It **reserves** the names it sets itself — `LANGSMITH_API_KEY`, `LANGCHAIN_PROJECT`, `POSTGRES_URI`, `REDIS_URI`, `PATH`, `PORT` and around thirty more. seekrit skips a reserved name rather than sending it, and reports it as a failure against that name alone, because the whole run is one request: one unacceptable name would otherwise take the entire environment down with it. Watch for it if you use a `--prefix`. A secret with an **empty value** is failed the same way. The control plane drops one rather than storing it, so a sent empty does not come back on the next read — and seekrit's "nothing changed" check would never agree, which would rebuild your agent on every reconcile tick for as long as the binding existed. Failing the one name is the cheap end of that trade.

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| `401` on every name | The key was revoked — or the connection names the wrong region, which fails identically | Check the region against the table above, then re-create the connection |
| `403` | An organization-scoped key with no workspace ID, or a key without update permission | Add `--langgraph-tenant`, or use a workspace-scoped key that can update the deployment |
| `404` on the deployment | The deployment name in the ID slot, or the deployment was deleted | Use the UUID from the dashboard URL |
| One name failed, rest landed | The mapped name is reserved by LangGraph, or its value is empty | Rename it (or fix the prefix); seekrit refuses to send it rather than fail the run |
| Every name reported failed at once | The single `PATCH` was rejected | Nothing landed — the control plane validates before applying. Fix the cause and re-run |
| The agent redeploys on every sync | A value really is changing every run — an interpolated reference, or a rotation schedule | Check the run ledger for which name; a stable environment sends no request at all |
| A run fails with "refusing to write" | The control plane returned masked secret values, so writing them back would overwrite real ones | Please report it — the connector needs updating, and it fails loudly rather than destroying your environment |

## See also

- [Third-party sync](/docs/guides/third-party-sync) — the shared model, naming and filtering, deletions
- [Agent sandboxes](/docs/guides/sandboxes) — the other half of agent hosting, where you *do* control the process
- [CLI reference](/docs/reference/cli#third-party-sync) — every `seekrit sync` flag
