smithery/jeremylongshore

exa-enterprise-rbac

Manage Exa API key scoping, team access controls, and domain restrictions. Use when implementing multi-key access control, configuring per-team search limits, or setting up organization-level Exa governance. Trigger with phrases like "exa access control", "exa RBAC", "exa enterprise", "exa team keys", "exa permissions". '

Installation

$ npx skills add smithery/jeremylongshore --skill exa-enterprise-rbac

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/jeremylongshore · top by installs.

npx skills add smithery/jeremylongshore

Browse all from smithery/jeremylongshore

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Skill metadata

Parsed from SKILL.md frontmatter.

Version1.11.0
LicenseMIT
CompatibilityDesigned for Claude Code
Allowed toolsRead, Write, Edit, Bash(curl:*)
Declared agents claude-code

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 7,319 B
  • docs SUMMARY.md 353 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Exa Enterprise RBAC

Output

  • A least-privilege Exa project/credential role configuration with named owners, review evidence, and a tested revocation path.
  • Verified separation between development, staging, and production access scopes.

Examples

Grant a development service identity only the search/research capability it needs, verify it against a non-sensitive query, and confirm it cannot use production credentials or administrative actions. Record ownership and access-review date; revoke broad roles and correct group mappings before continuing.

Overview

Manage access to Exa search API through API key scoping and application-level controls. Exa is API-key-based (no built-in RBAC), so access control is implemented through multiple API keys per use case, application-layer permission enforcement, domain restrictions per team, and per-key usage monitoring.

Prerequisites

  • Exa API account with team/enterprise plan
  • Dashboard access at dashboard.exa.ai
  • Multiple API keys for key isolation

Instructions

Step 1: Key-Per-Use-Case Architecture

// config/exa-keys.ts
import Exa from "exa-js";

// Create separate clients for each use case
const exaClients = {
  // High-volume RAG pipeline — production key with higher limits
  ragPipeline: new Exa(process.env.EXA_KEY_RAG!),

  // Internal research tool — lower volume key
  researchTool: new Exa(process.env.EXA_KEY_RESEARCH!),

  // Customer-facing search — separate key for isolation
  customerSearch: new Exa(process.env.EXA_KEY_CUSTOMER!),
};

export function getExaForUseCase(
  useCase: keyof typeof exaClients
): Exa {
  const client = exaClients[useCase];
  if (!client) throw new Error(`No Exa client for use case: ${useCase}`);
  return client;
}

Step 2: Application-Level Permission Enforcement

// middleware/exa-permissions.ts
interface ExaPermissions {
  maxResults: number;
  allowedTypes: ("auto" | "neural" | "keyword" | "fast" | "deep")[];
  allowedCategories: string[];
  includeDomains?: string[];     // restrict to these domains
  dailySearchLimit: number;
}

const ROLE_PERMISSIONS: Record<string, ExaPermissions> = {
  "rag-pipeline": {
    maxResults: 10,
    allowedTypes: ["neural", "auto"],
    allowedCategories: [],
    dailySearchLimit: 10000,
  },
  "research-analyst": {
    maxResults: 25,
    allowedTypes: ["neural", "keyword", "auto", "deep"],
    allowedCategories: ["research paper", "news"],
    dailySearchLimit: 500,
  },
  "marketing-team": {
    maxResults: 5,
    allowedTypes: ["keyword", "auto"],
    allowedCategories: ["company", "news"],
    dailySearchLimit: 100,
  },
  "compliance-team": {
    maxResults: 10,
    allowedTypes: ["keyword", "auto"],
    allowedCategories: [],
    includeDomains: ["nist.gov", "owasp.org", "sans.org", "sec.gov"],
    dailySearchLimit: 200,
  },
};

function validateSearchRequest(
  role: string,
  searchType: string,
  numResults: number,
  category?: string
): { allowed: boolean; reason?: string } {
  const perms = ROLE_PERMISSIONS[role];
  if (!perms) return { allowed: false, reason: "Unknown role" };
  if (!perms.allowedTypes.includes(searchType as any)) {
    return { allowed: false, reason: `Search type ${searchType} not allowed for ${role}` };
  }
  if (numResults > perms.maxResults) {
    return { allowed: false, reason: `Max ${perms.maxResults} results for ${role}` };
  }
  if (category && perms.allowedCategories.length > 0 && !perms.allowedCategories.includes(category)) {
    return { allowed: false, reason: `Category ${category} not allowed for ${role}` };
  }
  return { allowed: true };
}

Step 3: Domain Restrictions per Team

// Enforce domain restrictions so compliance-sensitive teams
// only see results from vetted sources
async function enforcedSearch(
  exa: Exa,
  role: string,
  query: string,
  opts: any = {}
) {
  const perms = ROLE_PERMISSIONS[role];
  if (!perms) throw new Error(`Unknown role: ${role}`);

  const validation = validateSearchRequest(
    role,
    opts.type || "auto",
    opts.numResults || 10,
    opts.category
  );
  if (!validation.allowed) throw new Error(validation.reason);

  return exa.searchAndContents(query, {
    ...opts,
    numResults: Math.min(opts.numResults || 10, perms.maxResults),
    type: opts.type || "auto",
    // Merge domain restrictions from role permissions
    includeDomains: perms.includeDomains || opts.includeDomains,
  });
}

Step 4: Per-Key Usage Tracking

// Track usage per API key / role for budget enforcement
class KeyUsageTracker {
  private usage = new Map<string, { count: number; resetAt: number }>();

  checkAndIncrement(role: string): void {
    const perms = ROLE_PERMISSIONS[role];
    if (!perms) throw new Error(`Unknown role: ${role}`);

    const now = Date.now();
    const dayStart = new Date().setHours(0, 0, 0, 0);
    let entry = this.usage.get(role);

    if (!entry || entry.resetAt < now) {
      entry = { count: 0, resetAt: dayStart + 24 * 60 * 60 * 1000 };
    }

    if (entry.count >= perms.dailySearchLimit) {
      throw new Error(
        `Daily search limit (${perms.dailySearchLimit}) exceeded for ${role}`
      );
    }

    entry.count++;
    this.usage.set(role, entry);
  }

  getUsage(role: string) {
    const entry = this.usage.get(role);
    const limit = ROLE_PERMISSIONS[role]?.dailySearchLimit || 0;
    return {
      used: entry?.count || 0,
      limit,
      remaining: limit - (entry?.count || 0),
    };
  }
}

Step 5: Key Rotation Procedure

set -euo pipefail
# 1. Create new key in Exa dashboard (dashboard.exa.ai)
# 2. Deploy new key alongside old key
# 3. Verify new key works
curl -s -o /dev/null -w "%{http_code}" \
  -X POST https://api.exa.ai/search \
  -H "x-api-key: $NEW_EXA_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query":"key rotation test","numResults":1}'

# 4. Switch traffic to new key
# 5. Monitor for errors
# 6. Revoke old key in dashboard after 24h

Error Handling

Issue Cause Solution
401 on search Invalid or revoked API key Regenerate in dashboard
429 rate limited Key-level rate limit exceeded Distribute across keys
Daily limit hit Search budget exhausted Adjust limits or wait for reset
Wrong domain results Missing domain filter Apply includeDomains per role

Resources

Next Steps

For policy enforcement, see exa-policy-guardrails. For multi-env setup, see exa-multi-env-setup.