When building skills that make API calls requiring credentials (API keys, tokens, secrets), follow this protocol to prevent credentials from appearing in chat.
Raw curl commands with environment variables expose credentials:
```bash
# ❌ BAD - API key visible in chat
curl -H "Authorization: Bearer $API_KEY" https://api.example.com/data
```
When Claude executes this, the full command with expanded `$API_KEY` appears in the conversation.
Use `~/.claude/scripts/secure-api.sh` - a wrapper that loads credentials internally.
```bash
# ✅ GOOD - No credentials visible
~/.claude/scripts/secure-api.sh [args]
# Examples:
~/.claude/scripts/secure-api.sh facebook list-campaigns
~/.claude/scripts/secure-api.sh ghl search-contact "email@example.com"
```
When building a new skill that requires API calls:
1. **Add operations to the wrapper** (`~/.claude/scripts/secure-api.sh`):
```bash
case "$SERVICE" in
yourservice)
case "$OPERATION" in
list-items)
curl -s -G \
-H "Authorization: Bearer $YOUR_API_KEY" \
"https://api.yourservice.com/items"
;;
get-item)
ITEM_ID=$1
curl -s -G \
-H "Authorization: Bearer $YOUR_API_KEY" \
"https://api.yourservice.com/items/$ITEM_ID"
;;
*)
echo "Unknown operation: $OPERATION" >&2
exit 1
;;
esac
;;
esac
```
2. **Add profile support to the wrapper** (if service needs multiple accounts):
```bash
# In secure-api.sh, add to profile remapping section:
yourservice)
SERVICE_UPPER="YOURSERVICE"
YOURSERVICE_API_KEY=$(eval echo \$${SERVICE_UPPER}_${PROFILE_UPPER}_API_KEY)
YOURSERVICE_ACCOUNT_ID=$(eval echo \$${SERVICE_UPPER}_${PROFILE_UPPER}_ACCOUNT_ID)
;;
```
3. **Add credential placeholders to `~/.claude/.env`** using profile naming:
```bash
# Check if entries already exist
grep -q "YOURSERVICE_MAIN_API_KEY=" ~/.claude/.env 2>/dev/null || \
echo -e "\n# Your Service - Main profile\nYOURSERVICE_MAIN_API_KEY=\nYOURSERVICE_MAIN_ACCOUNT_ID=" >> ~/.claude/.env
echo "Added credential placeholders to ~/.claude/.env - user needs to fill them in"
```
4. **Document profile workflow in your SKILL.md**:
```markdown
## Profile Selection Workflow
**CRITICAL:** Always use profile selection to prevent using wrong account credentials.
### When user requests YourService operation:
1. **Check for saved profile:**
```bash
~/.claude/scripts/profile-state get yourservice
```
2. **If no profile saved, discover available profiles:**
```bash
~/.claude/scripts/list-profiles yourservice
```
3. **If only ONE profile:** Use it automatically and announce:
```
"Using YourService profile 'main' to list items..."
```
4. **If MULTIPLE profiles:** Ask user which one:
```
"Which YourService profile: main, clienta, or clientb?"
```
5. **Save user's selection:**
```bash
~/.claude/scripts/profile-state set yourservice
```
6. **Always announce which profile before calling API:**
```
"Using YourService profile 'main' to list items..."
```
7. **Make API call with profile:**
```bash
~/.claude/scripts/secure-api.sh yourservice: list-items
```
## Secure API Calls
All API calls use profile syntax:
```bash
~/.claude/scripts/secure-api.sh yourservice: [args]
# Examples:
~/.claude/scripts/secure-api.sh yourservice:main list-items
~/.claude/scripts/secure-api.sh yourservice:main get-item
```
**Profile persists for session:** Once selected, use same profile for subsequent operations unless user explicitly changes it.
```
```bash
curl -s -G \
-H "Authorization: Bearer $API_KEY" \
"https://api.example.com/endpoint"
```
```bash
ITEM_ID=$1
curl -s -X POST \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d @- \
"https://api.example.com/items/$ITEM_ID"
```
Usage:
```bash
echo '{"name":"value"}' | ~/.claude/scripts/secure-api.sh service create-item
```
```bash
curl -s -X POST \
-F "field1=value1" \
-F "field2=value2" \
-F "access_token=$API_TOKEN" \
"https://api.example.com/endpoint"
```
**Location:** `~/.claude/.env` (global for all skills, accessible from any directory)
**Format:**
```bash
# Service credentials
SERVICE_API_KEY=your-key-here
SERVICE_ACCOUNT_ID=account-id-here
# Another service
OTHER_API_TOKEN=token-here
OTHER_BASE_URL=https://api.other.com
```
**Loading in script:**
```bash
set -a
source ~/.claude/.env 2>/dev/null || { echo "Error: ~/.claude/.env not found" >&2; exit 1; }
set +a
```
1. **Never use raw curl with `$VARIABLE` in skill examples** - always use the wrapper
2. **Add all operations to the wrapper** - don't make users figure out curl syntax
3. **Auto-create credential placeholders** - add empty fields to `~/.claude/.env` immediately when creating the skill
4. **Keep credentials in `~/.claude/.env`** - one central location, works everywhere
5. **Document each operation** - show examples in SKILL.md
6. **Handle errors gracefully** - check for missing env vars, show helpful error messages
Test the wrapper without exposing credentials:
```bash
# This command appears in chat
~/.claude/scripts/secure-api.sh facebook list-campaigns
# But API keys never appear - they're loaded inside the script
```
Verify credentials are loaded:
```bash
# Check .env exists
ls -la ~/.claude/.env
# Check specific variables (without showing values)
grep -q "YOUR_API_KEY=" ~/.claude/.env && echo "API key configured" || echo "API key missing"
```