---
title: "Errors & Limits"
description: "Understanding API errors, status codes, and plan-based resource limits, including the error response format, rate limits, and common 4xx causes."
---

# Errors & Limits

Understanding API errors, rate limits, and plan restrictions.

## Error Response Format

All errors follow a consistent format:

```json
{
  "statusCode": 400,
  "statusMessage": "Name is required",
  "message": "Name is required",
  "url": "/api/v1/minds",
  "error": true
}
```

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        statusCode
      </code>
    </td>
    
    <td>
      HTTP status code
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        statusMessage
      </code>
    </td>
    
    <td>
      Human-readable error description (set per-error by the handler — for validation errors this is the specific issue, e.g. <code>
        "Mind not found"
      </code>
      
       or <code>
        "Invalid Mind ID format"
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        message
      </code>
    </td>
    
    <td>
      Same content as <code>
        statusMessage
      </code>
      
       for v1 errors. Reserved for stack/extra context in <code>
        5xx
      </code>
      
       responses on debug builds.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        url
      </code>
    </td>
    
    <td>
      The request path (added by Nuxt H3)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        error
      </code>
    </td>
    
    <td>
      <code>
        true
      </code>
      
       for error responses (added by Nuxt H3)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        data
      </code>
    </td>
    
    <td>
      Optional. Present only when an error carries machine-readable detail (see below). Absent on most errors.
    </td>
  </tr>
</tbody>
</table>

> Always rely on `statusCode` for programmatic handling and `statusMessage` (or `message`) for the human-readable reason. The `url` and `error` fields are convenience metadata from the underlying framework.

### The `data` field

Some errors attach a `data` object with structured detail you can branch on instead of parsing `statusMessage`. Two shapes you will encounter:

- **data.code** — a stable machine-readable error code. Currently `"PLAN_LIMIT"` on plan-limit `403`s, alongside `limitType`, `currentPlan`, `limit`, and `current`. See **Plan Limits** below.
- **data.expectedNoise** — internal telemetry metadata, set to `true` on routine `401`s and on a few `404`s that are almost always crawler probes. It tells our error monitoring to drop the event so real faults are not buried. It is **not** part of the public contract: ignore it, and do not branch on it.

Treat any other `data` key as reserved. Unknown keys may be added at any time, so read the fields you care about rather than asserting on the whole object.

## HTTP Status Codes

### 2xx Success

<table>
<thead>
  <tr>
    <th>
      Code
    </th>
    
    <th>
      Status
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      200
    </td>
    
    <td>
      OK
    </td>
    
    <td>
      Request succeeded
    </td>
  </tr>
  
  <tr>
    <td>
      201
    </td>
    
    <td>
      Created
    </td>
    
    <td>
      Resource created successfully (e.g. <code>
        POST /minds
      </code>
      
      , <code>
        POST /minds/{id}/knowledge
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      202
    </td>
    
    <td>
      Accepted
    </td>
    
    <td>
      Request accepted for asynchronous processing (e.g. <code>
        POST /minds/{id}/knowledge
      </code>
      
       with <code>
        keywords
      </code>
      
      )
    </td>
  </tr>
  
  <tr>
    <td>
      204
    </td>
    
    <td>
      No Content
    </td>
    
    <td>
      Request succeeded, no response body (e.g. <code>
        DELETE /minds/{id}/knowledge/{itemId}
      </code>
      
      )
    </td>
  </tr>
</tbody>
</table>

### 4xx Client Errors

<table>
<thead>
  <tr>
    <th>
      Code
    </th>
    
    <th>
      Status
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      400
    </td>
    
    <td>
      Bad Request
    </td>
    
    <td>
      Invalid request parameters
    </td>
  </tr>
  
  <tr>
    <td>
      401
    </td>
    
    <td>
      Unauthorized
    </td>
    
    <td>
      Missing or invalid API key
    </td>
  </tr>
  
  <tr>
    <td>
      403
    </td>
    
    <td>
      Forbidden
    </td>
    
    <td>
      Access denied or plan limit reached
    </td>
  </tr>
  
  <tr>
    <td>
      404
    </td>
    
    <td>
      Not Found
    </td>
    
    <td>
      Resource doesn't exist
    </td>
  </tr>
  
  <tr>
    <td>
      415
    </td>
    
    <td>
      Unsupported Media Type
    </td>
    
    <td>
      Wrong Content-Type header
    </td>
  </tr>
  
  <tr>
    <td>
      429
    </td>
    
    <td>
      Too Many Requests
    </td>
    
    <td>
      Rate limit exceeded
    </td>
  </tr>
</tbody>
</table>

### 5xx Server Errors

<table>
<thead>
  <tr>
    <th>
      Code
    </th>
    
    <th>
      Status
    </th>
    
    <th>
      Description
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      500
    </td>
    
    <td>
      Internal Server Error
    </td>
    
    <td>
      Server-side error
    </td>
  </tr>
  
  <tr>
    <td>
      503
    </td>
    
    <td>
      Service Unavailable
    </td>
    
    <td>
      Service temporarily unavailable
    </td>
  </tr>
</tbody>
</table>

## Common Errors

### 400 Bad Request

**Missing Required Field:**

```json
{
  "statusCode": 400,
  "statusMessage": "Name is required"
}
```

**Invalid Input:**

```json
{
  "statusCode": 400,
  "statusMessage": "File too large: document.pdf (55.2MB). Maximum size is 50MB."
}
```

### 401 Unauthorized

**Missing API Key:**

```json
{
  "statusCode": 401,
  "statusMessage": "Unauthorized",
  "message": "Unauthorized",
  "url": "/api/v1/minds",
  "error": true,
  "data": {
    "expectedNoise": true
  }
}
```

The `data.expectedNoise` flag is internal telemetry metadata, not part of the contract — see **The data field** above.

**Solution:** Include the `Authorization` header:

```bash
-H "Authorization: Bearer minds_your_api_key"
```

### 403 Forbidden

**Plan Limit Reached:**

```json
{
  "statusCode": 403,
  "statusMessage": "Individual plan limit reached",
  "message": "Individual plan limit reached",
  "url": "/api/v1/minds",
  "error": true,
  "data": {
    "code": "PLAN_LIMIT",
    "limitType": "sparks",
    "currentPlan": "premium",
    "limit": 100,
    "current": 100
  }
}
```

Branch on `data.code === "PLAN_LIMIT"` and `data.limitType` rather than on the message text — see **Plan Limits** below.

**Access Denied:**

```json
{
  "statusCode": 403,
  "statusMessage": "Access denied"
}
```

### 404 Not Found

**Resource Doesn't Exist:**

```json
{
  "statusCode": 404,
  "statusMessage": "Mind not found"
}
```

### 415 Unsupported Media Type

**Wrong Content-Type:**

```json
{
  "statusCode": 415,
  "statusMessage": "Unsupported Content-Type. Use application/json for links or multipart/form-data for files"
}
```

**Solution:** Use the correct `Content-Type` header:

- `application/json` for JSON requests
- `multipart/form-data` for file uploads

### 429 Too Many Requests

**Rate Limit Exceeded:**

```json
{
  "statusCode": 429,
  "statusMessage": "Too many requests. Please try again later."
}
```

## Rate Limits

The v1 API enforces a per-account fixed-window limit. The default deployment limit is 300 requests per minute, but operators can tune it, so integrations must use response headers rather than hard-code that number:

- `RateLimit-Limit` — current per-minute ceiling
- `RateLimit-Remaining` — requests remaining in the active window
- `Retry-After` — seconds to wait after a `429`

The limiter is applied per application instance and may fail open during limiter infrastructure errors. Keep concurrency bounded even when no `429` is observed.

## Plan Limits

See the generated [plan-limit table](/docs/api/overview) for the current public defaults. Account-specific contract overrides may differ, so `data.limit` and `data.current` in an authenticated error response are authoritative for that request. The Individual plan is represented as `"premium"` in API payloads.

### Mind-limit example

**Error when limit reached:**

```json
{
  "statusCode": 403,
  "statusMessage": "Individual plan limit reached",
  "message": "Individual plan limit reached",
  "url": "/api/v1/minds",
  "error": true,
  "data": {
    "code": "PLAN_LIMIT",
    "limitType": "sparks",
    "currentPlan": "premium",
    "limit": 100,
    "current": 100
  }
}
```

### Reading a plan-limit error

Every plan-limit `403` carries the same `data` shape, so one handler covers all of them:

- **code** — always `"PLAN_LIMIT"` for this class of error.
- **limitType** — which allowance was exhausted. Values reachable from the v1 API are `"sparks"` (minds owned), `"flows"` (chats created, including panel sessions), `"messages"` (messages within one chat or panel), and `"groupMembers"` (minds in a single group).
- **currentPlan** — the caller's plan tier: `"free"`, `"premium"`, or `"team"`.
- **limit** — the allowance for that plan and limit type.
- **current** — the caller's usage at the time of the request.

`statusMessage` is human-readable and its wording may change; `code` and `limitType` are the stable contract.

### Knowledge Upload Limits

- **File Size:** Maximum 50MB per file (all plans)
- **Storage:** No explicit storage limits currently enforced

### API Key Limits

- **Maximum Keys:** No cap is currently enforced. Delete keys you no longer use rather than letting them accumulate.

## Best Practices

### Error Handling

**Always handle errors:**

```javascript
try {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
  });

  if (!response.ok) {
    const error = await response.json();
    console.error(`Error ${error.statusCode}: ${error.message}`);
    // Handle specific errors
    if (error.statusCode === 429) {
      // Implement retry logic
    }
  }

  const result = await response.json();
  return result;
} catch (error) {
  console.error('Network error:', error);
}
```

### Retry Logic

**Implement smart retries:**

- Retry on `429` (rate limit) and `5xx` errors
- Use exponential backoff
- Set maximum retry attempts
- Don't retry on `4xx` errors (except 429)

### Monitoring

**Track your usage:**

- Log rate limit headers
- Monitor error rates
- Set up alerts for recurring errors
- Track response times

### Upgrade When Needed

Upgrade your plan if you:

- Hit rate limits frequently
- Need more minds
- Require larger file uploads
- Want priority support

[View Plans](/settings?tab=subscription)

## Getting Help

### Check Status

Monitor our service status:

- [Minds service status](https://uptime.getminds.ai)
- Follow [@mindsai_co](https://x.com/mindsai_co) for updates

### Contact Support

If you experience:

- Persistent 500 errors
- Incorrect rate limiting
- Unexpected behavior

Contact us:

- Feedback form
- Email: [support@getminds.ai](mailto:support@getminds.ai)

### Review Documentation

- [API Overview](/docs/api/overview)
- [Authentication](/docs/api/authentication)
- [Minds API](/docs/api/minds)
- [Knowledge API](/docs/api/knowledge)
- [Chat API](/docs/api/chat)

## Status Codes Reference

Quick reference for all HTTP status codes:

```text
2xx Success
├─ 200 OK
└─ 201 Created

4xx Client Error
├─ 400 Bad Request
├─ 401 Unauthorized
├─ 403 Forbidden
├─ 404 Not Found
├─ 415 Unsupported Media Type
└─ 429 Too Many Requests

5xx Server Error
├─ 500 Internal Server Error
└─ 503 Service Unavailable
```
