Memory OS

Errors

Memory OS uses conventional HTTP response codes to indicate success or failure of API requests. This page documents all error responses and provides strategies for handling them.

Error Response Format

All error responses follow a consistent JSON structure:

JSON
{
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error description",
    "details": {}
  },
  "meta": {
    "request_id": "req_abc123"
  }
}
FieldTypeDescription
error.codestringMachine-readable error code
error.messagestringHuman-readable error description
error.detailsobjectAdditional error context (optional)
meta.request_idstringUnique request identifier for debugging

HTTP Status Codes

StatusMeaningWhen It Occurs
200SuccessRequest completed successfully
400Bad RequestInvalid request body or parameters
401UnauthorizedMissing or invalid API key
403ForbiddenValid key but insufficient permissions
404Not FoundResource does not exist
429Too Many RequestsRate limit exceeded
500Internal Server ErrorServer-side error

Error Codes Reference

UNAUTHORIZED (401)

Returned when authentication fails.

JSON
{
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or missing API key"
  },
  "meta": {
    "request_id": "req_abc123"
  }
}

Common Causes:

  • Missing Authorization header
  • API key does not start with mos_
  • API key has been revoked
  • API key has expired
  • Invalid key hash (key was modified)

Resolution:

  1. Verify the API key format: mos_live_<random_string>
  2. Check that the key is active in your dashboard
  3. Ensure the Authorization: Bearer <key> header is included

FORBIDDEN (403)

Returned when the API key lacks required permissions.

JSON
{
  "error": {
    "code": "FORBIDDEN",
    "message": "Missing scope: memories:write"
  },
  "meta": {
    "request_id": "req_abc123"
  }
}

Common Causes:

  • API key does not have the required scope
  • Attempting admin operations with a non-admin key

Resolution:

  1. Check required scopes in endpoint documentation
  2. Create a new API key with appropriate scopes
  3. Use the admin dashboard to modify key scopes

Scope Reference:

ScopeRequired For
memories:readGET /v1/memories, GET /v1/entities
memories:writePOST/PATCH/DELETE /v1/memories, POST /v1/entities
search:readPOST /v1/search, POST /v1/context
adminGET/POST /v1/keys, GET /v1/usage

NOT_FOUND (404)

Returned when a requested resource does not exist.

JSON
{
  "error": {
    "code": "NOT_FOUND",
    "message": "Memory not found"
  },
  "meta": {
    "request_id": "req_abc123"
  }
}

Common Causes:

  • Invalid or non-existent resource ID
  • Resource was deleted
  • Resource belongs to a different tenant

Resolution:

  1. Verify the resource ID is correct
  2. Check if the resource was recently deleted
  3. List resources to confirm existence

VALIDATION_ERROR (400)

Returned when request data fails validation.

JSON
{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "content is required",
    "details": {
      "field": "content",
      "constraint": "required"
    }
  },
  "meta": {
    "request_id": "req_abc123"
  }
}

Common Causes:

  • Missing required fields
  • Invalid field types or formats
  • Value out of allowed range
  • Duplicate unique field values

Validation Rules:

FieldRules
contentRequired, non-empty string
content_typeOne of: text, conversation, document, event, fact
tierOne of: short, medium, long
memory_natureOne of: episodic, semantic
parent_memory_idValid UUID format
external_idUnique per tenant
limitInteger 1-100
thresholdNumber 0-1

RATE_LIMITED (429)

Returned when you exceed the rate limit.

JSON
{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded"
  },
  "meta": {
    "request_id": "req_abc123"
  }
}

Headers:

HTTP
X-RateLimit-Reset: 1704067200

Resolution:

  1. Check the X-RateLimit-Reset header for reset time
  2. Implement exponential backoff
  3. Consider upgrading your plan for higher limits

INTERNAL_ERROR (500)

Returned when an unexpected server error occurs.

JSON
{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "Internal server error"
  },
  "meta": {
    "request_id": "req_abc123"
  }
}

Resolution:

  1. Save the request_id for support inquiries
  2. Retry with exponential backoff
  3. Contact support if the error persists

Handling Errors

JavaScript Error Handler

JavaScript
class MemoryOSError extends Error {
  constructor(status, code, message, requestId, details = null) {
    super(message);
    this.name = 'MemoryOSError';
    this.status = status;
    this.code = code;
    this.requestId = requestId;
    this.details = details;
  }

  get isRetryable() {
    return this.status === 429 || this.status >= 500;
  }
}

async function callAPI(url, options = {}) {
  const response = await fetch(url, {
    ...options,
    headers: {
      'Authorization': 'Bearer mos_live_<your_key>',
      'Content-Type': 'application/json',
      ...options.headers
    }
  });

  const data = await response.json();

  if (!response.ok) {
    throw new MemoryOSError(
      response.status,
      data.error?.code || 'UNKNOWN_ERROR',
      data.error?.message || 'An error occurred',
      data.meta?.request_id,
      data.error?.details
    );
  }

  return data;
}

// Usage with error handling
try {
  const result = await callAPI('https://api.mymemoryos.com/api/v1/memories', {
    method: 'POST',
    body: JSON.stringify({ content: 'Test memory' })
  });
} catch (error) {
  if (error instanceof MemoryOSError) {
    switch (error.code) {
      case 'UNAUTHORIZED':
        console.error('Check your API key');
        break;
      case 'FORBIDDEN':
        console.error('Insufficient permissions:', error.message);
        break;
      case 'VALIDATION_ERROR':
        console.error('Invalid request:', error.details);
        break;
      case 'RATE_LIMITED':
        console.error('Rate limited, retry after limit resets');
        break;
      default:
        console.error(`Error [${error.code}]: ${error.message}`);
    }
  }
}

Python Error Handler

Python
import requests
from dataclasses import dataclass
from typing import Optional, Any


@dataclass
class MemoryOSError(Exception):
    status: int
    code: str
    message: str
    request_id: str
    details: Optional[Any] = None

    def __str__(self):
        return f"[{self.code}] {self.message}"

    @property
    def is_retryable(self) -> bool:
        return self.status == 429 or self.status >= 500


def call_api(url: str, method: str = 'GET', **kwargs) -> dict:
    headers = {
        'Authorization': 'Bearer mos_live_<your_key>',
        'Content-Type': 'application/json',
        **kwargs.pop('headers', {})
    }

    response = requests.request(method, url, headers=headers, **kwargs)
    data = response.json()

    if not response.ok:
        raise MemoryOSError(
            status=response.status_code,
            code=data.get('error', {}).get('code', 'UNKNOWN_ERROR'),
            message=data.get('error', {}).get('message', 'An error occurred'),
            request_id=data.get('meta', {}).get('request_id'),
            details=data.get('error', {}).get('details')
        )

    return data


# Usage
try:
    result = call_api(
        'https://api.mymemoryos.com/api/v1/memories',
        method='POST',
        json={'content': 'Test memory'}
    )
except MemoryOSError as e:
    if e.code == 'UNAUTHORIZED':
        print('Check your API key')
    elif e.code == 'FORBIDDEN':
        print(f'Insufficient permissions: {e.message}')
    elif e.code == 'VALIDATION_ERROR':
        print(f'Invalid request: {e.details}')
    elif e.code == 'RATE_LIMITED':
        print('Rate limited, retry after limit resets')
    else:
        print(f'Error [{e.code}]: {e.message}')

Retry Strategies

Exponential Backoff

For retryable errors (429, 5xx), use exponential backoff:

JavaScript
async function fetchWithRetry(url, options, maxRetries = 3) {
  let lastError;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);
      const data = await response.json();

      if (!response.ok) {
        const error = new MemoryOSError(
          response.status,
          data.error?.code,
          data.error?.message,
          data.meta?.request_id
        );

        if (!error.isRetryable) {
          throw error;
        }

        lastError = error;

        // Handle rate limiting
        if (response.status === 429) {
          const resetTime = parseInt(response.headers.get('X-RateLimit-Reset'));
          const waitMs = (resetTime * 1000) - Date.now() + 1000;
          console.log(`Rate limited. Waiting ${waitMs}ms...`);
          await sleep(Math.max(waitMs, 1000));
        } else {
          // Exponential backoff for server errors
          const backoffMs = Math.min(1000 * Math.pow(2, attempt), 30000);
          const jitter = Math.random() * 1000;
          console.log(`Retry ${attempt + 1}/${maxRetries} in ${backoffMs + jitter}ms`);
          await sleep(backoffMs + jitter);
        }

        continue;
      }

      return data;
    } catch (error) {
      if (error instanceof MemoryOSError && !error.isRetryable) {
        throw error;
      }
      lastError = error;
    }
  }

  throw lastError || new Error('Max retries exceeded');
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

Python Retry with Tenacity

Python
from tenacity import (
    retry,
    stop_after_attempt,
    wait_exponential,
    retry_if_exception
)


def should_retry(exception):
    if isinstance(exception, MemoryOSError):
        return exception.is_retryable
    return False


@retry(
    stop=stop_after_attempt(3),
    wait=wait_exponential(multiplier=1, min=1, max=30),
    retry=retry_if_exception(should_retry)
)
def call_api_with_retry(url: str, method: str = 'GET', **kwargs) -> dict:
    return call_api(url, method, **kwargs)

Circuit Breaker Pattern

Prevent cascading failures:

JavaScript
class CircuitBreaker {
  constructor(options = {}) {
    this.failureThreshold = options.failureThreshold || 5;
    this.resetTimeout = options.resetTimeout || 60000;
    this.failures = 0;
    this.lastFailureTime = null;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
  }

  async call(fn) {
    if (this.state === 'OPEN') {
      if (Date.now() - this.lastFailureTime > this.resetTimeout) {
        this.state = 'HALF_OPEN';
      } else {
        throw new Error('Circuit breaker is OPEN');
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failures = 0;
    this.state = 'CLOSED';
  }

  onFailure() {
    this.failures++;
    this.lastFailureTime = Date.now();

    if (this.failures >= this.failureThreshold) {
      this.state = 'OPEN';
    }
  }
}

const breaker = new CircuitBreaker();

async function safeAPICall(url, options) {
  return breaker.call(() => fetchWithRetry(url, options));
}

Debugging Tips

1. Check Request ID

Every response includes a unique request_id. Save this for support inquiries:

JavaScript
try {
  await callAPI('/v1/memories');
} catch (error) {
  console.error(`Request failed. ID: ${error.requestId}`);
  // Log or report error.requestId for debugging
}

2. Validate Request Before Sending

JavaScript
function validateMemoryInput(input) {
  const errors = [];

  if (!input.content || typeof input.content !== 'string') {
    errors.push('content must be a non-empty string');
  }

  if (input.content_type &&
      !['text', 'conversation', 'document', 'event', 'fact'].includes(input.content_type)) {
    errors.push('content_type must be one of: text, conversation, document, event, fact');
  }

  if (input.tier && !['short', 'medium', 'long'].includes(input.tier)) {
    errors.push('tier must be one of: short, medium, long');
  }

  if (errors.length > 0) {
    throw new Error(`Validation failed: ${errors.join('; ')}`);
  }
}

3. Log All API Interactions

JavaScript
async function callAPIWithLogging(url, options = {}) {
  const startTime = Date.now();
  const requestId = `local_${Date.now()}`;

  console.log(`[${requestId}] Request: ${options.method || 'GET'} ${url}`);

  try {
    const response = await fetch(url, options);
    const data = await response.json();

    console.log(`[${requestId}] Response: ${response.status} (${Date.now() - startTime}ms)`);

    if (!response.ok) {
      console.error(`[${requestId}] Error:`, data.error);
    }

    return { response, data };
  } catch (error) {
    console.error(`[${requestId}] Network error:`, error.message);
    throw error;
  }
}

Common Error Scenarios

Scenario: Memory Creation Fails

JavaScript
// Problem: content is empty
const response = await fetch('/v1/memories', {
  method: 'POST',
  body: JSON.stringify({ content: '' })  // Empty string
});
// Error: VALIDATION_ERROR - content is required

// Solution: Ensure content is non-empty
if (!content || content.trim().length === 0) {
  throw new Error('Content cannot be empty');
}

Scenario: Search Returns No Results

JavaScript
// Problem: threshold too high
const response = await fetch('/v1/search', {
  method: 'POST',
  body: JSON.stringify({
    query: 'user preferences',
    threshold: 0.95  // Very high threshold
  })
});
// Returns: { results: [] }

// Solution: Lower threshold for broader matches
const response = await fetch('/v1/search', {
  method: 'POST',
  body: JSON.stringify({
    query: 'user preferences',
    threshold: 0.6  // More permissive
  })
});

Scenario: Intermittent 500 Errors

JavaScript
// Problem: Server under load
// Solution: Implement retry with backoff

const result = await fetchWithRetry('/v1/memories', {
  method: 'GET'
}, 3);  // Max 3 retries
Ctrl+Shift+C to copy