Build

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.

Updated March 2026

CI/CD Pipelines

GitHub Actions

Generic CI pattern

#!/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"
Always use --yes in non-interactive contexts

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

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

# Create a deal, then immediately add it to a pipeline
DEAL_ID=$(lev --yes -q deals create --title "123 Main St Acquisition" --loan-type acquisition)
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

See CLI Reference for the full exit code table.

Data Export

CSV export

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

Combine resources

# 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

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:

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

Pipe IDs between commands

# 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
Prefer --include over per-ID loops

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

Example: agent-driven deal workflow

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

# Agent creates a deal and captures the ID
DEAL_ID=$(lev --yes -q deals create --title "123 Main St Acquisition" --loan-type acquisition --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
Pair with MCP for the best of both

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:

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

Retry with backoff

#!/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

LEV_DEBUG_LOG=/tmp/lev-debug.log lev deals list --verbose
# Request IDs, timing, and headers are written to the log file
Next steps
More in this section