> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wiseyield.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Error handling

> Patterns for typed, recoverable error handling across the WiseYield API.

The [Errors & status codes](/concepts/errors) page documents the canonical response shape and every code the API returns. This guide is the practical companion — how to handle them in code without writing the same boilerplate at every call site.

## The minimum viable handler

```javascript theme={null}
async function callApi(url, init) {
  const res = await fetch(url, init);
  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    throw new WiseyieldApiError(res.status, body);
  }
  return res.json();
}

class WiseyieldApiError extends Error {
  constructor(status, body) {
    super(body.message || body.error || `HTTP ${status}`);
    this.status = status;
    this.code = body.code;
    this.details = body.details;
  }
}
```

Now every caller can pattern-match on `error.code` rather than parsing JSON ad-hoc.

## Typed handling by status class

<CodeGroup>
  ```javascript JavaScript theme={null}
  try {
    const farm = await callApi(url, init);
    // ...
  } catch (error) {
    if (!(error instanceof WiseyieldApiError)) throw error;

    switch (error.code) {
      case 'VALIDATION_ERROR':
        // error.details is { fieldName: [messages] }
        return renderFieldErrors(error.details);

      case 'INSUFFICIENT_SCOPE':
        // error.details.requiredScope tells you which scope to add
        return promptKeyRotation(error.details.requiredScope);

      case 'RATE_LIMIT_EXCEEDED':
        // Use X-RateLimit-Reset to schedule the retry
        return scheduleRetry();

      case 'NOT_FOUND':
        return null; // Treat as missing rather than failure

      case 'CONFLICT':
        // Idempotent create — fetch the existing resource
        return fetchExisting();

      default:
        // Unknown error — log and propagate
        logger.error('Unhandled WiseYield API error', error);
        throw error;
    }
  }
  ```

  ```python Python theme={null}
  try:
      farm = call_api(url, headers=headers)
  except WiseyieldApiError as error:
      if error.code == 'VALIDATION_ERROR':
          return render_field_errors(error.details)
      elif error.code == 'INSUFFICIENT_SCOPE':
          return prompt_key_rotation(error.details['requiredScope'])
      elif error.code == 'RATE_LIMIT_EXCEEDED':
          return schedule_retry()
      elif error.code == 'NOT_FOUND':
          return None
      elif error.code == 'CONFLICT':
          return fetch_existing()
      else:
          logger.error('Unhandled WiseYield API error', extra={'error': error})
          raise
  ```
</CodeGroup>

## Validation errors — surfacing field messages

`400 VALIDATION_ERROR` returns per-field arrays of human-readable messages:

```json theme={null}
{
  "error": "Validation failed",
  "code": "VALIDATION_ERROR",
  "details": {
    "name": ["Name is required"],
    "totalArea": ["Total area must be greater than 0"]
  }
}
```

Map them straight onto a form:

```javascript theme={null}
function applyFieldErrors(form, details) {
  for (const [field, messages] of Object.entries(details)) {
    form.setFieldError(field, messages[0]);
  }
}
```

Nested fields use dot-notation in the key (e.g. `boundaries.coordinates`).

## Retry semantics by status

| Status | `code`                | Retry? | How                                                                             |
| ------ | --------------------- | ------ | ------------------------------------------------------------------------------- |
| `400`  | `VALIDATION_ERROR`    | No     | Fix the payload                                                                 |
| `400`  | `INVALID_ID`          | No     | UUID is malformed                                                               |
| `401`  | `EXPIRED_API_KEY`     | No     | Mint a fresh key                                                                |
| `401`  | `INVALID_API_KEY`     | No     | Key was revoked                                                                 |
| `403`  | `INSUFFICIENT_SCOPE`  | No     | Rotate to a key with the required scope                                         |
| `404`  | `NOT_FOUND`           | No     | The resource doesn't exist                                                      |
| `409`  | `CONFLICT`            | Maybe  | Idempotent create — fetch the existing record                                   |
| `429`  | `RATE_LIMIT_EXCEEDED` | Yes    | Wait until `X-RateLimit-Reset` — see [Rate-limit handling](/guides/rate-limits) |
| `5xx`  | any                   | Yes    | Exponential backoff, max 3 attempts                                             |

## Idempotency on writes

Mutating endpoints accept an optional `Idempotency-Key` header (UUID) so a retry after a network blip doesn't create duplicate resources:

```http theme={null}
POST /api/v1/farms
Authorization: Bearer ...
Idempotency-Key: 8d1f4a2e-...

{ ... }
```

The server stores the result of the first request keyed on `(user_id, idempotency_key)` for 24 hours. Subsequent requests with the same key and a matching body return the cached response — different bodies for the same key return `409 CONFLICT`.

Use idempotency keys for:

* `POST` creates that you don't want to double-execute on retry
* Financial transactions (expenses, sales, payroll runs)
* Any state-changing call where retries are possible

## Logging recommendations

Capture all five fields of an API error in your logs:

```javascript theme={null}
logger.error('WiseYield API error', {
  status: error.status,
  code: error.code,
  message: error.message,
  details: error.details,
  // Plus the request context
  endpoint: req.url,
  method: req.method,
});
```

The `code` field is the most useful for grouping; the `details` field is the most useful for debugging individual incidents.

## See also

* [Errors & status codes](/concepts/errors) — complete reference of every code
* [Rate-limit handling](/guides/rate-limits) — backoff implementations for 429
* [Authentication](/authentication) — auth-specific error codes
