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