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

# Error Handling

> How to handle errors, retries, and rate limits when integrating with the 9Squid API.

Every response from the 9Squid API follows the same envelope, whether it succeeds or fails. This page explains what each error looks like and how to handle it correctly.

***

## Error Response Format

```json theme={null}
{
  "success": false,
  "message": "Descriptive error message",
  "statusCode": 400
}
```

The `message` field is human-readable and safe to surface in logs. Never rely on its exact wording in code — use `statusCode` for branching logic.

***

## HTTP Status Codes

| Code  | Name                  | When it happens                                                              |
| ----- | --------------------- | ---------------------------------------------------------------------------- |
| `400` | Bad Request           | Missing or invalid fields in the request body                                |
| `401` | Unauthorized          | Missing, expired, or malformed Bearer token                                  |
| `403` | Forbidden             | Your token is valid but your role cannot access this endpoint                |
| `404` | Not Found             | The resource (deal, loan, subscription) does not exist                       |
| `409` | Conflict              | A duplicate resource already exists (e.g. a `DRAFT` deal for this loan type) |
| `413` | Payload Too Large     | Uploaded file exceeds the size limit                                         |
| `422` | Unprocessable Entity  | Request is well-formed but semantically invalid                              |
| `429` | Too Many Requests     | Rate limit exceeded — see below                                              |
| `500` | Internal Server Error | Unexpected platform error — contact support                                  |

***

## Common Errors and Fixes

### 401 Unauthorized

```json theme={null}
{ "success": false, "message": "Unauthorized", "statusCode": 401 }
```

**Cause:** The `Authorization` header is missing, the token has expired, or it is malformed.

**Fix:** Ensure every request includes `Authorization: Bearer <your_token>`. Contact [support@9squid.com](mailto:support@9squid.com) if your token has expired.

***

### 403 Forbidden

```json theme={null}
{ "success": false, "message": "Forbidden", "statusCode": 403 }
```

**Cause:** Your token is valid but your role (Originator or Investor) does not have access to this endpoint.

**Fix:** Check the [Role-Based Access](/getting-started#role-based-access) table. If you believe you should have access, contact support.

***

### 409 Conflict — Draft Deal Already Exists

```json theme={null}
{
  "success": false,
  "message": "A DRAFT deal already exists for this loan type",
  "statusCode": 409
}
```

**Fix:** Call `PATCH /originator/loans/:dealId` to regenerate the upload URL for the existing draft, or `DELETE` it and start fresh.

***

### 413 Payload Too Large

**Fix:** Ensure your loan tape file is under the platform's size limit. For large datasets use the [Bulk Loan Upload](/workflows/bulk-loan-upload) workflow which handles chunked multi-deal submission.

***

### 429 Too Many Requests

```json theme={null}
{ "success": false, "message": "Too many requests", "statusCode": 429 }
```

The 9Squid API enforces a rate limit of **100 requests per 60 seconds** per token. Exceeding this returns a `429` with a `Retry-After` header indicating how many seconds to wait.

```http theme={null}
Retry-After: 30
x-ratelimit-limit: 100
x-ratelimit-remaining: 0
x-ratelimit-reset: 56
```

| Header                  | Description                                 |
| ----------------------- | ------------------------------------------- |
| `x-ratelimit-limit`     | Total requests allowed per 60-second window |
| `x-ratelimit-remaining` | Requests remaining in the current window    |
| `x-ratelimit-reset`     | Seconds until the current window resets     |
| `Retry-After`           | Seconds to wait before retrying             |

**Fix:** Back off for the duration specified in `Retry-After` before retrying. See the retry strategy below.

***

## Retry Strategy

Not all errors are worth retrying. Follow this decision tree:

| Status          | Retry? | Strategy                                     |
| --------------- | ------ | -------------------------------------------- |
| `400`           | No     | Fix the request — retrying will keep failing |
| `401`           | No     | Refresh your token first                     |
| `403`           | No     | Role issue — retrying won't help             |
| `404`           | No     | Resource doesn't exist                       |
| `409`           | No     | Handle the conflict explicitly               |
| `429`           | Yes    | Wait for `Retry-After`, then retry           |
| `500`           | Yes    | Exponential backoff, max 3 attempts          |
| Network timeout | Yes    | Exponential backoff                          |

### Exponential Backoff Example

```javascript theme={null}
async function requestWithRetry(fn, maxAttempts = 3) {
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      const retryable = [429, 500, 502, 503, 504].includes(err.statusCode);
      if (!retryable || attempt === maxAttempts) throw err;

      const delay = Math.min(1000 * 2 ** attempt, 30000); // cap at 30s
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}
```

***

## Validating Before You Send

Most `400` errors can be avoided by validating locally first:

* **Loan type** must be one of: `Auto`, `Personal`, `Mortgage`, `Student`, `Business`, `Credit Card`
* **File format** must be `text/csv` or `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet`
* **Required fields** are documented per endpoint in the [API Reference](/api-reference/originator-loans/loanscontroller_initiateloan)
* **deal\_id** and **file\_name** in the complete step must exactly match what was returned in the initiate step

***

## Support

Before reporting an error, check **[status.9squid.com](https://status.9squid.com)** to see if it corresponds to an ongoing incident.

If you encounter a `500` error or an undocumented error message, contact [support@9squid.com](mailto:support@9squid.com) with:

* The full request (endpoint, headers, body — redact your token)
* The full response body
* A timestamp of when the error occurred
