# Running in CI


> The CLI is non-interactive by design. A GitHub Action, and the one thing to get right.
The CLI runs unattended without a wrapper, which is the point of it in a pipeline.

## Authenticate with an environment variable

Set `API_TOKEN`. There is no login step, no prompt, and nothing stored on the runner.

```bash
export API_TOKEN=$SCREENLY_API_TOKEN
screenly screen list -o json
```

## GitHub Actions

There is a published action:

```yaml
- uses: screenly/cli@master
  with:
    screenly_api_token: ${{ secrets.SCREENLY_API_TOKEN }}
    cli_commands: edge-app deploy
```

`cli_commands` takes any command, so the same step deploys an app, uploads an asset, or reassigns a playlist. `cli_version` pins a version if you would rather not track the latest.

Anything that can run a binary works the same way: GitLab CI, CircleCI, Jenkins.

## The thing to get right

**Scope the token.** A pipeline token carries the permissions of whoever created it, and it lives in a secret store that more people can read than you might assume.

Create it from an account with the access the pipeline actually needs, name it after the pipeline rather than the person, and revoke it when the pipeline is retired. See [API tokens](/docs/developers/overview/api-tokens/).

## Deploying an app from a pipeline

The natural fit: your app is code in a repository, and a push deploys it to every screen running it.

```bash
screenly edge-app validate
screenly edge-app deploy
```

Validate first. It catches a malformed manifest before the deploy reaches any screens.