Claude-code-plugins-plus-skills sentry-rate-limits

install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/sentry-pack/skills/sentry-rate-limits" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-sentry-rate-limits && rm -rf "$T"
manifest: plugins/saas-packs/sentry-pack/skills/sentry-rate-limits/SKILL.md
source content

Sentry Rate Limits & Quota Optimization

Overview

Manage Sentry rate limits, sampling strategies, and quota usage to control costs without losing visibility into critical errors. Covers client-side sampling,

beforeSend
filtering, server-side inbound filters, per-key rate limits, spike protection, and the usage stats API.

Prerequisites

  • Sentry account with a project DSN configured
  • SENTRY_AUTH_TOKEN
    with
    org:read
    and
    project:write
    scopes (Settings > Auth Tokens)
  • SENTRY_ORG
    and
    SENTRY_PROJECT
    slugs known
  • SDK installed:
    @sentry/node
    (npm) or
    sentry-sdk
    (pip)
  • Current event volume visible at
    sentry.io/stats/

Instructions

Step 1 — Understand Rate Limit Behavior

When your project exceeds its quota, Sentry returns

429 Too Many Requests
with a
Retry-After
header. The SDK automatically stops sending events until the cooldown expires. Events generated during this window are permanently lost — there is no replay mechanism.

Rate limit tiers by plan:

PlanAPI Rate LimitNotes
Developer50 RPMShared quota, no reserved volume
Team1,000 RPMPer-organization, includes spike protection
Business10,000 RPMPer-organization, custom quotas available
EnterpriseCustomNegotiated per contract

Quota categories (billed separately):

  • Errors — exceptions and log messages
  • Transactions — performance monitoring spans
  • Replays — session replay recordings
  • Attachments — file uploads (crash dumps, minidumps)
  • Profiles — continuous profiling data
  • Cron monitors — scheduled job check-ins

Rate limit headers returned on 429:

HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-Sentry-Rate-Limit-Limit: 50
X-Sentry-Rate-Limit-Remaining: 0
X-Sentry-Rate-Limit-Reset: 1711324800

Step 2 — Configure Client-Side Sampling

Sampling is the first line of defense. Set

sampleRate
for errors and
tracesSampleRate
for performance transactions.

TypeScript / Node.js:

import * as Sentry from '@sentry/node';

Sentry.init({
  dsn: process.env.SENTRY_DSN,

  // Error sampling: 0.0 (drop all) to 1.0 (capture all)
  sampleRate: 0.25, // Capture 25% of errors

  // Transaction sampling: 0.0 to 1.0
  tracesSampleRate: 0.1, // Capture 10% of transactions

  // Dynamic transaction sampling — route-aware cost control
  tracesSampler: (samplingContext) => {
    const { name, parentSampled } = samplingContext;

    // Respect parent sampling decision in distributed traces
    if (parentSampled !== undefined) return parentSampled;

    // Drop health checks and readiness probes entirely
    if (name === 'GET /health' || name === 'GET /readiness') return 0;
    if (name?.includes('/health')) return 0;

    // High-value: payment and auth flows at 100%
    if (name?.includes('/api/payment') || name?.includes('/api/auth')) return 1.0;

    // Medium-value: API routes at 20%
    if (name?.startsWith('GET /api/') || name?.startsWith('POST /api/')) return 0.2;

    // Low-value: static assets — never trace
    if (name?.startsWith('GET /static/') || name?.startsWith('GET /assets/')) return 0;

    // Default fallback: 5%
    return 0.05;
  },
});

Python:

import sentry_sdk

def traces_sampler(sampling_context):
    tx_name = sampling_context.get("transaction_context", {}).get("name", "")

    # Drop health checks
    if "/health" in tx_name or "/readiness" in tx_name:
        return 0

    # High-value flows
    if "/api/payment" in tx_name or "/api/auth" in tx_name:
        return 1.0

    # API routes
    if tx_name.startswith(("GET /api/", "POST /api/")):
        return 0.2

    # Static assets
    if tx_name.startswith(("GET /static/", "GET /assets/")):
        return 0

    return 0.05

sentry_sdk.init(
    dsn=os.environ["SENTRY_DSN"],
    sample_rate=0.25,          # 25% of errors
    traces_sample_rate=0.1,    # 10% of transactions (fallback if no sampler)
    traces_sampler=traces_sampler,
)

Step 3 — Filter Noisy Errors with beforeSend

Use

beforeSend
to drop events before they count against your quota. This runs client-side, so filtered events never reach Sentry.

TypeScript / Node.js:

Sentry.init({
  dsn: process.env.SENTRY_DSN,

  beforeSend(event, hint) {
    const error = hint?.originalException as Error | undefined;

    // Drop browser extension errors (common in frontend SDKs)
    if (event.exception?.values?.some(e =>
      e.stacktrace?.frames?.some(f =>
        f.filename?.includes('extensions://') ||
        f.filename?.includes('moz-extension://') ||
        f.filename?.includes('chrome-extension://')
      )
    )) {
      return null; // Drop the event
    }

    // Drop known noisy browser errors
    if (error?.message?.match(/ResizeObserver loop/)) return null;
    if (error?.message?.match(/Non-Error promise rejection/)) return null;
    if (error?.name === 'AbortError') return null;
    if (error?.message?.match(/Load failed/)) return null;

    // CRITICAL: Always capture payment errors regardless of sampleRate
    if (error?.message?.includes('PaymentError') ||
        event.tags?.['transaction.type'] === 'payment') {
      return event; // Force capture
    }

    return event;
  },

  // Pattern-based error filtering (faster than beforeSend for known strings)
  ignoreErrors: [
    'ResizeObserver loop completed with undelivered notifications',
    'Non-Error promise rejection captured',
    /Loading chunk \d+ failed/,
    'Network request failed',
    'Failed to fetch',
    'AbortError',
    /^Script error\.?$/,
    'TypeError: cancelled',
    'TypeError: NetworkError when attempting to fetch resource',
  ],

  // Block errors originating from third-party scripts
  denyUrls: [
    /extensions\//i,
    /^chrome:\/\//i,
    /^chrome-extension:\/\//i,
    /^moz-extension:\/\//i,
    /hotjar\.com/,
    /google-analytics\.com/,
    /googletagmanager\.com/,
    /intercom\.io/,
  ],
});

Python:

def before_send(event, hint):
    if "exc_info" in hint:
        exc_type, exc_value, _ = hint["exc_info"]

        # Drop known noisy exceptions
        if exc_type.__name__ in ("ConnectionResetError", "BrokenPipeError"):
            return None

        # Drop health check 404s
        msg = str(exc_value)
        if "health" in msg.lower() and "404" in msg:
            return None

    # Always capture payment errors
    if event.get("tags", {}).get("transaction.type") == "payment":
        return event

    return event

sentry_sdk.init(
    dsn=os.environ["SENTRY_DSN"],
    before_send=before_send,
    ignore_errors=[
        "ConnectionResetError",
        "BrokenPipeError",
    ],
)

Step 4 — Enable Server-Side Inbound Data Filters

Inbound filters run on Sentry's servers before quota counting. Filtered events do not consume quota — this is free filtering.

Configure at Project Settings > Inbound Filters:

FilterWhat it blocksRecommended
Legacy browsersIE 9/10, old Safari, old AndroidEnable
Browser extensionsErrors from browser extension codeEnable
Localhost eventsEvents from localhost / 127.0.0.1Enable for production projects
Web crawlersBot-generated errors (Googlebot, etc.)Enable
Filtered releasesSpecific release versionsUse for deprecated releases
Error message patternsCustom regex patternsAdd known false-positive patterns

Configure via API:

# Enable legacy browser filter
curl -X PUT \
  -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"active": true}' \
  "https://sentry.io/api/0/projects/$SENTRY_ORG/$SENTRY_PROJECT/filters/legacy-browsers/"

# Enable browser extension filter
curl -X PUT \
  -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"active": true}' \
  "https://sentry.io/api/0/projects/$SENTRY_ORG/$SENTRY_PROJECT/filters/browser-extensions/"

# Enable web crawler filter
curl -X PUT \
  -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"active": true}' \
  "https://sentry.io/api/0/projects/$SENTRY_ORG/$SENTRY_PROJECT/filters/web-crawlers/"

Step 5 — Set Per-Key Rate Limits

Each DSN (Client Key) can have its own rate limit. This prevents a single project from exhausting the organization's entire quota.

Configure at Project Settings > Client Keys > Configure > Rate Limiting.

# Set rate limit to 1000 events per hour on a specific client key
# First, list client keys to find the key ID
curl -s -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
  "https://sentry.io/api/0/projects/$SENTRY_ORG/$SENTRY_PROJECT/keys/" \
  | python3 -m json.tool

# Then set the rate limit
curl -X PUT \
  -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rateLimit": {"window": 3600, "count": 1000}}' \
  "https://sentry.io/api/0/projects/$SENTRY_ORG/$SENTRY_PROJECT/keys/$KEY_ID/"

Strategy for multi-environment setups:

  • Production DSN: 5,000 events/hour (critical errors matter)
  • Staging DSN: 500 events/hour (only need representative sample)
  • Development DSN: 100 events/hour (prevent local debugging floods)

Step 6 — Enable Spike Protection

Spike protection is auto-enabled on Team and Business plans. It detects sudden event volume increases and temporarily rate-limits the project to prevent quota exhaustion from error storms.

Configure at Organization Settings > Spike Protection.

When spike protection triggers:

  1. Sentry detects volume exceeding 10x normal baseline
  2. Events are temporarily dropped (429 returned to SDK)
  3. An email notification is sent to organization owners
  4. Protection auto-disables after the spike subsides

For programmatic spike alerts, set up a Sentry alert rule:

  • Condition: Number of events in project exceeds threshold
  • Action: Send notification to Slack/PagerDuty/email
  • Frequency: Alert once per hour

Step 7 — Monitor Quota Usage via API

# Organization-wide usage stats for the last 7 days, grouped by category
curl -s -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
  "https://sentry.io/api/0/organizations/$SENTRY_ORG/stats_v2/?field=sum(quantity)&groupBy=category&interval=1d&statsPeriod=7d" \
  | python3 -m json.tool

# Per-project usage breakdown
curl -s -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
  "https://sentry.io/api/0/organizations/$SENTRY_ORG/stats_v2/?field=sum(quantity)&groupBy=project&category=error&interval=1d&statsPeriod=7d" \
  | python3 -m json.tool

# Outcome-based stats (accepted, filtered, rate_limited, invalid)
curl -s -H "Authorization: Bearer $SENTRY_AUTH_TOKEN" \
  "https://sentry.io/api/0/organizations/$SENTRY_ORG/stats_v2/?field=sum(quantity)&groupBy=outcome&category=error&interval=1d&statsPeriod=24h" \
  | python3 -m json.tool

Step 8 — Reduce Payload Size and Deduplicate

Reduce event size and improve grouping to lower quota consumption:

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  maxBreadcrumbs: 30,   // Default: 100
  maxValueLength: 500,   // Default: 250

  beforeSend(event) {
    // Truncate large request bodies
    if (event.request?.data && typeof event.request.data === 'string') {
      event.request.data = event.request.data.substring(0, 1000);
    }
    // Remove cookies
    if (event.request?.cookies) delete event.request.cookies;

    // Custom fingerprinting: normalize dynamic values for better grouping
    if (event.exception?.values?.[0]) {
      const { type, value } = event.exception.values[0];
      const normalized = value
        ?.replace(/\b[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}\b/gi, '<UUID>')
        ?.replace(/\b\d+\b/g, '<N>') || '';
      event.fingerprint = [type || 'unknown', normalized];
    }
    return event;
  },
});

Output

After completing these steps, you will have:

  • Sampling rates configured (
    sampleRate
    ,
    tracesSampleRate
    ,
    tracesSampler
    ) to reduce event volume while preserving visibility into critical paths
  • beforeSend
    filtering dropping noisy browser errors (ResizeObserver, AbortError, extension errors) before they count against quota
  • ignoreErrors
    and
    denyUrls
    patterns blocking known false positives
  • Server-side inbound filters enabled for free pre-quota filtering
  • Per-key rate limits set to prevent single-project quota exhaustion
  • Spike protection enabled and alert rules configured
  • Quota monitoring via the
    /stats_v2/
    API endpoint
  • Custom fingerprinting reducing event duplication

Error Handling

ErrorCauseSolution
429 Too Many Requests
Quota exhausted for current billing periodLower
sampleRate
and
tracesSampleRate
, add patterns to
ignoreErrors
, enable per-key rate limits
Events silently droppedClient SDK respecting
Retry-After
header
Reduce volume at source; SDK auto-recovers after cooldown expires
Critical errors missed
sampleRate
too low
Use
beforeSend
to force-return critical errors regardless of sampling; never filter payment/auth errors
Quota exhausted early in periodNo per-project rate limitsSet hourly rate limits per client key to spread quota evenly across the billing period
Spike consuming entire quotaSpike protection not enabled or threshold too highEnable spike protection in organization settings; set up volume alert rules
401 Unauthorized
on stats API
Invalid or expired auth tokenRegenerate token at Settings > Auth Tokens with
org:read
scope
Inbound filter not reducing volumeFilter configured but wrong error typeVerify filter targets correct category; browser extension filter only works for JS SDK errors
tracesSampler
not called
tracesSampleRate
takes precedence in some SDK versions
Remove
tracesSampleRate
when using
tracesSampler
— they conflict

Examples

Example 1 — Quota Monitoring Bash Script

#!/usr/bin/env bash
# Monitor Sentry quota usage and alert if approaching limit
set -euo pipefail

ORG="${SENTRY_ORG:?Set SENTRY_ORG}"
TOKEN="${SENTRY_AUTH_TOKEN:?Set SENTRY_AUTH_TOKEN}"
THRESHOLD=${QUOTA_THRESHOLD:-80}  # Alert at 80% usage

accepted=$(curl -s -H "Authorization: Bearer $TOKEN" \
  "https://sentry.io/api/0/organizations/$ORG/stats_v2/?field=sum(quantity)&groupBy=outcome&category=error&statsPeriod=1h&interval=1h" \
  | python3 -c "
import json, sys
data = json.load(sys.stdin)
for g in data.get('groups', []):
    if g['by'].get('outcome') == 'accepted':
        print(g['totals']['sum(quantity)']); break
else: print(0)")

echo "Accepted events (last hour): $accepted"
[ "$accepted" -gt "$THRESHOLD" ] && echo "WARNING: volume $accepted > threshold $THRESHOLD"

Example 2 — Enable All Inbound Filters via API

#!/usr/bin/env bash
set -euo pipefail
BASE="https://sentry.io/api/0/projects/$SENTRY_ORG/$SENTRY_PROJECT/filters"
AUTH="Authorization: Bearer $SENTRY_AUTH_TOKEN"

for filter in legacy-browsers browser-extensions web-crawlers; do
  curl -s -X PUT -H "$AUTH" -H "Content-Type: application/json" \
    -d '{"active": true}' "$BASE/$filter/" && echo "Enabled: $filter"
done

Resources

Next Steps

  • Cost review: Query
    /stats_v2/
    weekly to track spend trends by category
  • Alert rules: Create Sentry alerts for volume spikes per project
  • Replay sampling: Apply
    replaysSessionSampleRate
    and
    replaysOnErrorSampleRate
    for session replay cost control
  • Server-side sampling: Explore Sentry Dynamic Sampling (server-side) for organization-wide policies
  • Audit
    ignoreErrors
    : Review quarterly — patterns may suppress real bugs as code evolves