Skip to main content

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.

The Errors & status codes 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

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

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;
  }
}

Validation errors — surfacing field messages

400 VALIDATION_ERROR returns per-field arrays of human-readable messages:
{
  "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:
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

StatuscodeRetry?How
400VALIDATION_ERRORNoFix the payload
400INVALID_IDNoUUID is malformed
401EXPIRED_API_KEYNoMint a fresh key
401INVALID_API_KEYNoKey was revoked
403INSUFFICIENT_SCOPENoRotate to a key with the required scope
404NOT_FOUNDNoThe resource doesn’t exist
409CONFLICTMaybeIdempotent create — fetch the existing record
429RATE_LIMIT_EXCEEDEDYesWait until X-RateLimit-Reset — see Rate-limit handling
5xxanyYesExponential 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:
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:
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