Skip to main content
The Raise API returns errors in the RFC 7807 ProblemDetails 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:
{
  "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:
FieldDescription
typeA URI reference identifying the problem type. Often a link to a spec defining the error. Can be null.
titleA short, human-readable summary of the problem type — like "Bad Request" or "Not Found".
statusThe HTTP status code as a number — matches the response’s actual status.
detailA specific, human-readable explanation of this particular occurrence. The most useful field for diagnostics.
instanceA 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:
{
  "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 four success/error status codes across the surface:
StatusMeaningCommon causes
200 OKRequest succeededDefault success response on most endpoints.
204 No ContentRequest succeeded with no response bodyReturned by some delete/cancel operations.
400 Bad RequestRequest was syntactically valid but rejectedValidation failure, missing required field, malformed value. The errors map (when present) identifies the field-level issues.
404 Not FoundThe requested resource doesn’t existWrong ID, deleted record, or typo in the path.
Documented 400 responses appear on most endpoints; 404 appears on resource-specific paths (/api/Donor/{donorId}, etc.).

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.
StatusWhy it happens despite not being in the spec
401 UnauthorizedThe spec requires Bearer authentication on every endpoint — missing or invalid auth produces 401, but this isn’t declared per-endpoint.
403 ForbiddenThe authenticated token doesn’t have permission for the requested operation (e.g., a read-only token attempting a write).
429 Too Many RequestsRate limiting. The spec doesn’t document rate limit responses but they occur under sustained load.
500 Internal Server ErrorUnexpected server-side errors. Always possible on any endpoint.
502, 503, 504Infrastructure-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.
Future updates to the Raise OpenAPI spec are expected to add explicit documentation for 401, 403, 429, and 500 responses across all endpoints. Until then, code defensively as if every endpoint could return these status codes — because it can.

Handling errors in code

The pattern: check the status code, parse the body if non-2xx, route by status class.
JavaScript
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 for the full pattern. The Raise-specific summary:
CategoryStatus codesRecovery
Success200, 204None needed
Permanent client error400, 403, 404, 422Don’t retry — the request itself needs to change
Persistent-recoverable401Don’t retry — the credential needs to be refreshed
Transient429, 500, 502, 503, 504, network errorsRetry 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
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
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:
ErrorCause
DNS resolution failureWrong hostname (typo in prod-api.raisedonors.com) or local DNS issue
Connection refusedNetwork firewall blocking outbound traffic, or running against a wrong host
TLS certificate validation failedA man-in-the-middle proxy intercepting the connection, or running against a self-signed-certificate host
TimeoutNetwork 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 for the retry pattern.

Where to go next

Rate Limits

The detail on 429 responses and how to avoid them.

Error Recovery Patterns

The retry strategy, dead-letter handling, and circuit breaker patterns for production integrations.

Authentication

The deeper coverage of 401 causes and remediation.

Pagination and Filtering

Other error patterns specific to paginated reads.
Last modified on May 21, 2026