# CLI Workflows

> Production patterns for scripting with the Lev CLI — CI/CD integration, bulk exports, error handling, and composing commands with jq and shell pipelines.

Source: https://www.lev.com/docs/build/cli-workflows

Last updated: March 2026

---
## CI/CD Pipelines

### GitHub Actions

**GitHub Actions examples**

### Fetch deals

```yaml
- name: Install Lev CLI
  run: pipx install lev-cli

- name: Export deals snapshot
  env:
    LEV_API_KEY: ${{ secrets.LEV_API_KEY }}
  run: lev -o json deals list --all > deals.json

- name: Upload artifact
  uses: actions/upload-artifact@v4
  with:
    name: deals-snapshot
    path: deals.json
```

### Create deal on deploy

```yaml
- name: Create tracking deal
  env:
    LEV_API_KEY: ${{ secrets.LEV_API_KEY }}
  run: |
    DEAL_ID=$(lev --yes -q deals create --title "Deploy ${{ github.sha }}")
    echo "deal_id=$DEAL_ID" >> $GITHUB_OUTPUT
```

### Generic CI pattern

```bash
#!/usr/bin/env bash
set -euo pipefail

# Authenticate via environment
export LEV_API_KEY="${LEV_API_KEY:?LEV_API_KEY must be set}"

# Fetch and process
lev -o json deals list --all > /tmp/deals.json
echo "Fetched $(jq '.data | length' /tmp/deals.json) deals"
```

  Write operations fail with exit code 2 when output is piped and `--yes` is not provided. This is intentional — it prevents accidental mutations in automated pipelines.

## Scripting Patterns

### Create and capture the ID

```bash
DEAL_ID=$(lev --yes -q deals create --title "New Deal")
echo "Created deal $DEAL_ID"
```

The `-q` flag emits only the resource ID, making it easy to capture in a variable.

### Chain commands

```bash
# Create a deal, then immediately add it to a pipeline
DEAL_ID=$(lev --yes -q deals create --title "123 Main St Acquisition" --loan-type heavy_bridge)
lev --yes pipelines move --deal-id "$DEAL_ID" --pipeline-id 10 --status-id 1
echo "Deal $DEAL_ID created and added to pipeline"
```

### Conditional logic on exit codes

**Exit code branching**

### Check if deal exists

```bash
if lev deals get 42 > /dev/null 2>&1; then
  echo "Deal 42 exists"
else
  echo "Deal 42 not found (exit code $?)"
fi
```

### Handle rate limits

```bash
lev deals list --all > deals.json 2>&1
EXIT_CODE=$?

case $EXIT_CODE in
  0) echo "Success" ;;
  7) echo "Rate limited — retrying in 60s"; sleep 60; lev deals list --all > deals.json ;;
  3) echo "Auth failed — check LEV_API_KEY"; exit 1 ;;
  *) echo "Unexpected error (exit $EXIT_CODE)"; exit 1 ;;
esac
```

See **[CLI Reference](/build/cli-reference#exit-codes)** for the full exit code table.

## Data Export

### CSV export

```bash
lev -o csv deals list --all > deals.csv
lev -o csv contacts list --type lender > lender-contacts.csv
lev -o csv placements list --deal-id 2303 > placements.csv
```

### JSON with jq filtering

**jq filtering examples**

### Extract fields

```bash
lev deals list | jq '.data[] | {id, title, loan_amount}'
```

### Filter by amount

```bash
lev deals list | jq '.data[] | select(.loan_amount > 10000000) | .title'
```

### Count by type

```bash
lev deals list --all | jq '.data | group_by(.loan_type) | map({type: .[0].loan_type, count: length})'
```

### Combine resources

```bash
# Get a deal with all sub-resources in one call
lev deals get 2303 --include financials,properties,team -o json > deal-full.json

# Or combine separately
lev -o json deals get 2303 > deal.json
lev -o json placements list --deal-id 2303 > placements.json
lev -o json term-sheets list 2303 > term-sheets.json
```

## Bulk Operations

### Fetch all records

```bash
lev deals list --all
```

This streams all pages with a progress bar. A safety limit of 10,000 records applies by default — override with `--max-records`:

```bash
lev contacts list --all --max-records 50000
```

### Pipe IDs between commands

```bash
# Get IDs of all permanent loan deals, then fetch full details for each
lev -q deals list --loan-type permanent | while read -r DEAL_ID; do
  lev deals get "$DEAL_ID" --include financials -o json >> permanent-deals.jsonl
done
```

  When possible, use `--include` on the list command to embed sub-resources in a single paginated request. Looping per-ID is slower and consumes more rate limit budget.

## LLM Agent Integration

The CLI was built with LLM agents in mind. When an agent like Claude Code, Cursor, or Windsurf runs a CLI command, the output is automatically piped — which means the CLI emits structured JSON without any extra flags. This makes the CLI a natural tool-use surface for agents that can execute shell commands.

### Why the CLI works well for agents

**Agent-friendly design choices**

- **Auto-JSON on pipe:** Agents capture stdout, which triggers JSON mode automatically. No need to remember -o json.
- **Structured errors:** Errors include status codes, types, and request IDs in JSON — agents can parse and recover programmatically.
- **Deterministic exit codes:** Agents can branch on exit codes (0 = success, 5 = not found, 7 = rate limited) without parsing error text.
- **Quiet mode for IDs:** The -q flag returns just the resource ID — ideal for agents chaining create → update → move workflows.
- **Dry-run for safety:** Agents can preview mutations with --dry-run before committing, giving the human a chance to review.

### Example: agent-driven deal workflow

An LLM agent with shell access can chain CLI commands to accomplish multi-step workflows:

```bash
# Agent creates a deal and captures the ID
DEAL_ID=$(lev --yes -q deals create --title "123 Main St Acquisition" --loan-type heavy_bridge --loan-amount 5000000)

# Agent fetches lender matches
lev lenders search --state NY --limit 5

# Agent adds the deal to a pipeline
lev --yes pipelines move --deal-id "$DEAL_ID" --pipeline-id 10 --status-id 1

# Agent retrieves the full deal context for summarization
lev deals get "$DEAL_ID" --include financials,properties,team
```

  Use MCP when the agent needs conversational tool discovery and OAuth-scoped access. Use the CLI when the agent has shell access and you want deterministic, scriptable operations. They complement each other — MCP for interactive copilots, CLI for automated agent workflows.

## Error Handling in Scripts

### Structured error output

When the CLI encounters an error in JSON mode, it emits a structured error object:

```json
{
  "error": {
    "status": 404,
    "type": "not_found",
    "message": "Deal not found"
  },
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
```

### Retry with backoff

```bash
#!/usr/bin/env bash
set -euo pipefail

MAX_RETRIES=3
RETRY_DELAY=5

for attempt in $(seq 1 $MAX_RETRIES); do
  if lev -o json deals list --all > deals.json 2>&1; then
    echo "Success on attempt $attempt"
    break
  fi
  EXIT_CODE=$?
  if [ "$EXIT_CODE" -eq 7 ] && [ "$attempt" -lt "$MAX_RETRIES" ]; then
    echo "Rate limited — retrying in ${RETRY_DELAY}s (attempt $attempt/$MAX_RETRIES)"
    sleep $RETRY_DELAY
    RETRY_DELAY=$((RETRY_DELAY * 2))
  else
    echo "Failed with exit code $EXIT_CODE"
    exit $EXIT_CODE
  fi
done
```

### Debug logging

```bash
LEV_DEBUG_LOG=/tmp/lev-debug.log lev deals list --verbose
# Request IDs, timing, and headers are written to the log file
```