---
title: Errors
path: core-concepts/errors
status: published
---

The HTTP status code carries the primary signal. The response body shape depends on the kind of failure: regular errors return `{"detail": "<message>"}`, request-validation failures return FastAPI's structured `detail` array.

## Regular errors

Anything raised through the normal exception path — auth rejected, permission denied, resource not found, conflict, rate-limit, server error — comes back as:

```json
{"detail": "Permission denied: WRITE on file/fil_01J3M"}
```

The HTTP status code tells you the class. The `detail` string is human-readable; surface it in UIs or logs but don't parse it.

| Status | Class | When |
|--------|-------|------|
| 400 | Bad request | Malformed path or input that's not a schema-level validation failure |
| 401 | Unauthorized | Missing, invalid, expired, or wrong-issuer token |
| 403 | Forbidden | Authenticated but lacking permission; user suspended; ownership-only action |
| 404 | Not found | Resource doesn't exist, or isn't visible to the caller — we don't distinguish |
| 409 | Conflict | Name collision, version mismatch, already-in-state operations (e.g., invitation already used) |
| 410 | Gone | Soft-deleted resource; expired invitation, link, or upload session |
| 413 | Payload too large | Upload exceeds tenant's per-file size cap |
| 422 | Unprocessable | Pydantic schema validation failed — see next section |
| 429 | Rate limited | Honor `Retry-After` header where present |
| 500 | Internal | Unexpected server failure |
| 502 | Bad gateway | Upstream service unreachable (ScaiKey, S3, Weaviate, SMB/SharePoint connector source) |
| 503 | Service unavailable | Dependency drained or degraded; readiness probe failing |
| 504 | Gateway timeout | Upstream timed out |
| 507 | Insufficient storage | A quota would be exceeded — share, user, group, tenant, or partner level |

## Validation errors (422)

When a request body fails Pydantic validation, FastAPI returns its standard structured error with a list of issues. `detail` is an array, not a string:

```json
{
  "detail": [
    {
      "loc": ["body", "role"],
      "msg": "Input should be 'owner', 'admin', 'contributor' or 'reader'",
      "type": "enum"
    },
    {
      "loc": ["body", "expires_in_days"],
      "msg": "Input should be less than or equal to 365",
      "type": "less_than_equal"
    }
  ]
}
```

Each entry has:

- `loc` — a path from the request root to the failing field (e.g., `["body", "role"]`, `["query", "limit"]`)
- `msg` — human-readable description
- `type` — the validator kind that fired (`enum`, `missing`, `string_too_short`, etc.)

Parse this on the client side to render field-level error messages.

## Detecting the shape

`detail` can be either a string or an array. The two shapes never appear together. Practical client code:

```python
import httpx

resp = httpx.post(url, json=payload, headers={"Authorization": f"Bearer {token}"})
if resp.status_code >= 400:
    body = resp.json()
    detail = body.get("detail")
    if isinstance(detail, list):
        # Validation errors — show field-by-field
        for issue in detail:
            field = ".".join(str(x) for x in issue["loc"])
            print(f"{field}: {issue['msg']}")
    else:
        # Regular error — single message
        print(f"HTTP {resp.status_code}: {detail}")
```

```typescript
const resp = await fetch(url, { method: "POST", body: JSON.stringify(payload), headers });
if (!resp.ok) {
  const body = await resp.json();
  if (Array.isArray(body.detail)) {
    for (const issue of body.detail) {
      const field = issue.loc.join(".");
      console.error(`${field}: ${issue.msg}`);
    }
  } else {
    console.error(`HTTP ${resp.status}: ${body.detail}`);
  }
}
```

## Retry strategy

| Status | Retry? | Notes |
|--------|--------|-------|
| 401 | No | Refresh the token if `AUTH_TOKEN_EXPIRED`; otherwise fix credentials |
| 403 | No | User lacks permission — no amount of retrying will fix it |
| 404 | No | Resource doesn't exist |
| 409 | Maybe | Re-read current state, resolve, retry once. For sync conflicts, use the conflict-resolution API |
| 410 | No | Restore the resource or create a new one |
| 413 | No | Use [resumable uploads](/docs/scaidrive/advanced/resumable-uploads) for large files |
| 422 | No | Fix the request body |
| 429 | Yes | Honor `Retry-After`; exponential backoff with jitter |
| 500 | Once | Transient. Don't retry destructive operations without idempotency |
| 502, 503, 504 | Yes | Upstream issue; backoff and retry |
| 507 | No | Quota exceeded; free space or raise the quota |

## Streaming errors

For file downloads, an error after response headers are sent produces a truncated body — your client must verify the full `Content-Length` (or the file's `checksum_sha256`) before treating a stream as complete.

For WebSocket connections, errors typically arrive as connection-close codes. The custom close codes ScaiDrive uses:

| Code | Meaning |
|------|---------|
| 4401 | Auth token invalid or missing |
| 4403 | User suspended or no permission to subscribe |
| 4429 | Connection limit hit (per-token or per-tenant) |

Standard WebSocket codes (`1000`, `1001`, `1006`, ...) carry their usual meaning.

## Known gaps

Two things this API doesn't have yet, worth knowing about:

- **No machine-readable error codes.** The `detail` string is the only error identifier. If you're building robust error handling, branch on HTTP status code + the operation you were performing, not on substring matching the message.
- **No request ID propagation.** Responses don't include an `X-Request-Id` header or a body-level request identifier today. When filing support tickets, include the request timestamp, status code, full URL, and the error message — that's the highest-fidelity correlation we can offer right now.

Both are tracked as planned improvements; this page will be updated when they ship.

## What's next

- [Rate Limiting](/docs/scaidrive/advanced/rate-limiting) — how 429s are produced and how to back off.
- [Troubleshooting](/docs/scaidrive/operations/troubleshooting) — what to check when specific status codes appear.