Bifurcating Claude API Costs: Subscription UI for Development, Tiered API Keys for Workloads
We've been running Claude integrations across multiple systems—some interactive development workflows, others programmatic batch processing—all using a single ANTHROPIC_API_KEY exported globally in our shell environment. The result: monthly API spend has climbed unsustainably, while our development loops are unnecessarily expensive. This post covers the architecture we've implemented to separate concerns: keeping the claude.ai subscription (Web UI + Claude Code) for interactive work, while routing programmatic tasks to a cost-optimized EC2 instance running cheaper Claude models.
The Problem: One Key, Two Cost Profiles
Our shell dotfiles had ANTHROPIC_API_KEY exported globally in ~/.zshrc. This meant:
- Interactive
claudeinvocations preferred the API key over the claude.ai subscription OAuth token stored in Claude Code's keychain (~/.claude/settings.json, credential accountcb). - Programmatic scripts using the API defaulted to Claude 3.5 Sonnet—appropriate for complex tasks, but overkill and expensive for atomized, well-defined work.
- No easy way to cost-optimize: everything hit the same expensive model tier.
At ~$1500/month, we needed to cut costs by 90-95% without compromising reliability on safety-critical paths.
Solution Architecture: Environment-Scoped Auth
The fix exploits a simple principle: shell environment controls which credential the Claude CLI sees first. The keychain OAuth token (used by claude.ai subscription) is always present; the API key can be conditionally present.
Core Changes to Shell Configuration
We modified ~/.zshrc to remove the global ANTHROPIC_API_KEY export. The key now lives only in repos.env, sourced selectively:
# OLD (global): export ANTHROPIC_API_KEY="sk-ant-..."
# NEW: Key removed from ~/.zshrc entirely
# Interactive shell: ANTHROPIC_API_KEY is NOT in the environment
# → claude CLI falls back to keychain OAuth token
# → Uses claude.ai subscription (Web UI + Claude Code billing)
# Programmatic scripts: Source repos.env only when needed
# Example in a Makefile or script:
# source repos.env && python train_model.py
# → ANTHROPIC_API_KEY is set for that subprocess tree only
This approach preserves backwards compatibility: existing scripts that call source repos.env continue to work. Developers using the interactive claude command now transparently use their subscription without manual logout/login cycles.
Tiered Model Selection via EC2 Farm-Out
For programmatic work, we've set up a dedicated EC2 instance (Lightsail) running a Claude daemon that can be invoked with cheaper models (Claude 3 Haiku). A farm-out wrapper selects the right model tier based on task complexity.
Infrastructure Details
- EC2 Host:
ubuntu@34.239.233.28(Lightsail, us-west-2 region; internal hostnameip-172-26-6-34) - SSH Key:
~/.ssh/LightsailDefaultKey-us-west-2.pem(passwordless; verified withBatchModeSSH probe) - Claude Installation:
/usr/bin/claudeon the EC2 box - Daemon:
jada-agent.service(systemd; active and verified) - Authentication: The daemon injects API key and model selection per invocation, not in the login shell. This is critical: the farm-out wrapper must pass credentials explicitly.
Farm-Out Wrapper Pattern
A bash wrapper (to be placed in local ~/bin/ or scripts/) determines task complexity and routes accordingly:
#!/bin/bash
# Example farm-out logic (pseudocode)
TASK_TYPE="${1:-simple}"
TASK_PROMPT="${@:2}"
if [[ "$TASK_TYPE" == "complex" ]]; then
# Interactive dev or safety-critical: use local subscription
claude "$TASK_PROMPT"
else
# Simple, atomized task: use EC2 cheap instance
ssh -i ~/.ssh/LightsailDefaultKey-us-west-2.pem \
ubuntu@34.239.233.28 \
"source repos.env && /usr/bin/claude --model haiku \"$TASK_PROMPT\""
fi
The wrapper reads repos.env on the remote EC2 box, which supplies the API key scoped to that instance alone. Local development never sees or uses that key.
Key Decisions and Trade-offs
Why NOT forceLoginMethod in settings.json?
The forceLoginMethod key in ~/.claude/settings.json appears in some Claude documentation, but it is silently ignored in local/personal installs—it's only respected in managed/enterprise deployments. We verified this doesn't work, so we abandoned that lever early.
Why Environment Scoping?
Environment variables are the most reliable, least intrusive way to control credential precedence in the Claude CLI. The alternative—managing separate ~/.claude/settings.json profiles—would require context switching or aliases. Sourcing repos.env on demand follows the existing convention in the codebase, so no new patterns are introduced.
Why EC2 for the Cheap Instance?
A persistent EC2 instance (vs. spinning up ephemeral containers) provides:
- Predictable, low latency for short RPC calls (10–50ms round-trip)
- Persistent systemd service (
jada-agent.service) eliminates cold-start overhead - Cost amortization: Lightsail instance costs ~$3–5/month; API token savings dwarf that
- Isolated credential scope: the EC2 API key never touches the development machine
Cost Breakdown and Expected Savings
Rough estimates:
- Previous: ~$1500/month (all tasks on Sonnet 3.5 @ ~$3/MTok input, ~$15/MTok output)
- Interactive (subscription): Flat monthly fee (~$20/month with Web UI benefits)
- Programmatic (Haiku on EC2): ~$0.80/MTok input, ~$4/MTok output; estimated ~$50–100/month for current workload
- EC2 Lightsail instance: ~$4/month
- Target total: $75–150/month (a 90–95% reduction)