# Raw Data Mode

Access response status codes and headers alongside the parsed data.

## Overview

By default, Courier returns only the parsed response body (JSON or text). When you need to inspect the HTTP status code or read response headers, call the endpoint through the group's `.raw` accessor.

Raw data mode changes the return type from `Promise<T>` to `Promise<HttpResponse<T>>`:

```ts
// Normal mode (default) — returns just the data
const users = await caller.users.getAll()
// users = [{ id: 1, name: "Jane" }, ...]

// Raw data mode — returns status, headers, and data
const response = await caller.users.raw.getAll()
// response = {
//   status: 200,
//   headers: Headers { "content-type": "application/json", ... },
//   data: [{ id: 1, name: "Jane" }, ...],
// }
```

## Enabling raw data mode

Use the `.raw` namespace on any group to access raw data mode. This is per-request, not global, so you can use it only where you need the extra metadata:

> **Per-request option**
>
> `raw` is accessed via the dot-notation namespace `caller.{group}.raw.{method}()`. You use it on individual requests where you need status or headers.

## Checking status codes

Use raw data mode when your API uses different status codes to communicate different outcomes. For example, distinguishing between "created" (201) and "already exists" (200):

```ts title="lib/api-helpers.ts"
import { caller } from "@/lib/caller"

async function createUser(data: { name: string; email: string }) {
  const response = await caller.users.raw.create(data)

  if (response.status === 201) {
    console.log("User created:", response.data)
    return { created: true, user: response.data }
  }

  if (response.status === 200) {
    console.log("User already existed:", response.data)
    return { created: false, user: response.data }
  }

  return { created: false, user: null }
}
```

## Reading response headers

Many APIs include useful metadata in response headers. Raw data mode gives you access to the full `Headers` object:

### Pagination headers

```ts title="lib/api-helpers.ts"
import { caller } from "@/lib/caller"

async function fetchPaginatedUsers(page: number) {
  const response = await caller.users.raw.getAll({
    page,
    limit: 20,
  })

  // Read pagination info from response headers
  const totalCount = Number(response.headers?.get("X-Total-Count") ?? 0)
  const linkHeader = response.headers?.get("Link") ?? ""

  return {
    users: response.data,
    total: totalCount,
    hasMore: page * 20 < totalCount,
  }
}
```

### Rate-limit headers

```ts title="lib/api-helpers.ts"
import { caller } from "@/lib/caller"

async function fetchUsersWithRateLimit(): Promise<{
  data: User[]
  rateLimited: boolean
}> {
  const response = await caller.users.raw.getAll()

  const remaining = Number(response.headers?.get("X-RateLimit-Remaining") ?? -1)
  const resetAt = response.headers?.get("X-RateLimit-Reset")

  if (remaining <= 5 && remaining >= 0) {
    console.warn(`Rate limit warning: ${remaining} requests remaining, resets at ${resetAt}`)
  }

  return {
    data: response.data,
    rateLimited: remaining === 0,
  }
}
```

## Raw data in direct mode

Raw data mode works identically in both proxy and direct modes. The response shape is always `HttpResponse<T>` — `{ status, headers, data }`:

```ts title="app/dashboard/page.tsx"
import { caller } from "@/lib/caller"

export default async function DashboardPage() {
  const response = await caller.stats.raw.overview()

  // Same shape: { status, headers, data }
  const cacheStatus = response.headers?.get("X-Cache")

  return (
    <div>
      <p>Cache: {cacheStatus}</p>
      <pre>{JSON.stringify(response.data, null, 2)}</pre>
    </div>
  )
}
```

> **Which headers arrive**
>
> A direct call's `.raw` carries every header the upstream sent, cookies included. Through the proxy the browser receives the headers that describe the answer — `age`, `cache-control`, `content-disposition`, `content-language`, `etag`, `expires`, `last-modified`, `retry-after`, `vary`, the `x-ratelimit-*` trio and `x-request-id` — and the ones the source or the endpoint names, such as `X-Total-Count`. Cookies the upstream set arrive only where `set-cookie` is named.

> **Type inference with .raw**
>
> When using the `.raw` namespace, TypeScript automatically infers the return type as `Promise<HttpResponse<T>>` instead of `Promise<T>`. The `data` field inside the response retains the original endpoint's response type.

## Next steps

- [Streaming](/docs/advanced/streaming) — receive responses as ReadableStreams for SSE and large downloads
- [Options](/docs/advanced/options) — all per-request options, stream and timeout among them
- [Middleware](/docs/advanced/middleware) — intercept and transform requests with composable middleware
