API Authentication & Connection Debugging
This guide provides a systematic approach to debugging API authentication issues when developing pets. Follow this workflow when a pet's API connection fails.
Quick Debugging Checklist
- Test credentials directly with curl (bypass the plugin entirely)
- Check environment variable names match between .env and code
- Verify authentication method (Basic, Bearer, API Key header)
- Check for response compression (use
--compressedwith curl) - Inspect error responses for clues (401, 403, validation errors)
Step-by-Step Debugging Workflow
Step 1: Isolate the Problem - Test with curl
Before debugging plugin code, verify the credentials work at all. Always test the API directly with curl first.
# Load env vars and test - keeps credentials out of terminal history
source .env && curl -s --compressed \
-u "${API_USERNAME}:${API_PASSWORD}" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
"https://api.example.com/endpoint"Why source from .env?
- Keeps credentials out of terminal history
- Tests the exact same values your code will use
- Reveals env var naming mismatches immediately
Step 2: Identify Authentication Type
Different APIs use different auth methods. Test accordingly:
Basic Authentication (username:password)
source .env && curl -s --compressed \
-u "${SERVICE_USERNAME}:${SERVICE_PASSWORD}" \
"https://api.example.com/endpoint"Or with explicit header:
source .env && echo -n "${SERVICE_USERNAME}:${SERVICE_PASSWORD}" | base64
# Then use the output:
curl -s --compressed \
-H "Authorization: Basic <base64_string>" \
"https://api.example.com/endpoint"Bearer Token
source .env && curl -s --compressed \
-H "Authorization: Bearer ${SERVICE_API_KEY}" \
"https://api.example.com/endpoint"API Key in Header
source .env && curl -s --compressed \
-H "X-API-Key: ${SERVICE_API_KEY}" \
"https://api.example.com/endpoint"API Key in Query Parameter
source .env && curl -s --compressed \
"https://api.example.com/endpoint?api_key=${SERVICE_API_KEY}"Step 3: Check Environment Variable Names
A common issue is mismatched environment variable names between:
- What's in your
.envfile - What the plugin code expects
- What the OpenAPI generator configured
Debug command:
# Show variable names only (not values)
grep -o '^[A-Z_]*=' .envCommon naming mismatches:
| .env has | Code expects | Fix |
|---|---|---|
SERVICE_API_USERNAME | SERVICE_API_KEY | Align naming |
SERVICE_TOKEN | SERVICE_API_TOKEN | Check package.json authEnvVar |
API_KEY | SERVICE_API_KEY | Add service prefix |
Fix in package.json:
{
"openapiSpec": {
"authEnvVar": "SERVICE_API_USERNAME",
"authSecretEnvVar": "SERVICE_API_PASSWORD"
}
}Then regenerate: pets generate-openapi
Step 4: Handle Response Compression
Many APIs return compressed responses. If you see garbled output like:
� �VJ�OIU�R��/q,-��/ʬJMq-*�/R�Q*�,�I��%Add --compressed to curl:
curl -s --compressed "https://api.example.com/endpoint"Step 5: Interpret Error Responses
| Status | Meaning | Debug Action |
|---|---|---|
| 401 Unauthorized | Bad credentials or wrong auth method | Verify credentials, check auth type |
| 403 Forbidden | Valid auth but no permission | Check API scopes/permissions |
| 400 Bad Request | Invalid parameters | Read error message, check API docs |
| 404 Not Found | Wrong endpoint or resource | Verify URL path |
| 429 Too Many Requests | Rate limited | Add delays between requests |
Example 401 debugging:
# If this fails with 401:
curl -s -u "user:pass" "https://api.example.com/me"
# Try these:
# 1. Check if credentials are expired/regenerated
# 2. Verify auth method (maybe it's Bearer, not Basic)
# 3. Check if API access is enabled in dashboard
# 4. Look for IP restrictionsStep 6: Verify Plugin Configuration
After curl works, ensure plugin matches:
Check openapi-client.ts auth setup:
// Should match your working curl command
const apiKey = process.env.SERVICE_API_USERNAME // Match .env name
const apiSecret = process.env.SERVICE_API_PASSWORD
// Basic auth encoding
const credentials = `${apiKey}:${apiSecret}`
const encoded = Buffer.from(credentials).toString("base64")
headers["Authorization"] = `Basic ${encoded}`Check index.ts loads correct vars:
const env = loadEnv("service")
const API_USERNAME = env.SERVICE_API_USERNAME // Must match .env
const API_PASSWORD = env.SERVICE_API_PASSWORDCommon Issues & Solutions
Issue: curl works but plugin returns 401
This is the most common issue and is caused by environment variables not being loaded before the openapi-client module initializes.
Root cause: The generated openapi-client.ts reads process.env.MY_API_KEY at module load time. JavaScript modules execute top-level code when first imported. If .env isn't loaded yet, auth headers are built with undefined.
Solution: The OpenAPI generator now uses lazy authentication - regenerate your client:
cd pets/my-api
pets generate-openapiThe new generator creates a getAuthHeaders() function that reads env vars at request time:
// NEW: Lazy auth - reads env vars fresh on each request
function getAuthHeaders(): Record<string, string> {
const headers = { "Content-Type": "application/json" }
const apiKey = process.env.MY_API_KEY // Read at request time!
if (apiKey) {
headers["Authorization"] = `Bearer ${apiKey}`
}
return headers
}
async function fetchAPI(url: string, options: RequestInit) {
const authHeaders = getAuthHeaders() // Fresh headers per request
// ...
}Verification:
# After regenerating, test the plugin
bun test:pet my-api --query "test connection"Issue: "401 Unauthorized" but credentials look correct
Causes:
- Wrong environment variable name in code
- Credentials expired or regenerated
- Wrong authentication method
- API access not enabled in provider dashboard
Debug:
# 1. Verify env var names match
grep -o '^[A-Z_]*=' .env
grep -r "process.env\." index.ts openapi-client.ts
# 2. Test credentials directly
source .env && curl -s -u "${VAR_NAME}:${VAR_PASS}" "https://api.example.com/test"
# 3. Try different auth methods
source .env && curl -s -H "Authorization: Bearer ${VAR_NAME}" "https://api.example.com/test"Issue: Plugin works with curl but not in the OpenPets harness
Causes:
- Plugin not reading .env file
- Environment variables not exported
- Wrong variable prefix for loadEnv()
Debug:
# Check if plugin sees the env vars
bun test:pet service --query "test connection" 2>&1 | grep -i "not configured"
# Verify .env is in the right location (pet directory or project root)
ls -la .env
# Check loadEnv prefix matches variable names
# loadEnv("myservice") expects MYSERVICE_* variablesIssue: "Cannot parse JSON" or garbled response
Cause: Response is compressed (gzip/deflate)
Fix in generated client:
// The fetch API handles this automatically in most cases
// But ensure Accept-Encoding is set if needed
headers["Accept-Encoding"] = "gzip, deflate"Issue: Works locally but fails in CI/production
Causes:
- Environment variables not set in CI
- IP restrictions on API
- Different Node.js/Bun version
Debug:
# Verify env vars are set (without revealing values)
env | grep SERVICE_ | cut -d= -f1Regenerating After Fixes
After fixing configuration issues, always regenerate the OpenAPI client:
cd pets/your-service
pets generate-openapi --verboseThis ensures:
- Auth configuration from package.json is applied
- Environment variable names are consistent
- Headers and auth methods match your settings
Template: Debugging New Pet Connection
When a new pet fails to connect, run through this checklist:
# 1. Navigate to pet directory
cd pets/your-service
# 2. Check what env vars exist
grep -o '^[A-Z_]*=' .env
# 3. Check what code expects
grep "process.env\." openapi-client.ts | head -5
# 4. Test with curl (substitute your auth method)
source .env && curl -s --compressed \
-u "${YOUR_API_USER}:${YOUR_API_PASS}" \
-H "Accept: application/json" \
"https://api.yourservice.com/test-endpoint"
# 5. If curl works but plugin doesn't, check package.json
cat package.json | jq '.openapiSpec'
# 6. Fix authEnvVar/authSecretEnvVar names and regenerate
pets generate-openapi --verbose
# 7. Test again with the OpenPets harness
bun test:pet your-service --query "test connection"Summary
The most common authentication debugging issues are:
- Environment variable name mismatch - The #1 cause of "works in curl, fails in plugin"
- Wrong auth method - Basic vs Bearer vs API Key header
- Compressed responses - Use
--compressedwith curl - Credentials not loaded - Check .env location and loadEnv() prefix
Always start debugging by testing with curl directly using source .env to load credentials. This isolates whether the problem is with the credentials themselves or with the plugin code.