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

# Error Handling

> The Raise API's error response envelope, what each status code means, and how to handle errors defensively in your integration.

The Raise API returns errors in the [RFC 7807 ProblemDetails](https://datatracker.ietf.org/doc/html/rfc7807) format — the standard problem-details envelope used by ASP.NET Core. This page covers the envelope structure, the status codes documented in the spec, the ones that aren't documented but occur in practice, and the patterns for handling them in integration code.

## The ProblemDetails envelope

Every non-success response from the Raise API uses one of two envelope shapes — both based on RFC 7807. The base `ProblemDetails` envelope:

```json theme={null}
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "Bad Request",
  "status": 400,
  "detail": "The 'amount' field must be greater than zero.",
  "instance": "/api/Raise/give"
}
```

The five fields:

| Field      | Description                                                                                                  |
| ---------- | ------------------------------------------------------------------------------------------------------------ |
| `type`     | A URI reference identifying the problem type. Often a link to a spec defining the error. Can be `null`.      |
| `title`    | A short, human-readable summary of the problem type — like `"Bad Request"` or `"Not Found"`.                 |
| `status`   | The HTTP status code as a number — matches the response's actual status.                                     |
| `detail`   | A specific, human-readable explanation of this particular occurrence. The most useful field for diagnostics. |
| `instance` | A URI identifying this specific occurrence — often the request path that produced the error.                 |

### The validation variant

For validation errors (typically `400 Bad Request` on a write endpoint), the response uses `HttpValidationProblemDetails`, which extends `ProblemDetails` with a per-field error map:

```json theme={null}
{
  "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
  "title": "One or more validation errors occurred.",
  "status": 400,
  "errors": {
    "amount": ["The amount field is required.", "Amount must be greater than zero."],
    "donor.email": ["A valid email address is required."]
  }
}
```

The `errors` object is keyed by field path with arrays of error messages. Use it to surface field-specific feedback in your integration's UI rather than just showing the top-level `title`.

## Documented status codes

The Raise OpenAPI spec formally documents five success/error status codes across the surface:

| Status             | Meaning                                      | Common causes                                                                                                                   |
| ------------------ | -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `200 OK`           | Request succeeded                            | Default success response on most endpoints.                                                                                     |
| `204 No Content`   | Request succeeded with no response body      | Returned by some delete/cancel operations.                                                                                      |
| `400 Bad Request`  | Request was syntactically valid but rejected | Validation failure, missing required field, malformed value. The `errors` map (when present) identifies the field-level issues. |
| `401 Unauthorized` | The request wasn't authenticated             | Missing `Authorization` header, or a malformed, expired, or revoked token.                                                      |
| `404 Not Found`    | The requested resource doesn't exist         | Wrong ID, deleted record, or typo in the path.                                                                                  |

`401` is documented on every endpoint, since every endpoint requires Bearer authentication. Documented `400` responses appear on most endpoints; `404` appears on resource-specific paths (`/api/Donor/{donorId}`, etc.).

### What each endpoint's `400` and `404` mean

The generic "Bad Request" and "Not Found" labels are only the starting point. In the API reference, each endpoint's `400` and `404` responses describe what that particular endpoint rejects — which parameter or field was at fault, whether the stored record changed, and whether retrying can help. A few patterns worth knowing:

* **On writes,** a `400` means nothing was persisted. `Update a Campaign` leaves the stored campaign untouched; `Transfer a Donor Gift` leaves the gift with its current donor.
* **On deletes,** a `400` can mean the record still has dependencies. For campaigns, custom fields, and projects, read the dependency count first and reassign the dependent records with the matching replace endpoint.
* **On `Cancel a Recurring Gift`,** a `400` means the schedule exists but isn't cancellable — including when it's already cancelled — while a `404` means the ID doesn't resolve at all. Re-read the schedule rather than retrying.
* **On nested donor resources** (addresses, contact methods, emails, phone numbers), a `404` also covers the case where the child record exists but belongs to a different donor. The `detail` field names the record that was missing.
* **On existence checks** like `Check if Segment Code Exists`, a `400` means the query parameter itself was rejected — a code that simply isn't in use returns `200` with a negative result.

Fourteen endpoints — `Process a Donation Payment`, `Capture a Lead`, the `Query` endpoints, and a few others — document no error responses beyond `401`. They still return the ProblemDetails envelope; the spec just doesn't enumerate their failure cases, so treat `400` and `404` as possible there too.

## Status codes that occur but aren't in the spec

A few status codes aren't formally documented in the Raise spec but partner integrations will encounter them. Handle them defensively even though the spec is silent.

| Status                      | Why it happens despite not being in the spec                                                                              |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `403 Forbidden`             | The authenticated token doesn't have permission for the requested operation (e.g., a read-only token attempting a write). |
| `429 Too Many Requests`     | Rate limiting. The spec doesn't document rate limit responses but they occur under sustained load.                        |
| `500 Internal Server Error` | Unexpected server-side errors. Always possible on any endpoint.                                                           |
| `502`, `503`, `504`         | Infrastructure-layer issues — gateway errors, service unavailability, timeouts.                                           |

For each of these, the response body should still follow the `ProblemDetails` envelope — though the `detail` and `type` fields may be less informative than for documented errors.

<Note>
  `401` used to be in this list; it's now documented on every endpoint. Future updates to the spec are expected to add explicit documentation for `403`, `429`, and `500`. Until then, code defensively as if every endpoint could return these status codes — because it can.
</Note>

## Handling errors in code

The pattern: check the status code, parse the body if non-`2xx`, route by status class.

```javascript JavaScript theme={null}
async function callRaise(url, options = {}) {
  const response = await fetch(url, {
    ...options,
    headers: {
      Authorization: `Bearer ${process.env.RAISE_API_TOKEN}`,
      'Content-Type': 'application/json',
      ...options.headers,
    },
  });

  if (response.ok) {
    // 2xx — success. Parse and return the body if there is one.
    return response.status === 204 ? null : await response.json();
  }

  // Non-2xx — parse the ProblemDetails envelope
  let problem;
  try {
    problem = await response.json();
  } catch {
    // Some errors (network-layer issues, gateway errors) may not return JSON
    problem = { title: response.statusText };
  }

  const err = new Error(problem.detail || problem.title || 'Request failed');
  err.status = response.status;
  err.problem = problem;
  err.url = url;
  throw err;
}
```

Three things this gets right:

* **`204 No Content` is handled explicitly.** Some delete and cancel endpoints return `204` with no body — calling `response.json()` on an empty body throws. Detect `204` and return `null` instead.
* **The body is attempted to be parsed as JSON.** Most errors return JSON; some (gateway errors, network issues) may not. Fall back to the status text.
* **The error carries structured detail.** Downstream handlers can switch on `err.status` for retry decisions and surface `err.problem.detail` for user-facing messages.

### Routing by status class

For partner integrations doing retries, route errors by category — see [Error Recovery Patterns](/raise/best-practices/error-recovery-patterns#classifying-errors) for the full pattern. The Raise-specific summary:

| Category                   | Status codes                                      | Recovery                                           |
| -------------------------- | ------------------------------------------------- | -------------------------------------------------- |
| **Success**                | `200`, `204`                                      | None needed                                        |
| **Permanent client error** | `400`, `403`, `404`, `422`                        | Don't retry — the request itself needs to change   |
| **Persistent-recoverable** | `401`                                             | Don't retry — the credential needs to be refreshed |
| **Transient**              | `429`, `500`, `502`, `503`, `504`, network errors | Retry with exponential backoff                     |

Retrying a `400` won't make it succeed — the request body needs to change. Retrying a `401` won't make it succeed — the token needs to be replaced. Retrying a `500` usually will succeed within a few attempts.

## Validation errors in detail

When the spec documents `400 Bad Request` on a write endpoint, the most common cause is field-level validation. The response includes the `errors` map keyed by field name:

```javascript JavaScript theme={null}
async function submitDonation(donation) {
  try {
    return await callRaise('https://prod-api.raisedonors.com/api/Raise/give', {
      method: 'POST',
      body: JSON.stringify(donation),
    });
  } catch (err) {
    if (err.status === 400 && err.problem?.errors) {
      // Surface field-level errors to the user
      const fieldErrors = Object.entries(err.problem.errors).map(
        ([field, messages]) => `${field}: ${messages.join(', ')}`
      );
      throw new ValidationError('Donation rejected', fieldErrors);
    }
    throw err;
  }
}
```

For partner integrations exposing a UI to end users, surface the field-level messages directly — they're typically more helpful than generic "Submission failed" messages.

## What the `detail` field gives you

The `detail` field is the most useful diagnostic in the ProblemDetails envelope. Three patterns for using it well:

### Log it on every non-2xx

```javascript JavaScript theme={null}
console.error('Raise API error', {
  url: err.url,
  status: err.status,
  type: err.problem?.type,
  title: err.problem?.title,
  detail: err.problem?.detail,
});
```

This produces searchable log entries with enough context to investigate later — without leaking sensitive request body content.

### Surface it to your UI carefully

`detail` is human-readable but written for an API consumer (developer), not an end user. It may contain technical jargon, internal field names, or unhelpful messages like `"An error occurred while processing your request."` Sanitize before surfacing to non-technical users.

A reasonable pattern: use `errors` for field-level user-facing messages; use `detail` for logs and developer-facing error contexts only.

### Don't switch on it

The exact text of `detail` is not a stable contract. The platform may rephrase messages, translate them, or improve them. Code that parses or matches on `detail` strings is brittle — switch on `status` codes and structured `type` URIs instead.

## Network and TLS errors

Errors that occur before the request reaches Raise are network-layer issues — they don't produce a ProblemDetails response because the server never saw the request:

| Error                             | Cause                                                                                                    |
| --------------------------------- | -------------------------------------------------------------------------------------------------------- |
| DNS resolution failure            | Wrong hostname (typo in `prod-api.raisedonors.com`) or local DNS issue                                   |
| Connection refused                | Network firewall blocking outbound traffic, or running against a wrong host                              |
| TLS certificate validation failed | A man-in-the-middle proxy intercepting the connection, or running against a self-signed-certificate host |
| Timeout                           | Network latency, server-side slowness, or downstream overload                                            |

Most clients surface these as exceptions before any HTTP response is available. Handle them as transient errors with retry — see [Error Recovery Patterns](/raise/best-practices/error-recovery-patterns) for the retry pattern.

## Where to go next

<CardGroup cols={2}>
  <Card title="Rate Limits" icon="gauge" href="/raise/rate-limits">
    The detail on `429` responses and how to avoid them.
  </Card>

  <Card title="Error Recovery Patterns" icon="arrows-rotate" href="/raise/best-practices/error-recovery-patterns">
    The retry strategy, dead-letter handling, and circuit breaker patterns for production integrations.
  </Card>

  <Card title="Authentication" icon="key" href="/raise/authentication">
    The deeper coverage of `401` causes and remediation.
  </Card>

  <Card title="Pagination and Filtering" icon="list" href="/raise/pagination">
    Other error patterns specific to paginated reads.
  </Card>
</CardGroup>
