agentsclimarketplace

Perplexity enterprise rbac

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/perplexity-pack/skills/perplexity-enterprise-rbac

425 plugins, 2,810 skills, 200 agents for Claude Code. Open-source marketplace at tonsofskills.com with the ccpi CLI package manager.

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill perplexity-enterprise-rbac

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its author says it does

Copied from the file, not written here

'Configure Perplexity API key scoping, per-team model access, cost controls,

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

7.4 KB, as published. Nobody here has run it

Perplexity Enterprise RBAC

Overview

Control access to Perplexity Sonar API at the organizational level. Perplexity does not have built-in RBAC -- you implement access control through: separate API keys per team/environment, a gateway that enforces model and budget policies, and domain restrictions for compliance.

Access Control Strategy

LayerMechanismPerplexity Support
AuthenticationAPI key per teamYes (multiple keys)
Model restrictionGateway enforcementBuild yourself
Budget capPer-key monthly limitVia dashboard
Domain restrictionsearch_domain_filterYes (per-request)
Rate limitingGateway + key limitsYes (per-key RPM)

Prerequisites

  • Perplexity API account with admin access
  • Separate API keys per team/environment
  • Gateway or middleware for policy enforcement

Instructions

Step 1: Create Per-Team API Keys

Generate separate keys at perplexity.ai/settings/api:

Key: pplx-support-bot-prod     → Budget: $200/mo, sonar only
Key: pplx-research-team        → Budget: $1000/mo, sonar + sonar-pro
Key: pplx-data-team            → Budget: $500/mo, sonar only
Key: pplx-executive-reports    → Budget: $300/mo, sonar-pro

Step 2: Gateway with Policy Enforcement

// perplexity-gateway.ts
import OpenAI from "openai";

interface TeamPolicy {
  apiKey: string;
  allowedModels: string[];
  maxTokensPerRequest: number;
  maxRequestsPerMinute: number;
  requiredDomainFilter?: string[];  // Force search to specific domains
  blockedDomainFilter?: string[];   // Block specific domains
}

const TEAM_POLICIES: Record<string, TeamPolicy> = {
  support: {
    apiKey: process.env.PPLX_KEY_SUPPORT!,
    allowedModels: ["sonar"],
    maxTokensPerRequest: 512,
    maxRequestsPerMinute: 30,
  },
  research: {
    apiKey: process.env.PPLX_KEY_RESEARCH!,
    allowedModels: ["sonar", "sonar-pro", "sonar-reasoning-pro"],
    maxTokensPerRequest: 4096,
    maxRequestsPerMinute: 50,
  },
  compliance: {
    apiKey: process.env.PPLX_KEY_COMPLIANCE!,
    allowedModels: ["sonar", "sonar-pro"],
    maxTokensPerRequest: 2048,
    maxRequestsPerMinute: 20,
    requiredDomainFilter: ["sec.gov", "edgar.sec.gov", "law.cornell.edu"],
  },
  marketing: {
    apiKey: process.env.PPLX_KEY_MARKETING!,
    allowedModels: ["sonar"],
    maxTokensPerRequest: 1024,
    maxRequestsPerMinute: 20,
    blockedDomainFilter: ["-competitor1.com", "-competitor2.com"],
  },
};

function enforcePolicy(
  team: string,
  requestedModel: string,
  requestedTokens: number
): { client: OpenAI; model: string; maxTokens: number; domainFilter?: string[] } {
  const policy = TEAM_POLICIES[team];
  if (!policy) throw new Error(`Unknown team: ${team}`);

  if (!policy.allowedModels.includes(requestedModel)) {
    console.warn(`Team ${team} not allowed ${requestedModel}, using ${policy.allowedModels[0]}`);
  }

  const model = policy.allowedModels.includes(requestedModel)
    ? requestedModel
    : policy.allowedModels[0];

  const maxTokens = Math.min(requestedTokens, policy.maxTokensPerRequest);

  return {
    client: new OpenAI({ apiKey: policy.apiKey, baseURL: "https://api.perplexity.ai" }),
    model,
    maxTokens,
    domainFilter: policy.requiredDomainFilter || policy.blockedDomainFilter,
  };
}

Step 3: Enforced Search with Domain Restrictions

async function teamSearch(
  team: string,
  query: string,
  requestedModel: string = "sonar"
) {
  const { client, model, maxTokens, domainFilter } = enforcePolicy(
    team, requestedModel, 2048
  );

  const response = await client.chat.completions.create({
    model,
    messages: [{ role: "user", content: query }],
    max_tokens: maxTokens,
    ...(domainFilter && { search_domain_filter: domainFilter }),
  } as any);

  return {
    answer: response.choices[0].message.content,
    citations: (response as any).citations || [],
    model: response.model,
    team,
    tokens: response.usage?.total_tokens,
  };
}

// Usage
const result = await teamSearch("compliance", "latest SEC filing for AAPL", "sonar-pro");
// -> Uses sonar-pro (allowed for compliance team)
// -> Searches only sec.gov, edgar.sec.gov, law.cornell.edu

const supportResult = await teamSearch("support", "How to reset password", "sonar-pro");
// -> Downgrades to sonar (support team only allowed sonar)

Step 4: Usage Tracking per Team

class TeamUsageTracker {
  private usage: Map<string, Array<{ timestamp: number; tokens: number; model: string; cost: number }>> = new Map();

  record(team: string, tokens: number, model: string) {
    const entries = this.usage.get(team) || [];
    const cost = model === "sonar-pro" ? tokens * 0.000009 : tokens * 0.000001;
    entries.push({ timestamp: Date.now(), tokens, model, cost });
    this.usage.set(team, entries);
  }

  getDailySummary(team: string) {
    const today = new Date().toDateString();
    const entries = (this.usage.get(team) || []).filter(
      (e) => new Date(e.timestamp).toDateString() === today
    );
    return {
      team,
      queries: entries.length,
      totalTokens: entries.reduce((s, e) => s + e.tokens, 0),
      estimatedCost: entries.reduce((s, e) => s + e.cost, 0).toFixed(4),
      modelBreakdown: {
        sonar: entries.filter((e) => e.model === "sonar").length,
        "sonar-pro": entries.filter((e) => e.model === "sonar-pro").length,
      },
    };
  }
}

Step 5: Key Rotation Schedule

Rotate API keys every 90 days. Name keys with quarter (pplx-research-2026Q1) for tracking.

set -euo pipefail
# 1. Generate new key at perplexity.ai/settings/api
# 2. Deploy new key alongside old key (24-hour overlap)
# 3. Verify new key works
curl -s -o /dev/null -w "%{http_code}" \
  -H "Authorization: Bearer $NEW_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"sonar","messages":[{"role":"user","content":"test"}],"max_tokens":5}' \
  https://api.perplexity.ai/chat/completions
# 4. Remove old key from perplexity.ai/settings/api

Error Handling

IssueCauseSolution
401 for a teamKey expired or revokedRegenerate key for that team
Model downgrade unexpectedPolicy restricting accessCheck team's allowedModels
Compliance citations from wrong domainDomain filter not appliedVerify requiredDomainFilter in policy
Budget exceededTeam over monthly capAlert team lead, increase cap or throttle

Output

  • Per-team API key management
  • Gateway enforcing model and token policies
  • Domain-restricted search for compliance teams
  • Usage tracking and cost allocation per team

Resources

Next Steps

For migration planning, see perplexity-migration-deep-dive.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.