molroomolroo
Guides

Error Handling

Error codes, response formats, and recovery strategies for molroo-api.

Error Handling

All molroo-api errors follow a consistent JSON format. This guide covers every error code, what causes it, and how to handle it -- including WebSocket-specific errors.

Error Response Format

Every REST error response has this structure:

{
  "error": {
    "code": "ERROR_CODE",
    "status": 400,
    "message": "Human-readable description of what went wrong"
  }
}
FieldTypeDescription
codestringMachine-readable error code (use this in code)
statusnumberHTTP status code
messagestringHuman-readable explanation

Error Codes

Authentication Errors

CodeStatusDescriptionFix
UNAUTHORIZED401Missing or invalid API keyCheck Authorization: Bearer <key> header
FORBIDDEN403API key lacks permission for this actionVerify key permissions in Dashboard

Validation Errors

CodeStatusDescriptionFix
VALIDATION_ERROR400Request body failed validationCheck field types, required fields, ranges
INVALID_PERSONA400Personality traits out of [0, 1] rangeEnsure all HEXACO values are between 0-1
INVALID_APPRAISAL400Appraisal vector values out of valid rangesCheck ranges: goal_relevance [0,1], goal_congruence [-1,1], etc.
INVALID_VAD400VAD values out of rangeV: [-1,1], A: [0,1], D: [-1,1]
INVALID_PRESET400Unknown preset nameUse a valid preset: companion, mentor, rival, stoic, creative, professional

Resource Errors

CodeStatusDescriptionFix
NOT_FOUND404Persona, session, or resource not foundVerify the ID exists and belongs to your account

Rate Limiting

CodeStatusDescriptionFix
RATE_LIMIT429Too many requestsBack off and retry with exponential delay

The 429 response includes a Retry-After header indicating how many seconds to wait.

Server Errors

CodeStatusDescriptionFix
INTERNAL_ERROR500Unexpected server errorRetry after a short delay; contact support if persistent
SERVICE_UNAVAILABLE503API is temporarily unavailableCheck status page; retry later

Handling Errors in Code

JavaScript

async function callMolroo(url, options) {
  const response = await fetch(url, options);
 
  if (!response.ok) {
    const { error } = await response.json();
 
    switch (error.code) {
      case "UNAUTHORIZED":
        throw new Error("Invalid API key. Check your credentials.");
      case "RATE_LIMIT":
        const retryAfter = response.headers.get("Retry-After") || 5;
        await new Promise((r) => setTimeout(r, retryAfter * 1000));
        return callMolroo(url, options); // retry
      case "NOT_FOUND":
        throw new Error(`Resource not found: ${error.message}`);
      case "VALIDATION_ERROR":
        throw new Error(`Bad request: ${error.message}`);
      default:
        throw new Error(`API error [${error.code}]: ${error.message}`);
    }
  }
 
  return response.json();
}

Python

import time
import requests
 
def call_molroo(method, url, **kwargs):
    response = getattr(requests, method)(url, **kwargs)
 
    if response.ok:
        return response.json()
 
    error = response.json().get("error", {})
    code = error.get("code", "UNKNOWN")
    message = error.get("message", "Unknown error")
 
    if code == "RATE_LIMIT":
        retry_after = int(response.headers.get("Retry-After", 5))
        time.sleep(retry_after)
        return call_molroo(method, url, **kwargs)
 
    if code == "UNAUTHORIZED":
        raise PermissionError(f"Invalid API key: {message}")
 
    if code == "NOT_FOUND":
        raise LookupError(f"Resource not found: {message}")
 
    if code == "VALIDATION_ERROR":
        raise ValueError(f"Validation failed: {message}")
 
    raise RuntimeError(f"API error [{code}]: {message}")

WebSocket Error Handling

WebSocket connections have their own error patterns. Errors are sent as JSON messages with type: "error":

{
  "type": "error",
  "code": "INVALID_MESSAGE",
  "message": "Unknown message type: foo"
}

WebSocket Error Codes

CodeDescriptionFix
INVALID_MESSAGEMalformed JSON or unknown message typeCheck message format and type field
UNAUTHORIZEDInvalid or expired token in connection URLReconnect with a valid API key
SESSION_NOT_FOUNDSession ID does not existVerify session ID or create a new session
RATE_LIMITToo many messages sentThrottle message frequency

Handling WebSocket Errors

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
 
  if (msg.type === "error") {
    console.error(`WebSocket error [${msg.code}]: ${msg.message}`);
 
    switch (msg.code) {
      case "UNAUTHORIZED":
        // Token expired or invalid — reconnect with fresh token
        ws.close();
        reconnectWithNewToken();
        break;
      case "INVALID_MESSAGE":
        // Fix the message format and retry
        console.error("Check your message format");
        break;
      case "RATE_LIMIT":
        // Slow down message frequency
        throttleMessages();
        break;
    }
    return;
  }
 
  // Handle normal messages...
};

Connection Drop Recovery

WebSocket connections can close unexpectedly. Always implement reconnection logic:

ws.onclose = (event) => {
  if (event.code === 1000) {
    // Normal close — do nothing
    return;
  }
 
  // Abnormal close — reconnect
  console.log(`Connection lost (code: ${event.code}). Reconnecting...`);
  reconnectWithBackoff();
};
 
ws.onerror = (error) => {
  console.error("WebSocket error:", error);
  // The onclose handler will fire after this — reconnection happens there
};

Common WebSocket close codes:

CodeMeaningAction
1000Normal closureNone
1001Server going away (restart)Reconnect after short delay
1006Abnormal closure (no close frame)Reconnect with backoff
1008Policy violationCheck your API key and usage
1011Server errorReconnect with backoff

Retry Strategy

For transient errors (429, 500, 503), use exponential backoff:

async function withRetry(fn, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (attempt === maxRetries) throw err;
 
      const delay = Math.min(1000 * 2 ** attempt, 30000);
      const jitter = delay * (0.5 + Math.random() * 0.5);
      await new Promise((r) => setTimeout(r, jitter));
    }
  }
}
 
// Usage
const result = await withRetry(() =>
  callMolroo("https://api.molroo.io/v1/turn", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  })
);

Common Mistakes

MistakeError CodeSolution
Missing Content-Type headerVALIDATION_ERRORAdd Content-Type: application/json
Personality trait > 1 or < 0INVALID_PERSONAKeep all HEXACO values in [0, 1]
Using deleted session IDNOT_FOUNDCreate a new session
Sending goal_congruence: 2.0INVALID_APPRAISALValid range is [-1, 1]
Invalid preset nameINVALID_PRESETCheck available presets in Persona Design
Sending non-JSON over WebSocketINVALID_MESSAGEAlways JSON.stringify() before sending
Using test key (mk_test_) in production--Switch to mk_live_ key