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:
| Source | Format | How to identify |
|---|---|---|
| Rivaro | {"error": "message"} | Simple JSON with error string |
| AI Provider | Provider-specific format | Matches 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 Status | Code | Meaning |
|---|---|---|
| 401 | Unauthorized | Detection key missing, invalid, expired, or revoked |
| 403 | Forbidden | Model not allowed, org suspended, or actor quarantined |
| 429 | Rate Limited | Too many requests for this key or IP |
| 451 | Blocked | Request blocked by enforcement policy |
Provider errors (passed through)
These come from the AI provider, not Rivaro. Common examples:
| HTTP Status | Provider | Meaning |
|---|---|---|
| 401 | OpenAI/Anthropic | Invalid provider API key |
| 429 | OpenAI/Anthropic | Provider rate limit (separate from Rivaro's) |
| 500 | Any | Provider internal error |
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-Keyheader 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))
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:
- Check the Rivaro dashboard for the violation details
- Look for the detection type (e.g.
PII_SSN,SECURITY_PROMPT_INJECTION) - 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()
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_EXFILTRATIONdetections 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
- Understanding Detections — Full reference of what Rivaro detects
- Enforcement & Policies — How enforcement decisions are made
- API Reference — Full endpoint reference