Skip to main content

Error Handling

How to distinguish Rivaro errors from provider errors, handle each error type, implement retry strategies, and debug enforcement decisions.

Rivaro Errors vs Provider Errors

When something goes wrong, the error can come from two places:

SourceFormatHow to identify
Rivaro{"error": "message"}Simple JSON with error string
AI ProviderProvider-specific formatMatches OpenAI/Anthropic/etc. error schema

Rivaro errors use HTTP status codes in the 4xx range. Provider errors are passed through unchanged — Rivaro doesn't modify them.

Rivaro error codes

HTTP StatusCodeMeaning
401UnauthorizedDetection key missing, invalid, expired, or revoked
403ForbiddenModel not allowed, org suspended, or actor quarantined
429Rate LimitedToo many requests for this key or IP
451BlockedRequest blocked by enforcement policy

Provider errors (passed through)

These come from the AI provider, not Rivaro. Common examples:

HTTP StatusProviderMeaning
401OpenAI/AnthropicInvalid provider API key
429OpenAI/AnthropicProvider rate limit (separate from Rivaro's)
500AnyProvider internal error
note

How to tell them apart: Rivaro errors always have the format {"error": "string"}. Provider errors use their own format (e.g. OpenAI returns {"error": {"message": "...", "type": "...", "code": "..."}}).

Handling Each Error Type

401 — Invalid Detection Key

{"error": "Detection key required for proxy endpoints. Add X-Detection-Key header."}
{"error": "Invalid detection key"}
{"error": "Organization is not active"}

Don't retry. The key is wrong, missing, or revoked. Check:

  • Is the X-Detection-Key header present?
  • Is the key correct (starts with detect_live_)?
  • Has the key been revoked in the dashboard?
  • Has the key expired?
  • Is the organization active?
try:
response = client.chat.completions.create(...)
except openai.AuthenticationError as e:
if "detection key" in str(e).lower() or "Detection key" in str(e):
# Rivaro auth error — check your detection key
logger.error("Rivaro detection key invalid")
else:
# OpenAI auth error — check your provider API key
logger.error("OpenAI API key invalid")

403 — Model Not Permitted

{"error": "The requested model is not permitted by your API key's policy."}

Don't retry. The model you requested is not in the AppContext's allowed models list. Either:

  • Use a different model that's in the allowed list
  • Ask your admin to add the model to the AppContext configuration

429 — Rate Limited

{"error": "Rate limit exceeded"}

Retry with backoff. Use the response headers to determine when to retry:

import time

def call_with_retry(func, max_retries=3):
for attempt in range(max_retries):
try:
return func()
except openai.RateLimitError as e:
if attempt == max_retries - 1:
raise

retry_after = e.response.headers.get('Retry-After', 60)
remaining = e.response.headers.get('X-RateLimit-Remaining', '0')

logger.warning(
f"Rate limited. Remaining: {remaining}. "
f"Retrying in {retry_after}s (attempt {attempt + 1})"
)
time.sleep(int(retry_after))
note

Two sources of 429s: Rivaro and the AI provider both have rate limits. Check the error format to determine which one you hit.

451 — Blocked by Policy

{"error": "Request blocked by enforcement policy"}

Don't retry. The enforcement engine determined that your request content violates a policy rule. This is intentional — the request was blocked to prevent a violation.

In the response body (for non-streaming), you may also see:

{
"choices": [{
"message": {"content": "Content blocked due to policy violations"},
"finish_reason": "content_filter"
}]
}

To understand why it was blocked:

  1. Check the Rivaro dashboard for the violation details
  2. Look for the detection type (e.g. PII_SSN, SECURITY_PROMPT_INJECTION)
  3. Review the policy rule that triggered the block
try:
response = client.chat.completions.create(...)

if response.choices[0].finish_reason == "content_filter":
logger.warning("Response was filtered by Rivaro enforcement")

except openai.APIStatusError as e:
if e.status_code == 451:
logger.warning("Request blocked by Rivaro enforcement policy")

Graceful Degradation

When the proxy is unavailable

If your Rivaro proxy instance is down or unreachable, your AI requests will fail. Consider these patterns:

Circuit breaker: Track consecutive proxy failures and temporarily fall back to calling the AI provider directly.

class ProxyCircuitBreaker:
def __init__(self, failure_threshold=5, recovery_timeout=60):
self.failures = 0
self.threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.last_failure_time = None
self.is_open = False

def call(self, proxy_func, fallback_func):
if self.is_open:
if time.time() - self.last_failure_time > self.recovery_timeout:
self.is_open = False # Try proxy again
else:
return fallback_func() # Still in recovery

try:
result = proxy_func()
self.failures = 0
return result
except ConnectionError:
self.failures += 1
self.last_failure_time = time.time()
if self.failures >= self.threshold:
self.is_open = True
return fallback_func()
warning

Important: Falling back to the AI provider directly means bypassing enforcement. Log these fallback events and alert your security team. This should be a temporary measure, not a permanent pattern.

Timeout handling

Set reasonable timeouts for proxy requests. The proxy adds minimal latency (typically <50ms) but AI provider responses can be slow:

client = OpenAI(
base_url="https://your-org.rivaro.ai/v1",
timeout=60.0, # 60 second timeout
default_headers={
"X-Detection-Key": "detect_live_your_key_here"
}
)

Debugging Enforcement Decisions

When a request is blocked or modified and you need to understand why:

1. Check the dashboard

The Rivaro dashboard shows every enforcement event with:

  • Detection type — what was found (e.g. PII_SSN, PROMPT_INJECTION_EXPLOIT)
  • Severity — LOW, MEDIUM, HIGH, CRITICAL
  • Action taken — BLOCK, REDACT, LOG, etc.
  • Policy rule — which rule triggered the action
  • Risk category — the broader risk classification
  • Content — the detected content (masked in the UI by default)

2. Check the request ID

Every proxied request gets a request ID. Look for it in:

  • Dashboard activity feed (filter by timestamp)
  • Session view (if the request was part of a session)

3. Common debugging scenarios

"My request was blocked but I don't see why"

  • Check the dashboard for the detection details
  • The detection might be in the input (ingress) or a previous response (session context)
  • The actor might be quarantined from previous violations

"Content was redacted unexpectedly"

  • Look for PII/PHI/financial data detection types in the dashboard
  • Check if a broad risk-category rule is matching (e.g. all EXTERNAL_DATA_EXFILTRATION detections are set to REDACT)
  • The redacted content might be a false positive — review the detected content in the dashboard

"I'm getting 403 but my key is valid"

  • The key's AppContext might not have the requested model in its allowed list
  • The organization status might have changed (trial expired, etc.)
  • The actor associated with this key might be quarantined

Next steps