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:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description",
"details": {}
},
"meta": {
"request_id": "req_abc123"
}
}| Field | Type | Description |
|---|---|---|
error.code | string | Machine-readable error code |
error.message | string | Human-readable error description |
error.details | object | Additional error context (optional) |
meta.request_id | string | Unique request identifier for debugging |
HTTP Status Codes
| Status | Meaning | When It Occurs |
|---|---|---|
200 | Success | Request completed successfully |
400 | Bad Request | Invalid request body or parameters |
401 | Unauthorized | Missing or invalid API key |
403 | Forbidden | Valid key but insufficient permissions |
404 | Not Found | Resource does not exist |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Server-side error |
Error Codes Reference
UNAUTHORIZED (401)
Returned when authentication fails.
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing API key"
},
"meta": {
"request_id": "req_abc123"
}
}Common Causes:
- Missing
Authorizationheader - API key does not start with
mos_ - API key has been revoked
- API key has expired
- Invalid key hash (key was modified)
Resolution:
- Verify the API key format:
mos_live_<random_string> - Check that the key is active in your dashboard
- Ensure the
Authorization: Bearer <key>header is included
FORBIDDEN (403)
Returned when the API key lacks required permissions.
{
"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:
- Check required scopes in endpoint documentation
- Create a new API key with appropriate scopes
- Use the admin dashboard to modify key scopes
Scope Reference:
| Scope | Required For |
|---|---|
memories:read | GET /v1/memories, GET /v1/entities |
memories:write | POST/PATCH/DELETE /v1/memories, POST /v1/entities |
search:read | POST /v1/search, POST /v1/context |
admin | GET/POST /v1/keys, GET /v1/usage |
NOT_FOUND (404)
Returned when a requested resource does not exist.
{
"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:
- Verify the resource ID is correct
- Check if the resource was recently deleted
- List resources to confirm existence
VALIDATION_ERROR (400)
Returned when request data fails validation.
{
"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:
| Field | Rules |
|---|---|
content | Required, non-empty string |
content_type | One of: text, conversation, document, event, fact |
tier | One of: short, medium, long |
memory_nature | One of: episodic, semantic |
parent_memory_id | Valid UUID format |
external_id | Unique per tenant |
limit | Integer 1-100 |
threshold | Number 0-1 |
RATE_LIMITED (429)
Returned when you exceed the rate limit.
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded"
},
"meta": {
"request_id": "req_abc123"
}
}Headers:
X-RateLimit-Reset: 1704067200Resolution:
- Check the
X-RateLimit-Resetheader for reset time - Implement exponential backoff
- Consider upgrading your plan for higher limits
INTERNAL_ERROR (500)
Returned when an unexpected server error occurs.
{
"error": {
"code": "INTERNAL_ERROR",
"message": "Internal server error"
},
"meta": {
"request_id": "req_abc123"
}
}Resolution:
- Save the
request_idfor support inquiries - Retry with exponential backoff
- Contact support if the error persists
Handling Errors
JavaScript Error Handler
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
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:
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
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:
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:
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
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
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
// 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
// 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
// Problem: Server under load
// Solution: Implement retry with backoff
const result = await fetchWithRetry('/v1/memories', {
method: 'GET'
}, 3); // Max 3 retries