# How Server Error Envelopes Are Structured and Validated in Instatic

> Learn how Instatic structures and validates server error envelopes using TypeBox and extracts human-readable messages with helpful utilities. Explore the code in apiClient.ts.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: internals
- Published: 2026-08-01

---

**Instatic wraps every failed HTTP response in a TypeBox-validated JSON envelope with an optional `error` field, extracting human-readable messages through `readEnvelope` and `responseErrorMessage` utilities in [`src/core/http/apiClient.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/http/apiClient.ts).**

Instatic implements a consistent, type-safe approach to HTTP error handling using **server error envelopes**—minimal JSON structures that standardize failure responses across the application. Every API client in the repository relies on a single TypeBox schema defined in [`src/core/http/apiClient.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/http/apiClient.ts) to parse and validate these envelopes. This design ensures that UI components receive predictable error messages while maintaining strict runtime type safety.

## Error Envelope Structure and Schema Definition

The canonical envelope shape follows a simple contract: an optional `error` property that may contain any JSON value, though typically a descriptive string. In [`src/core/http/apiClient.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/http/apiClient.ts), the **TypeBox** schema definition looks like this:

```typescript
const ErrorEnvelopeSchema = Type.Object({
  error: Type.Optional(Type.Unknown()),
})

```

This schema permits flexibility in error payloads while providing compile-time and runtime validation. The `Type.Optional` wrapper ensures that responses missing the error field still pass validation, allowing the system to gracefully handle malformed or unexpected server responses.

## The Validation Pipeline with readEnvelope

The core validation logic resides in the `readEnvelope` function, which clones the Response, parses the JSON body, and validates it against the supplied schema. If validation fails or the response is not OK, the utility throws an **ApiError** containing the extracted message.

The function signature follows this pattern:

```typescript
readEnvelope<T>(res: Response, Schema: TSchema, fallbackMessage: string): Promise<Static<T>>

```

When processing error responses, callers pass `ErrorEnvelopeSchema` to `readEnvelope`. The utility parses the body via `parseJsonResponse`, validates the structure against the TypeBox schema, and either returns the typed payload or throws an `ApiError` initialized with the fallback message.

## Extracting Human-Readable Messages

For UI-level error handling, Instatic provides `responseErrorMessage`, a specialized helper that guarantees a string output regardless of the envelope's contents. Located alongside `readEnvelope` in [`src/core/http/apiClient.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/http/apiClient.ts), this function implements the following fallback hierarchy:

- Extract the `error` property if present and valid
- Fall back to the raw response text body
- Use the provided fallback string if all else fails

This ensures that toast notifications and error boundaries always receive a usable string rather than undefined or complex objects.

## Practical Implementation Examples

The following patterns demonstrate how server error envelopes integrate into real Instatic workflows.

### Automatic Error Handling in API Requests

Higher-level utilities like `apiRequest` abstract envelope parsing entirely. When calling endpoints, developers supply a success schema while the client automatically handles error envelopes:

```typescript
import { apiRequest } from '@core/http'
import { UserSchema } from '@/src/core/persistence/cmsUsers'

async function loadCurrentUser() {
  // Returns Static<typeof UserSchema> on success
  // Throws ApiError with envelope message on HTTP error
  return await apiRequest('/admin/api/cms/user', {
    schema: UserSchema,
  })
}

```

### Manual Envelope Parsing

For low-level fetch operations, manually invoke `readEnvelope` and `responseErrorMessage`:

```typescript
import { readEnvelope, responseErrorMessage, ApiError } from '@core/http'

async function fetchWithRawError(url: string) {
  const res = await fetch(url, { credentials: 'include' })

  if (!res.ok) {
    const msg = await responseErrorMessage(res, `Request failed: ${res.status}`)
    throw new ApiError(msg, res.status)
  }

  return await readEnvelope(res, SomeSuccessSchema, 'Unable to decode response')
}

```

### Unit Testing Error Scenarios

The test suite in [`src/__tests__/http/apiClient.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/__tests__/http/apiClient.test.ts) validates envelope extraction and fallback behavior:

```typescript
import { jsonResponse, responseErrorMessage } from '@core/http'

test('responseErrorMessage extracts envelope error', async () => {
  const errRes = jsonResponse({ error: 'boom' }, 500)
  expect(await responseErrorMessage(errRes, 'fallback')).toBe('boom')
})

```

## Summary

- **Server error envelopes** in Instatic follow a minimal JSON structure with an optional `error` field defined by `ErrorEnvelopeSchema` in [`src/core/http/apiClient.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/http/apiClient.ts).
- **TypeBox validation** occurs through the `readEnvelope` utility, which parses responses and throws `ApiError` instances for invalid or failed requests.
- **Message extraction** is handled by `responseErrorMessage`, ensuring UI components always receive string-based error descriptions through a reliable fallback chain.
- **Test coverage** in [`src/__tests__/http/apiClient.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/__tests__/http/apiClient.test.ts) guarantees envelope parsing resilience against malformed JSON or missing bodies.

## Frequently Asked Questions

### What is the exact TypeBox schema for Instatic's server error envelopes?

The schema is defined in [`src/core/http/apiClient.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/http/apiClient.ts) as `Type.Object({ error: Type.Optional(Type.Unknown()) })`, allowing an optional `error` property containing any JSON value while maintaining runtime type safety through TypeBox validation.

### How does Instatic handle malformed error responses that don't match the envelope schema?

When `readEnvelope` encounters invalid JSON or a body missing the expected structure, it throws an `ApiError` initialized with the provided fallback message. The `responseErrorMessage` utility provides additional resilience by attempting to read raw response text before falling back to the default string.

### Where can I find real-world usage examples of error envelope handling?

Production implementations appear throughout `src/core/persistence/` (such as [`cmsMedia.ts`](https://github.com/CoreBunch/Instatic/blob/main/cmsMedia.ts)), where persistence layers call `readEnvelope` with domain-specific success schemas. The comprehensive test suite in [`src/__tests__/http/apiClient.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/__tests__/http/apiClient.test.ts) demonstrates edge cases and validation behavior.

### Is the server error envelope pattern documented outside the source code?

Yes, the architectural rationale and usage guidelines are documented in [`docs/reference/typebox-patterns.md`](https://github.com/CoreBunch/Instatic/blob/main/docs/reference/typebox-patterns.md) under the "Server error envelope" section, which explains the design decisions behind the optional `error` field and TypeBox integration.