Skip to content

AdvancedRaw Data Mode

Raw Data Mode

Access response status codes and headers alongside the parsed data.

2 min readboth modes

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):

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

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

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 }:

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 — receive responses as ReadableStreams for SSE and large downloads
  • Options — all per-request options, stream and timeout among them
  • Middleware — intercept and transform requests with composable middleware