# Errors & Troubleshooting

When something goes wrong, the FairePlace API returns structured error responses with enough detail to diagnose and fix the issue.

## Error response format

Every error follows this structure:

```json
{
  "error": {
    "code": 422,
    "type": "VALIDATION_ERROR",
    "message": "Invalid lease data",
    "details": {
      "field": "rent_amount",
      "message": "Rent amount must be greater than 0"
    }
  },
  "meta": {
    "request_id": "req_7f3a8b2c-1d4e-5f60-a7b8-c9d0e1f23456",
    "timestamp": "2026-02-19T10:30:00Z"
  }
}
```

| Field | Description |
|-------|-------------|
| `error.code` | HTTP status code |
| `error.type` | Machine-readable error type (e.g., `VALIDATION_ERROR`, `NOT_FOUND`) |
| `error.message` | Human-readable description |
| `error.details` | Additional context (field names, constraints, etc.) |
| `meta.request_id` | Unique request identifier — include this when contacting support |
| `meta.timestamp` | When the error occurred |

## HTTP status codes

| Code | Type | When it happens |
|------|------|----------------|
| **400** | Bad Request | Malformed JSON, invalid UUID format, missing required fields |
| **401** | Unauthorized | Missing, expired, or invalid API key |
| **403** | Forbidden | Valid token but insufficient permissions |
| **404** | Not Found | Resource doesn't exist or belongs to another tenant |
| **409** | Conflict | Cannot delete resource with dependencies (e.g., place with estates) |
| **422** | Unprocessable Entity | Valid JSON but business rule violation (e.g., rent exceeds cap) |
| **429** | Too Many Requests | Rate limit exceeded |
| **500** | Internal Server Error | Unexpected server error — contact support with `request_id` |

## Common errors and fixes

### 400 — Invalid UUID format

```json
{
  "error": {
    "code": 400,
    "type": "BAD_REQUEST",
    "message": "Invalid UUID format for field 'estate_id'",
    "details": {
      "field": "estate_id",
      "value": "not-a-uuid"
    }
  }
}
```

**Fix:** Use a valid UUID v4 format: `550e8400-e29b-41d4-a716-446655440000`.

### 400 — Missing required field

```json
{
  "error": {
    "code": 400,
    "type": "VALIDATION_ERROR",
    "message": "Missing required fields",
    "details": [
      { "field": "name", "message": "Name is required" },
      { "field": "owner_id", "message": "Owner ID is required" }
    ]
  }
}
```

**Fix:** Check the API module documentation for required fields.

### 401 — Token expired

```json
{
  "error": {
    "code": 401,
    "type": "UNAUTHORIZED",
    "message": "Token expired"
  }
}
```

**Fix:** Generate a new API key from the FairePlace dashboard. See [Authentication](/quickstart/authentication) for details.

### 403 — Permission denied

```json
{
  "error": {
    "code": 403,
    "type": "FORBIDDEN",
    "message": "Permission 'leases:write' required"
  }
}
```

**Fix:** Your token is missing the required permission. See [Authentication](/quickstart/authentication) for the full permissions table.

### 404 — Resource not found

```json
{
  "error": {
    "code": 404,
    "type": "NOT_FOUND",
    "message": "Lease not found"
  }
}
```

**Common causes:**
- The UUID is wrong or has a typo
- The resource belongs to a different tenant (cross-tenant access returns 404, not 403)
- The resource was deleted

### 409 — Dependency conflict

```json
{
  "error": {
    "code": 409,
    "type": "CONFLICT",
    "message": "Cannot delete place with associated estates"
  }
}
```

**Fix:** Remove dependent resources first. For example, delete all estates in a place before deleting the place itself.

### 422 — Rent cap violation

```json
{
  "error": {
    "code": 422,
    "type": "COMPLIANCE_ERROR",
    "message": "Rent exceeds legal maximum for this area",
    "details": {
      "rule": "RentCap",
      "current_value": 28.00,
      "maximum_allowed": 25.50,
      "unit": "EUR/m2"
    }
  }
}
```

**Fix:** The rent per square meter exceeds the reference rent (loyer de reference majore) for the property's area. Lower the rent amount or check if a rent complement (complement de loyer) applies.

### 422 — Deposit limit exceeded

```json
{
  "error": {
    "code": 422,
    "type": "COMPLIANCE_ERROR",
    "message": "Deposit exceeds legal maximum",
    "details": {
      "rule": "DepositLimit",
      "current_value": 2000.00,
      "maximum_allowed": 950.00,
      "explanation": "Deposit cannot exceed 1 month rent for unfurnished residential leases"
    }
  }
}
```

**Fix:** French law limits the security deposit to 1 month's rent (excluding charges) for unfurnished leases, and 2 months for furnished leases.

### 422 — IRL revision violation

```json
{
  "error": {
    "code": 422,
    "type": "COMPLIANCE_ERROR",
    "message": "Rent revision exceeds IRL cap",
    "details": {
      "rule": "IRLRevisionCap",
      "previous_rent": 950.00,
      "proposed_rent": 1100.00,
      "max_allowed": 972.82,
      "irl_previous": 142.06,
      "irl_current": 145.47
    }
  }
}
```

**Fix:** Annual rent revisions are capped by the IRL index. Use `New rent = Previous rent × (New IRL / Previous IRL)`.

### 429 — Rate limit exceeded

```json
{
  "error": {
    "code": 429,
    "type": "RATE_LIMITED",
    "message": "Rate limit exceeded",
    "details": {
      "limit": 1000,
      "window": "1 hour",
      "retry_after": 847
    }
  }
}
```

**Fix:** Wait until the rate limit resets. Check these response headers:

| Header | Description |
|--------|-------------|
| `X-RateLimit-Limit` | Maximum requests per hour (1,000) |
| `X-RateLimit-Remaining` | Requests remaining in current window |
| `X-RateLimit-Reset` | Unix timestamp when the window resets |
| `Retry-After` | Seconds to wait before retrying (on 429 responses) |

### 409 — Active lease dependency

```json
{
  "error": {
    "code": 409,
    "type": "CONFLICT",
    "message": "Cannot delete lessee with active leases",
    "details": {
      "lessee_id": "...",
      "active_leases": 2
    }
  }
}
```

**Fix:** End or transfer all active leases for this tenant before deleting them.

## Common pitfalls

- **Expired API key** → 401: Generate a new key from the dashboard
- **Missing permission** → 403: Check which permission the endpoint requires
- **Rate limited** → 429: Implement exponential backoff with `Retry-After` header
- **Compliance violation** → 422 COMPLIANCE_ERROR: rent exceeds cap, deposit exceeds limit, IRL cap exceeded
- **Dependency conflict** → 409: Cannot delete resource with active dependencies

## Retry strategy

For transient errors (429, 500), use exponential backoff:

```javascript
async function apiCall(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status === 429) {
      const retryAfter = response.headers.get("Retry-After") || 60;
      await sleep(retryAfter * 1000);
      continue;
    }

    if (response.status >= 500) {
      await sleep(Math.pow(2, attempt) * 1000); // 1s, 2s, 4s
      continue;
    }

    return response;
  }
  throw new Error("Max retries exceeded");
}
```

**Do not retry** 400, 401, 403, 404, 409, or 422 errors — these require fixing the request.

### Python retry example

```python
import time
import requests

def api_call(url, headers, method="GET", json=None, max_retries=3):
    for attempt in range(max_retries):
        response = requests.request(method, url, headers=headers, json=json)

        if response.status_code == 429:
            retry_after = int(response.headers.get("Retry-After", 60))
            time.sleep(retry_after)
            continue

        if response.status_code >= 500:
            time.sleep(2 ** attempt)  # 1s, 2s, 4s
            continue

        response.raise_for_status()
        return response.json()

    raise Exception("Max retries exceeded")
```

### Which errors to retry

| Code | Retry? | Strategy |
|------|--------|----------|
| 429 | Yes | Wait for `Retry-After` header value |
| 500 | Yes | Exponential backoff (1s, 2s, 4s) |
| 400, 401, 403, 404, 409, 422 | No | Fix the request |

## Debugging checklist

When you get an unexpected error:

1. **Check the `request_id`** — Include it when contacting support
2. **Verify your API key** — Is it expired? Check the key status in your FairePlace dashboard under Paramètres > Développeur > Clés API
3. **Check the `tenant_id`** — Are you targeting the right organization?
4. **Validate UUIDs** — Ensure all IDs are valid UUID v4 format
5. **Review required fields** — Check the API module docs for the endpoint you're calling
6. **Check dependencies** — Does the parent resource exist? (e.g., does the `estate_id` exist before creating a lease?)
7. **Inspect the full error** — The `details` field often tells you exactly what to fix

## Known constraints

Things to be aware of when integrating:

| Constraint | Details |
|------------|---------|
| **Rate limits** | 1,000 req/hour on Starter plan. No burst pooling across keys. |
| **No batch endpoints** | Each resource must be created individually. No bulk create/update. |
| **UUID references only** | All cross-resource references use UUIDs. No slug or name-based lookups. |
| **Deletion is hard** | Resources with dependencies cannot be deleted. Remove children first. |
| **No soft delete** | Deleted resources are permanently removed. No trash or recovery. |
| **PDF generation is synchronous** | Large lease PDFs (20+ pages) may take 5-10 seconds. Plan for timeouts. |
| **Signature credits are non-refundable** | Credits are consumed at initiation, not completion. Cancelled/expired signatures are not refunded. |
| **France only** | Regulatory compliance covers French law only. No international support yet. |
| **No webhooks for all events** | Webhooks are currently limited to Stripe payment events. Signature status requires polling. |
| **No real-time updates** | Use polling for signature status. Recommended interval: every 30 seconds. |
