← Files LoopsARCHIVED FILE

skills/loops-cli/references/cli.md

10.3 KB · Sep 30, 2026 · 22:53 UTC

↓ Download file

# Loops CLI Reference

## Contents

- Source URLs
- When To Use The CLI
- Installation
- Auth And Configuration
- Global flags
- Common Workflows
- Practical Guidance

## Source URLs

- https://loops.so/docs/cli
- https://github.com/loops-so/cli
- https://github.com/loops-so/cli/blob/main/README.md

Use `loops agent-context` when you need exact flags or the latest command shape. Requires a recent CLI build (v0.5.0+).

## When To Use The CLI

Prefer the CLI when the user is:

- working from the shell
- doing one-off operational tasks
- validating or rotating credentials
- troubleshooting requests without writing application code
- inspecting output in text or JSON locally

Prefer SDKs or raw HTTP for application code.

## Installation

Prefer the official CLI docs as the source of truth if installation behavior seems to be moving.

### Homebrew

```bash
brew install loops-so/tap/loops
```

### Install script (macOS, Linux, Windows via WSL)

```bash
curl -fsSL https://install.loops.so/cli | sh
```

To install a specific version or to a custom path, append `-s -- <version> <path>` to `sh` in the command above. The default install path is `~/.local/bin`.

If the user is working from source instead of using an installer, defer to the CLI repo README.

### Go install

```bash
go install github.com/loops-so/cli/cmd/loops@latest
```

## Install script (Windows PowerShell)

```pwsh
irm https://raw.githubusercontent.com/loops-so/cli/main/install.ps1 | iex
```

## Auth And Configuration

The CLI needs a Loops API key. You can either:

- set `LOOPS_API_KEY` in the environment, or
- store named keys with `loops auth ...`

Get the API key from `Settings > API` in Loops.

### Keyring storage

```bash
# Save a key interactively, verify it, and make it the default
loops auth login my-team

# Save a key without verifying first and make it the default
loops auth login my-team --skip-verify

# List stored keys
loops auth list

# Set a stored key as the default
loops auth use my-team

# Clear the default stored key
loops auth use --clear

# Show resolved config, active key, masked API key, team, and endpoint
loops auth status

# Remove a stored key
loops auth logout my-team
```

Run `loops auth login <name>` again with a different name to store keys for multiple teams.
Each `loops auth login <name>` call also sets that stored key as the current default.

Use `--team <name>` on any command to select a specific stored key without changing the default.

### Environment variable

Set `LOOPS_API_KEY` directly when keyring storage is unavailable or when running in CI.

### Auth precedence

When multiple credentials are available, the CLI resolves them in this order:

1. `LOOPS_API_KEY`
2. `--team <name>`
3. the current default stored key set with `loops auth use` or the most recent `loops auth login`
4. if no default is set and exactly one stored key exists, the CLI uses that key automatically

`loops auth use --clear` clears the explicit default, but it does not disable that single-stored-key fallback.

### Global flags

These apply across the CLI:

- `-o, --output text|json`
- `-t, --team <name>` to select a stored team key explicitly
- `--debug` to print API request details before sending

## Common Workflows

### Validate credentials

```bash
loops api-key
loops api-key --output json
```

Use this for a quick API-key check against the resolved config.

### Agent discovery

```bash
loops agent-context
loops --version
```

`agent-context` prints JSON describing every command, subcommand, and flag — useful when this reference is behind the installed CLI version.

### Contacts

```bash
# Find
loops contacts find --email user@example.com
loops contacts find --user-id usr_123 --output json

# Create
loops contacts create \
  --email user@example.com \
  --first-name Alex \
  --last-name Chen \
  --user-id usr_123 \
  --subscribed true \
  --user-group premium \
  --list cm_abc123=true \
  --contact-props ./contact-props.json

# Update
loops contacts update \
  --email user@example.com \
  --first-name Alex \
  --list cm_abc123=false \
  --contact-props ./contact-props.json

# Delete
loops contacts delete --email user@example.com
```

Useful contact flags:

- `--email`
- `--user-id`
- `--first-name`
- `--last-name`
- `--subscribed true|false`
- `--user-group`
- `--list id=true|false` (repeatable)
- `--contact-props path/to/file.json`

### Contact properties

```bash
loops contact-properties list
loops contact-properties list --custom
loops contact-properties create --name planTier --type string
```

Use camelCase for property names.

### Mailing lists

```bash
loops lists list
loops lists list --output json
```

### Events

```bash
loops events send signup \
  --email user@example.com \
  --props ./event-props.json \
  --contact-props ./contact-props.json \
  --list list_123=true \
  --idempotency-key signup-user-123
```

Useful event flags:

- `--email` or `--user-id`
- `--props path/to/file.json`
- `--contact-props path/to/file.json`
- `--list id=true|false`
- `--idempotency-key`

If the JSON file passed to `--props` contains a top-level `eventProperties` object, the CLI uses that nested object. Otherwise it treats the full JSON object as the event-properties payload.

### Transactional emails

```bash
# List transactional emails
loops transactional list
loops transactional list --per-page 20
loops transactional list --cursor abc123 --output json
loops transactional list --pick

# Send
loops transactional send cll42l54f20i1la0lfooe3z12 \
  --email user@example.com \
  -v firstName=Alex \
  -v resetLink=https://example.com/reset \
  --idempotency-key reset-user-123

# Send with JSON vars and attachments
loops transactional send cll42l54f20i1la0lfooe3z12 \
  --email user@example.com \
  -j ./vars.json \
  -A ./invoice.pdf \
  -a
```

Useful transactional flags:

- `--email`
- `-a, --add-to-audience`
- `-v, --var KEY=value` (repeatable)
- `-j, --json-vars path/to/file.json`
- `-A, --attachment path/to/file` (repeatable)
- `--idempotency-key`
- `--per-page`
- `--cursor`
- `--pick` (list; requires fzf; cannot combine with `--output json`)

The CLI also exposes a top-level alias:

```bash
loops send cll42l54f20i1la0lfooe3z12 --email user@example.com -v resetLink=https://example.com/reset
```

This is the same transactional send flow as `loops transactional send`.

### Campaigns

Create and edit campaigns.

A sending domain must be configured before creating campaigns.

```bash
# List (auto-paginates unless --cursor is set)
loops campaigns list
loops campaigns list --per-page 20 --output json
loops campaigns list --pick   # interactive fzf picker; copies IDs to clipboard

# Create a draft campaign (prints campaignId, emailMessageId, contentRevisionId)
loops campaigns create --name "Spring product announcement"

# Get or update a draft campaign
loops campaigns get <campaignId>
loops campaigns update <campaignId> --name "Renamed announcement"
loops campaigns update <campaignId> --audience-segment-id seg_123
loops campaigns update <campaignId> --schedule-at 2026-08-01T12:00:00Z
```

Useful campaign flags:

- `--name` / `-n` (required on create; optional on update)
- `--campaign-group-id`, `--mailing-list-id`
- `--audience-segment-id` or `--audience-filter-file`
- `--schedule-now` or `--schedule-at <RFC3339 timestamp>`
- `--per-page`, `--cursor` (list)
- `--pick` (list; requires fzf; cannot combine with `--output json`)

### Email messages

```bash
# Get message fields and syntax-highlighted LMX
loops email-messages get <emailMessageId>
loops email-messages get <emailMessageId> --output json

# Update draft content (requires a revision flag and at least one field)
loops email-messages update <emailMessageId> \
  --expected-revision-id rev_123 \
  --subject "Big spring updates" \
  --preview-text "A quick look at what's new" \
  --from-name "Loops" \
  --from-email hello \
  --reply-to support@example.com \
  --lmx-file ./email.lmx

# Or fetch the latest revision automatically (overwrites concurrent edits)
loops email-messages update <emailMessageId> --force --lmx-file ./email.lmx

# Send a test preview
loops email-messages preview <emailMessageId> \
  --email user@example.com \
  --contact-prop firstName=Alex
```

Useful email-message flags:

- `--expected-revision-id` / `-r` — pass `contentRevisionId` from a prior `get` or `campaigns create` (mutually exclusive with `--force`)
- `--force` / `-f` — fetch the current revision and use it
- `--subject`, `--preview-text`, `--from-name`, `--from-email`, `--reply-to`
- `--cc`, `--bcc`, `--language-code`, `--email-format`
- `--contact-fallback`, `--event-fallback`, `--data-fallback`
- `--lmx` or `--lmx-file` (mutually exclusive)
- `preview`: `--email`, `--contact-prop`, `--event-prop`, `-v`/`--var`, or `-j`/`--json-vars`

`--from-email` accepts a username only; if you pass `hello@example.com`, the CLI keeps `hello`.

After a successful update, text output may include LMX `warnings`. JSON output includes them on the response object.

### Themes

```bash
loops themes list
loops themes list --pick
loops themes get <themeId>
```

`themes get` prints style fields for reference.

### Components

```bash
loops components list
loops components list --pick
loops components get <componentId>
```

`components get` prints the component LMX body.

### Uploads

Upload files that can be used in email content.

```bash
loops uploads create ./header.png
```

Use the returned `finalUrl` in LMX `<Image src="..." />` tags.

By default the CLI sniffs the MIME type from the file contents. Override it with `--content-type` if needed:

```bash
loops uploads create ./header.png --content-type image/png
```

Useful uploads flags:

- `--content-type` — MIME type to use for the upload (default: sniffed from file contents)

## Practical Guidance

- Use `--output json` when the CLI output needs to feed another program.
- Use named keys plus `loops auth use` when switching between Loops teams regularly.
- Use `--team` when you want to override the active stored key for a single command.
- Use `--debug` when you need to inspect request behavior from the terminal.
- For campaign editing, save `contentRevisionId` from `campaigns create` or `email-messages get`, then pass it to `email-messages update --expected-revision-id`. Use `--force` only when overwriting concurrent edits is acceptable.
- Use `--lmx-file` for non-trivial LMX bodies; pair with the `loops-lmx` skill for valid markup.
- If the user needs an exact flag or command not covered here, fall back to `loops agent-context`.

SHA-256: 23fc06af720235d2dcd80f4b64f2556f2f5fe96643f344615d91bd2855842f77