> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zespan.com/llms.txt
> Use this file to discover all available pages before exploring further.

# zespan link

> Pick the Zespan project a directory belongs to and write it to a committable .zespan/config.json — and list every project your sign-in can reach, without prompting, for CI.

`zespan link` records which Zespan project the current directory belongs to, so
`zespan policy plan` and `apply` do not need a project id on the command line.
`zespan projects list` prints the same data without prompting, which is what CI
needs.

<Note>
  Both commands need a user session — run [`zespan auth login`](/cli/auth) first.
  An API key authenticates a project, not a person, so it cannot answer "which
  projects can *you* reach?".
</Note>

## Quick start

```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
zespan auth login
zespan link
```

```text theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
✔ Linked Acme Health / Intake Prod.
  Project:       Intake Prod  (2845c0ef-1a3d-4f10-9b77-6c1f0a2d8e41)
  Organization:  Acme Health  (acme-health, your role: admin)
  Written to /home/you/checkout/.zespan/config.json — it holds no secret, so commit it.
```

The picker groups projects under the organization that owns them, and every row
carries the org name alongside the project name — because the ambiguity this
command exists to resolve, the same project name in two organizations, is
invisible from the project name alone.

## `.zespan/config.json`

```json .zespan/config.json theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
{
  "project": {
    "id": "2845c0ef-1a3d-4f10-9b77-6c1f0a2d8e41",
    "name": "Intake Prod"
  },
  "org": {
    "slug": "acme-health",
    "name": "Acme Health"
  },
  "environment": "prod"
}
```

<Tip>
  **Commit this file.** It carries no credential and cannot redirect one — there
  is no `apiKey` field and no `apiUrl` field, so a teammate cloning the repo picks
  up the project without picking up a secret, and a hostile edit cannot change
  where your token is sent.
</Tip>

A Zespan project has an id and a name, but no slug — so a bare
`ZESPAN_PROJECT_ID=2845c0ef-…` tells a human nothing about which organization it
belongs to. The link config stores the readable names **beside** the id for
exactly that reason, and error messages print the names rather than the UUID.

Re-running `zespan link` to change project keeps the environment already recorded
in the file unless you pass a new `--env`.

## `zespan link`

```text theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
Usage: zespan link [flags]

Flags:
  --project <id>     Link this project id without prompting (works in CI)
  --env <name>       Record a default environment alongside the project
  --api-url <url>    API base URL (or ZESPAN_API_URL)
  --help             Show this help
```

The picker needs a terminal. Over a pipe or in CI, `zespan link` refuses
**before** it makes any network call and names `--project` as the alternative,
rather than blocking forever on input nobody will type:

```bash theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
zespan link --project 2845c0ef-1a3d-4f10-9b77-6c1f0a2d8e41 --env prod
```

A `--project` id your sign-in cannot reach writes nothing, and says so by id
rather than leaving a config file naming a project that does not exist.

## `zespan projects list`

```text theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
Usage: zespan projects list [flags]

Flags:
  --json             Print machine-readable JSON
  --api-url <url>    API base URL (or ZESPAN_API_URL)
  --help             Show this help
```

Never prompts, so it is safe in CI and over a pipe.

```text theme={"theme":{"light":"github-light","dark":"one-dark-pro"}}
    ORGANIZATION               PROJECT      ID
  * Acme Health (acme-health)  Intake Prod  2845c0ef-1a3d-4f10-9b77-6c1f0a2d8e41
    Acme Health (acme-health)  Sandbox      9f14bb02-7c55-4e88-b0a2-3d5e7f1c4a90
  * linked in this directory
```

`--json` prints the same data as `{ user, projects }`, with `id`, `name`,
`orgSlug`, `orgName` and `role` per project. It goes straight to stdout with no
indentation or colour, so it pipes cleanly into `jq`.

Only projects you can actually reach are listed — every organization you are a
member of, and every project in it that has not been deleted. It is the same list
the dashboard shows you.

## Where the project id comes from

For `zespan policy` and any other command that needs a project, the resolution
order is:

**`--project` flag → `ZESPAN_PROJECT_ID` → `.zespan/config.json` → `.zespan.yaml`**

`zespan link` outranks `.zespan.yaml` because it is the newer, explicit act
("link *this* directory to *that* project"). Nothing changes for existing
projects, which have no `.zespan/config.json` until someone runs `zespan link`.

The environment resolves the same way: `--env` → `ZESPAN_ENVIRONMENT` →
`.zespan/config.json` → `.zespan.yaml`.

<Note>
  The API key and the API base URL are never read from either file — see
  [CLI overview](/cli/overview).
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="zespan auth" icon="log-in" href="/cli/auth">
    Sign in first — both commands need a user session.
  </Card>

  <Card title="zespan policy" icon="file-code" href="/cli/policy">
    Plan and apply guardrail policies against the project you just linked.
  </Card>

  <Card title="CLI overview" icon="terminal" href="/cli/overview">
    Configuration precedence and the two credentials.
  </Card>

  <Card title="Environments" icon="layers" href="/dashboard/environments">
    What `--env` targets, and how environment slugs resolve.
  </Card>
</CardGroup>
