# API Response

> Discriminated union API response with success/error variants and factory functions.

- Name: `api-response`
- Categories: api
- Depends on: `@open-types/pagination`
- Detail page: https://open-types.dev/types/api-response

## Install

```bash
# Types only
npx shadcn add @open-types/api-response

# Types + Zod v4 validators
npx shadcn add @open-types/api-response-zod

# Types + real-world examples
npx shadcn add @open-types/api-response-examples
```

## Types

```typescript
import type { PaginationResponse } from "./pagination";

export interface ApiErrorItem {
  code: string;
  message: string;
  field?: string;
  details?: Record<string, unknown>;
}

export interface ApiSuccessResponse<T = unknown> {
  success: true;
  data: T;
  pagination?: PaginationResponse;
  meta?: Record<string, unknown>;
}

export interface ApiErrorResponse {
  success: false;
  errors: ApiErrorItem[];
}

export type ApiResponse = ApiSuccessResponse | ApiErrorResponse;
```

## Zod validator

```typescript
import * as z from "zod";
import type {
  ApiErrorItem,
  ApiSuccessResponse,
  ApiErrorResponse,
  ApiResponse,
} from "../types/api-response";
import { paginationResponseSchema } from "./pagination";

export const apiErrorItemSchema = z.object({
  code: z.string().min(1, { error: "Error code is required" }),
  message: z.string().min(1, { error: "Error message is required" }),
  field: z.string().optional(),
  details: z.record(z.string(), z.any()).optional(),
}) satisfies z.ZodType<ApiErrorItem>;

export const apiSuccessResponseSchema = z.object({
  success: z.literal(true),
  data: z.unknown(),
  pagination: paginationResponseSchema.optional(),
  meta: z.record(z.string(), z.any()).optional(),
}) satisfies z.ZodType<ApiSuccessResponse>;

export const apiErrorResponseSchema = z.object({
  success: z.literal(false),
  errors: z
    .array(apiErrorItemSchema)
    .min(1, { error: "At least one error is required" }),
}) satisfies z.ZodType<ApiErrorResponse>;

export const apiResponseSchema = z.discriminatedUnion("success", [
  apiSuccessResponseSchema,
  apiErrorResponseSchema,
]);

/**
 * Factory: create a typed success response schema for a specific data shape.
 */
export function createSuccessResponseSchema<T extends z.ZodType>(dataSchema: T) {
  return z.object({
    success: z.literal(true),
    data: dataSchema,
    pagination: paginationResponseSchema.optional(),
    meta: z.record(z.string(), z.any()).optional(),
  });
}

/**
 * Factory: create a standard error response schema.
 */
export function createErrorResponseSchema() {
  return apiErrorResponseSchema;
}

export type {
  ApiErrorItem,
  ApiSuccessResponse,
  ApiErrorResponse,
  ApiResponse,
} from "../types/api-response";
```

## Examples

```typescript
// Source: hand-crafted
import type {
  ApiErrorItem,
  ApiSuccessResponse,
  ApiErrorResponse,
  ApiResponse,
} from "../types/api-response";
import { paginationResponseExamples } from "./pagination.examples";

export const apiErrorItemExamples = {
  validation: {
    code: "validation_failed",
    message: "Email is required",
    field: "email",
  } satisfies ApiErrorItem,
  notFound: {
    code: "not_found",
    message: "Resource not found",
  } satisfies ApiErrorItem,
  withDetails: {
    code: "rate_limited",
    message: "Too many requests",
    details: { retry_after: 60, limit: 100 },
  } satisfies ApiErrorItem,
} as const;

export const apiSuccessResponseExamples = {
  singleObject: {
    success: true,
    data: { id: "user_321", name: "Jenny Rosen" },
  } satisfies ApiSuccessResponse,
  paginated: {
    success: true,
    data: [{ id: "1" }, { id: "2" }, { id: "3" }],
    pagination: paginationResponseExamples.middlePage,
  } satisfies ApiSuccessResponse,
  withMeta: {
    success: true,
    data: { value: 42 },
    meta: { requestId: "req_abc123", duration_ms: 12 },
  } satisfies ApiSuccessResponse,
  nullishData: { success: true, data: null } satisfies ApiSuccessResponse,
} as const;

export const apiErrorResponseExamples = {
  validation: {
    success: false,
    errors: [apiErrorItemExamples.validation],
  } satisfies ApiErrorResponse,
  multipleErrors: {
    success: false,
    errors: [
      apiErrorItemExamples.validation,
      { code: "validation_failed", message: "Password is too short", field: "password" },
    ],
  } satisfies ApiErrorResponse,
  rateLimited: {
    success: false,
    errors: [apiErrorItemExamples.withDetails],
  } satisfies ApiErrorResponse,
} as const;

export const apiResponseExamples = {
  success: apiSuccessResponseExamples.singleObject satisfies ApiResponse,
  paginatedSuccess: apiSuccessResponseExamples.paginated satisfies ApiResponse,
  validationError: apiErrorResponseExamples.validation satisfies ApiResponse,
  rateLimited: apiErrorResponseExamples.rateLimited satisfies ApiResponse,
} as const;
```
