# Exa Multi Env Setup

> Configure Exa neural search API across development, staging, and production environments. Use when setting up multi-environment search pipelines, managing API key isolation, or configuring caching and result limits per deployment tier. Trigger with phrases like "exa environments", "exa staging", "exa dev prod", "exa environment setup", "exa api key by env".

- Skill: `micsapp/exa-multi-env-setup` (Agent Skill)
- Install (CLI): `npx skillmds@latest add micsapp/exa-multi-env-setup`
- Raw SKILL.md: https://api.skillmd.com/api/skills/micsapp/exa-multi-env-setup/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- License: MIT
- Author: micsapp (https://skillmd.com/u/micsapp)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/micsapp/exa-multi-env-setup

---

# Exa Multi-Environment Setup

## Overview
Exa's neural search API (`api.exa.ai`) charges per search request. Multi-environment setup focuses on API key isolation, request caching to reduce costs in staging/production, and controlling `numResults` and `text.maxCharacters` per environment (higher values cost more).

## Prerequisites
- Exa API key(s) from dashboard.exa.ai
- `exa-js` npm package (`npm install exa-js`)
- Optional: Redis for search result caching in staging/production

## Environment Strategy

| Environment | Key Isolation | numResults | Cache TTL | Rate Limit |
|-------------|---------------|------------|-----------|------------|
| Development | Shared dev key | 3 (low cost) | None | Low |
| Staging | Staging key | 5 | 5 minutes | Moderate |
| Production | Prod key | 5-10 per query | 1 hour | Full |

## Instructions

### Step 1: Configuration Structure
```typescript
// config/exa.ts
import Exa from "exa-js";

type Env = "development" | "staging" | "production";

interface ExaConfig {
  apiKey: string;
  defaultNumResults: number;
  maxCharacters: number;       // per result content length
  cacheEnabled: boolean;
  cacheTtlSeconds: number;
}

const configs: Record<Env, ExaConfig> = {
  development: {
    apiKey: process.env.EXA_API_KEY!,
    defaultNumResults: 3,       // fewer results = lower cost in dev
    maxCharacters: 500,  # HTTP 500 Internal Server Error
    cacheEnabled: false,        // don't bother caching in dev
    cacheTtlSeconds: 0,
  },
  staging: {
    apiKey: process.env.EXA_API_KEY_STAGING!,
    defaultNumResults: 5,
    maxCharacters: 1000,  # 1000: 1 second in ms
    cacheEnabled: true,
    cacheTtlSeconds: 300,       // 5-minute cache in staging  # 300: timeout: 5 minutes
  },
  production: {
    apiKey: process.env.EXA_API_KEY_PROD!,
    defaultNumResults: 5,
    maxCharacters: 1000,  # 1 second in ms
    cacheEnabled: true,
    cacheTtlSeconds: 3600,      // 1-hour cache for repeated queries  # 3600: timeout: 1 hour
  },
};

export function getExaConfig(): ExaConfig {
  const env = (process.env.NODE_ENV || "development") as Env;
  const config = configs[env] || configs.development;
  if (!config.apiKey) {
    throw new Error(`EXA_API_KEY not set for ${env} environment`);
  }
  return config;
}

export function getExaClient(): Exa {
  return new Exa(getExaConfig().apiKey);
}
```

### Step 2: Search Service with Caching
```typescript
// lib/exa-search.ts
import { getExaClient, getExaConfig } from "../config/exa";
import { Redis } from "ioredis";

const redis = process.env.REDIS_URL ? new Redis(process.env.REDIS_URL) : null;

export async function search(query: string, numResults?: number) {
  const exa = getExaClient();
  const cfg = getExaConfig();
  const n = numResults ?? cfg.defaultNumResults;

  // Check cache if enabled
  if (cfg.cacheEnabled && redis) {
    const cacheKey = `exa:${Buffer.from(`${query}:${n}`).toString("base64")}`;
    const cached = await redis.get(cacheKey);
    if (cached) return JSON.parse(cached);

    const results = await exa.searchAndContents(query, {
      type: "neural",
      numResults: n,
      text: { maxCharacters: cfg.maxCharacters },
    });

    await redis.set(cacheKey, JSON.stringify(results), "EX", cfg.cacheTtlSeconds);
    return results;
  }

  return exa.searchAndContents(query, {
    type: "neural",
    numResults: n,
    text: { maxCharacters: cfg.maxCharacters },
  });
}
```

### Step 3: Environment Variable Setup
```bash
# .env.local (development)
EXA_API_KEY=exa-dev-abc123

# GitHub Actions - Staging
EXA_API_KEY_STAGING=exa-staging-def456

# GitHub Actions - Production
EXA_API_KEY_PROD=exa-prod-xyz789
REDIS_URL=redis://prod-redis:6379  # 6379: Redis port
```

### Step 4: Health Check Per Environment
```typescript
// lib/exa-health.ts
export async function checkExaHealth(): Promise<{ status: string; env: string }> {
  try {
    const exa = getExaClient();
    await exa.search("test connectivity", { numResults: 1 });
    return { status: "healthy", env: process.env.NODE_ENV || "development" };
  } catch (err: any) {
    return { status: "unhealthy", env: process.env.NODE_ENV || "development" };
  }
}
```

### Step 5: CI/CD Configuration
```yaml
# .github/workflows/deploy.yml
jobs:
  deploy-staging:
    environment: staging
    env:
      EXA_API_KEY_STAGING: ${{ secrets.EXA_API_KEY_STAGING }}
      NODE_ENV: staging
    steps:
      - run: npm run build && npm run deploy:staging

  deploy-production:
    environment: production
    env:
      EXA_API_KEY_PROD: ${{ secrets.EXA_API_KEY_PROD }}
      NODE_ENV: production
    steps:
      - run: npm run deploy:prod
```

## Error Handling
| Issue | Cause | Solution |
|-------|-------|----------|
| `401 Unauthorized` | Wrong API key for environment | Verify `EXA_API_KEY` in env vars |
| `429 rate_limit_exceeded` | Too many requests | Implement caching and request queuing |
| High API costs in staging | No caching enabled | Enable Redis cache with 5-minute TTL |
| Empty results | Query too narrow | Broaden query terms for neural search |

## Examples

### Check Active Configuration
```typescript
import { getExaConfig } from "./config/exa";

const cfg = getExaConfig();
console.log(`Results per query: ${cfg.defaultNumResults}`);
console.log(`Cache enabled: ${cfg.cacheEnabled}, TTL: ${cfg.cacheTtlSeconds}s`);
```

## Resources
- [Exa API Documentation](https://docs.exa.ai)
- [Exa JavaScript SDK](https://github.com/exa-labs/exa-js)
- [Exa Pricing](https://exa.ai/pricing)

## Next Steps
For deployment configuration, see `exa-deploy-integration`.

## Output

- Configuration files or code changes applied to the project
- Validation report confirming correct implementation
- Summary of changes made and their rationale
