Exa Enterprise RBAC
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 };
}