> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/modrinth/code/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Understanding HTTP status codes, error formats, and debugging strategies

The Modrinth API uses conventional HTTP status codes and returns structured error messages to help you debug issues quickly.

## Error Response Format

All API errors follow a consistent JSON structure:

```json theme={null}
{
  "error": "error_type",
  "description": "Human-readable error message explaining what went wrong"
}
```

Some errors may include additional details:

```json theme={null}
{
  "error": "invalid_input",
  "description": "Validation failed for project creation",
  "details": {
    "field": "slug",
    "message": "Slug is already taken"
  }
}
```

## HTTP Status Codes

The API uses standard HTTP status codes to indicate the success or failure of requests.

### Success Codes (2xx)

<ResponseField name="200 OK" type="success">
  Request succeeded. Response body contains the requested data.

  ```json theme={null}
  {
    "id": "AABBCCDD",
    "name": "My Project",
    "slug": "my-project"
  }
  ```
</ResponseField>

<ResponseField name="201 Created" type="success">
  Resource successfully created. Response body contains the new resource.

  ```json theme={null}
  {
    "id": "IIJJKKLL",
    "name": "Version 1.0.0",
    "version_number": "1.0.0"
  }
  ```
</ResponseField>

<ResponseField name="204 No Content" type="success">
  Request succeeded but there's no response body (e.g., DELETE operations).
</ResponseField>

<ResponseField name="304 Not Modified" type="success">
  Resource hasn't changed since the last request (when using `If-None-Match` with ETags).
</ResponseField>

### Client Error Codes (4xx)

<ResponseField name="400 Bad Request" type="error">
  The request is malformed or contains invalid data.

  **Common error types:**

  * `invalid_input`: Invalid request parameters
  * `json_error`: Malformed JSON in request body
  * `xml_error`: Malformed XML data
  * `validation_error`: Data failed validation rules

  ```json theme={null}
  {
    "error": "invalid_input",
    "description": "Project slug must be between 3 and 64 characters"
  }
  ```
</ResponseField>

<ResponseField name="401 Unauthorized" type="error">
  Authentication is required or credentials are invalid.

  **Common error types:**

  * `unauthorized`: Missing or invalid authentication token
  * `auth_error`: Authentication failed

  ```json theme={null}
  {
    "error": "unauthorized",
    "description": "Invalid authentication credentials"
  }
  ```

  **Solutions:**

  * Include a valid `Authorization` header
  * Check that your token hasn't expired
  * Verify your token hasn't been revoked
</ResponseField>

<ResponseField name="403 Forbidden" type="error">
  You're authenticated but don't have permission to perform this action.

  **Common causes:**

  * Token lacks required scope
  * Attempting to modify someone else's resource
  * Attempting a restricted operation

  ```json theme={null}
  {
    "error": "unauthorized",
    "description": "Token does not have the required scope: PROJECT_WRITE"
  }
  ```
</ResponseField>

<ResponseField name="404 Not Found" type="error">
  The requested resource doesn't exist.

  ```json theme={null}
  {
    "error": "not_found",
    "description": "Resource not found"
  }
  ```

  **Common causes:**

  * Invalid project/version/user ID
  * Resource has been deleted
  * Typo in the endpoint URL
</ResponseField>

<ResponseField name="409 Conflict" type="error">
  The request conflicts with the current state of the resource.

  ```json theme={null}
  {
    "error": "conflict",
    "description": "A project with this slug already exists"
  }
  ```

  **Common scenarios:**

  * Duplicate slug or username
  * Concurrent modification conflicts
  * Resource state prevents the operation
</ResponseField>

<ResponseField name="410 Gone" type="error">
  The API version or resource has been permanently removed.

  ```json theme={null}
  {
    "error": "api_deprecated",
    "description": "You are using an outdated version of Modrinth's API. Please update your application."
  }
  ```

  **Solution:** Migrate to a supported API version (v2 or v3).
</ResponseField>

<ResponseField name="429 Too Many Requests" type="error">
  You've exceeded the rate limit.

  ```json theme={null}
  {
    "error": "ratelimit_error",
    "description": "You are being rate-limited. Please wait 15000 milliseconds. 0/300 remaining."
  }
  ```

  **Response headers:**

  ```http theme={null}
  X-Ratelimit-Limit: 300
  X-Ratelimit-Remaining: 0
  X-Ratelimit-Reset: 15
  Retry-After: 15
  ```

  See [Rate Limits](/api/rate-limits) for detailed handling strategies.
</ResponseField>

### Server Error Codes (5xx)

<ResponseField name="500 Internal Server Error" type="error">
  An unexpected error occurred on the server.

  **Common error types:**

  * `internal_error`: Unexpected server error
  * `database_error`: Database operation failed
  * `search_error`: Search engine error

  ```json theme={null}
  {
    "error": "internal_error",
    "description": "An unexpected error occurred while processing your request"
  }
  ```

  **What to do:**

  * Retry the request after a short delay
  * If the error persists, report it to Modrinth support
</ResponseField>

<ResponseField name="502 Bad Gateway" type="error">
  The API server received an invalid response from an upstream server.

  **Common causes:**

  * Temporary connectivity issues
  * Upstream service degradation

  **Solution:** Retry with exponential backoff.
</ResponseField>

<ResponseField name="503 Service Unavailable" type="error">
  The API is temporarily unavailable.

  **Common causes:**

  * Scheduled maintenance
  * Server overload
  * Deployment in progress

  **Solution:** Retry after waiting. Check [Modrinth's status page](https://status.modrinth.com).
</ResponseField>

<ResponseField name="504 Gateway Timeout" type="error">
  The request took too long to process.

  **Common causes:**

  * Complex search queries
  * Large file uploads
  * Database slowdowns

  **Solutions:**

  * Simplify your query
  * Break large requests into smaller chunks
  * Retry the request
</ResponseField>

## Common Error Types

Here's a reference of error type strings returned in the `error` field:

### Authentication Errors

| Error Type            | Status | Description                                   |
| --------------------- | ------ | --------------------------------------------- |
| `unauthorized`        | 401    | Invalid or missing authentication credentials |
| `auth_error`          | 401    | Authentication process failed                 |
| `invalid_credentials` | 401    | Username/password or token is invalid         |
| `invalid_auth_method` | 401    | Authentication method not supported           |

### Input Validation Errors

| Error Type         | Status | Description                    |
| ------------------ | ------ | ------------------------------ |
| `invalid_input`    | 400    | Request contains invalid data  |
| `validation_error` | 400    | Data failed validation rules   |
| `json_error`       | 400    | Malformed JSON in request body |
| `xml_error`        | 400    | Malformed XML data             |
| `decoding_error`   | 400    | Failed to decode base62 ID     |

### Resource Errors

| Error Type  | Status | Description                              |
| ----------- | ------ | ---------------------------------------- |
| `not_found` | 404    | Requested resource doesn't exist         |
| `conflict`  | 409    | Request conflicts with existing resource |

### Rate Limiting

| Error Type        | Status | Description                            |
| ----------------- | ------ | -------------------------------------- |
| `ratelimit_error` | 429    | Too many requests, rate limit exceeded |

### Server Errors

| Error Type             | Status | Description               |
| ---------------------- | ------ | ------------------------- |
| `internal_error`       | 500    | Unexpected server error   |
| `database_error`       | 500    | Database operation failed |
| `redis_database_error` | 500    | Redis cache error         |
| `clickhouse_error`     | 500    | Analytics database error  |
| `search_error`         | 500    | Search engine error       |
| `file_hosting_error`   | 500    | File storage error        |
| `mail_error`           | 500    | Email sending failed      |

### External Service Errors

| Error Type        | Status | Description                         |
| ----------------- | ------ | ----------------------------------- |
| `payments_error`  | 424    | Payment processor error             |
| `stripe_error`    | 424    | Stripe payment processing failed    |
| `discord_error`   | 424    | Discord integration failed          |
| `turnstile_error` | 400    | Cloudflare Turnstile captcha failed |

## Error Handling Best Practices

### 1. Implement Comprehensive Error Handling

```javascript theme={null}
async function makeApiRequest(url, options = {}) {
  try {
    const response = await fetch(url, {
      ...options,
      headers: {
        'User-Agent': 'my-app/1.0.0',
        ...options.headers
      }
    });
    
    // Handle rate limiting
    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After');
      throw new RateLimitError(
        `Rate limited. Retry after ${retryAfter} seconds`,
        parseInt(retryAfter)
      );
    }
    
    // Handle client errors
    if (response.status >= 400 && response.status < 500) {
      const error = await response.json();
      throw new ClientError(error.description, response.status, error.error);
    }
    
    // Handle server errors
    if (response.status >= 500) {
      const error = await response.json();
      throw new ServerError(error.description, response.status, error.error);
    }
    
    // Success
    return response.json();
    
  } catch (error) {
    if (error instanceof RateLimitError) {
      // Wait and retry
      await sleep(error.retryAfter * 1000);
      return makeApiRequest(url, options);
    }
    
    if (error instanceof ClientError) {
      // Log and handle client error
      console.error('Client error:', error.message);
      throw error;
    }
    
    if (error instanceof ServerError) {
      // Retry with exponential backoff
      console.warn('Server error, retrying...');
      await sleep(1000);
      return makeApiRequest(url, options);
    }
    
    // Network or other error
    throw error;
  }
}

class RateLimitError extends Error {
  constructor(message, retryAfter) {
    super(message);
    this.name = 'RateLimitError';
    this.retryAfter = retryAfter;
  }
}

class ClientError extends Error {
  constructor(message, status, type) {
    super(message);
    this.name = 'ClientError';
    this.status = status;
    this.type = type;
  }
}

class ServerError extends Error {
  constructor(message, status, type) {
    super(message);
    this.name = 'ServerError';
    this.status = status;
    this.type = type;
  }
}
```

### 2. Use Exponential Backoff for Retries

```python theme={null}
import time
import requests
from requests.exceptions import RequestException

def make_request_with_retry(url, max_retries=5):
    """Make API request with exponential backoff retry logic."""
    
    for attempt in range(max_retries):
        try:
            response = requests.get(
                url,
                headers={'User-Agent': 'my-app/1.0.0'}
            )
            
            # Handle rate limiting
            if response.status_code == 429:
                retry_after = int(response.headers.get('Retry-After', 60))
                print(f"Rate limited. Waiting {retry_after} seconds...")
                time.sleep(retry_after)
                continue
            
            # Handle server errors with exponential backoff
            if response.status_code >= 500:
                wait_time = min(2 ** attempt, 60)  # Max 60 seconds
                print(f"Server error. Retrying in {wait_time} seconds...")
                time.sleep(wait_time)
                continue
            
            # Raise for client errors (don't retry)
            if 400 <= response.status_code < 500:
                error_data = response.json()
                raise Exception(
                    f"{error_data.get('error')}: {error_data.get('description')}"
                )
            
            # Success
            response.raise_for_status()
            return response.json()
            
        except RequestException as e:
            if attempt == max_retries - 1:
                raise
            
            wait_time = min(2 ** attempt, 60)
            print(f"Request failed: {e}. Retrying in {wait_time} seconds...")
            time.sleep(wait_time)
    
    raise Exception(f"Max retries ({max_retries}) exceeded")

# Usage
try:
    data = make_request_with_retry('https://api.modrinth.com/v2/search')
    print(data)
except Exception as e:
    print(f"Request failed: {e}")
```

### 3. Log Errors for Debugging

```javascript theme={null}
class ApiClient {
  constructor(baseUrl, logger) {
    this.baseUrl = baseUrl;
    this.logger = logger;
  }
  
  async request(endpoint, options = {}) {
    const url = `${this.baseUrl}${endpoint}`;
    
    this.logger.info('API Request', {
      method: options.method || 'GET',
      url,
      timestamp: new Date().toISOString()
    });
    
    try {
      const response = await fetch(url, {
        ...options,
        headers: {
          'User-Agent': 'my-app/1.0.0',
          ...options.headers
        }
      });
      
      // Log rate limit info
      const remaining = response.headers.get('X-Ratelimit-Remaining');
      if (remaining !== null) {
        this.logger.debug('Rate Limit', {
          remaining,
          limit: response.headers.get('X-Ratelimit-Limit'),
          reset: response.headers.get('X-Ratelimit-Reset')
        });
      }
      
      if (!response.ok) {
        const error = await response.json();
        
        this.logger.error('API Error', {
          status: response.status,
          error: error.error,
          description: error.description,
          url,
          timestamp: new Date().toISOString()
        });
        
        throw new Error(error.description);
      }
      
      this.logger.info('API Success', {
        status: response.status,
        url
      });
      
      return response.json();
      
    } catch (error) {
      this.logger.error('Request Failed', {
        error: error.message,
        url,
        timestamp: new Date().toISOString()
      });
      throw error;
    }
  }
}
```

### 4. Provide User-Friendly Error Messages

```javascript theme={null}
function getUserFriendlyError(error) {
  const errorMessages = {
    'not_found': 'The requested item could not be found.',
    'unauthorized': 'Please sign in to continue.',
    'ratelimit_error': 'Too many requests. Please wait a moment and try again.',
    'conflict': 'This name is already taken. Please choose another.',
    'invalid_input': 'Please check your input and try again.',
    'internal_error': 'Something went wrong on our end. Please try again later.'
  };
  
  return errorMessages[error.type] || error.description;
}

// Usage in UI
try {
  await createProject(data);
} catch (error) {
  const message = getUserFriendlyError(error);
  showNotification(message, 'error');
}
```

## Debugging Tips

### Check Response Headers

Always inspect response headers for debugging information:

```bash theme={null}
curl -i "https://api.modrinth.com/v2/project/invalid-id"
```

```http theme={null}
HTTP/2 404
content-type: application/json
x-ratelimit-limit: 300
x-ratelimit-remaining: 299
x-ratelimit-reset: 60

{
  "error": "not_found",
  "description": "Resource not found"
}
```

### Verify Your Request

1. **Check the HTTP method**: Ensure you're using the correct method (GET, POST, etc.)
2. **Validate the URL**: Check for typos in the endpoint path
3. **Inspect headers**: Ensure required headers are present
4. **Verify request body**: Validate JSON syntax and required fields

### Test with cURL

Test your requests using cURL to isolate issues:

```bash theme={null}
# Test authentication
curl -H "Authorization: YOUR_TOKEN" \
  "https://api.modrinth.com/v2/user"

# Test with verbose output
curl -v "https://api.modrinth.com/v2/project/fabric-api"

# Test POST request
curl -X POST "https://api.modrinth.com/v2/project" \
  -H "Authorization: YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "My Project", "slug": "my-project"}'
```

### Common Mistakes

<AccordionGroup>
  <Accordion title="Missing User-Agent Header">
    **Error:** `Unable to obtain user IP address!` or blocked requests

    **Solution:** Always include a `User-Agent` header:

    ```javascript theme={null}
    headers: { 'User-Agent': 'my-app/1.0.0' }
    ```
  </Accordion>

  <Accordion title="Using Slug Instead of ID">
    **Error:** `invalid_input` or `decoding_error`

    **Solution:** Some endpoints only accept IDs, not slugs. Check the API documentation for the specific endpoint.
  </Accordion>

  <Accordion title="Incorrect Authorization Format">
    **Error:** `unauthorized`

    **Solution:** Use the token directly without "Bearer":

    ```javascript theme={null}
    // Correct
    headers: { 'Authorization': 'mrp_YOUR_TOKEN' }

    // Incorrect
    headers: { 'Authorization': 'Bearer mrp_YOUR_TOKEN' }
    ```
  </Accordion>

  <Accordion title="Missing Required Scopes">
    **Error:** `unauthorized` with message about missing scope

    **Solution:** Create a new token with the required scopes from your [account settings](https://modrinth.com/settings/account).
  </Accordion>
</AccordionGroup>

## Getting Help

If you're experiencing issues:

1. **Check the documentation**: Verify you're using the correct endpoint and parameters
2. **Search existing issues**: Look for similar problems on [GitHub](https://github.com/modrinth/labrinth/issues)
3. **Join the community**: Ask questions on [Modrinth's Discord](https://discord.modrinth.com)
4. **Contact support**: For persistent issues, email [support@modrinth.com](mailto:support@modrinth.com)

### What to Include in Bug Reports

When reporting API issues, include:

* **Request details**: Method, URL, headers (excluding tokens)
* **Response**: Status code, error message, response headers
* **Expected behavior**: What you expected to happen
* **Actual behavior**: What actually happened
* **Steps to reproduce**: Minimal code example
* **Environment**: Programming language, library versions

## Summary

<CardGroup cols={2}>
  <Card title="Consistent Format" icon="file-code">
    All errors return `error` and `description` fields in JSON format
  </Card>

  <Card title="Standard Codes" icon="hashtag">
    Uses HTTP status codes: 4xx for client errors, 5xx for server errors
  </Card>

  <Card title="Retry Logic" icon="rotate">
    Implement exponential backoff for server errors and rate limits
  </Card>

  <Card title="Helpful Messages" icon="message">
    Error descriptions provide actionable guidance for resolution
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="API Overview" icon="book" href="/api/overview">
    Learn about API basics and common patterns
  </Card>

  <Card title="Authentication" icon="key" href="/api/authentication">
    Fix authentication and authorization errors
  </Card>
</CardGroup>
