Skip to main content

REST Client

The REST client provides typed HTTP requests to the Heimdall API.

apiRequest

Make an authenticated REST request. The first argument is always an ApiClientConfig — pass getApiConfig() from @/lib/api.

import { getApiConfig, apiRequest } from "@/lib/api";
import type { ApiResponse } from "@/lib/api";

const response: ApiResponse<User> = await apiRequest<User>(getApiConfig(), "/v1/users/123");

Parameters

ParameterTypeRequiredDescription
configApiClientConfigYesAPI client config (baseUrl, optional systemApiKey) — use getApiConfig()
endpointstringYesAPI endpoint (e.g., /v1/users/123)
optionsApiRequestOptionsNoRequest options

Options

interface ApiRequestOptions {
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
body?: unknown;
headers?: Record<string, string>;
accessToken?: string;
userAgent?: string;
clientIp?: string;
sourceService?: string;
system?: boolean;
anonymous?: boolean;
}
OptionTypeDefaultDescription
methodstring"GET"HTTP method
bodyunknown-Request body (auto-serialized to JSON)
headersRecord<string, string>-Additional headers
accessTokenstring-User Bearer token — user-scoped requests
userAgentstring-Original client User-Agent (forwarded as X-Original-User-Agent)
clientIpstring-Original client IP (forwarded as X-Forwarded-For)
sourceServicestring-Source service (sent as X-Source-Service)
systemboolean-Explicit opt-in to use the system API key (no user context)
anonymousboolean-Genuinely public / pre-auth call — sends NO auth header (mutually exclusive with system)
Fail-closed auth

apiRequest never silently falls back to the system key. Every call must set exactly one of accessToken, system: true, or anonymous: true; otherwise it throws AuthRequiredError (→ 401 AUTH_REQUIRED).

Response

interface ApiResponse<T> {
data?: T;
error?: string;
status: number;
}

Examples

GET Request

import { getApiConfig, apiRequest } from "@/lib/api";

const response = await apiRequest<UserProfile>(getApiConfig(), "/v1/users/123/profile", {
accessToken: session.accessToken,
});

if (response.error) {
console.error("Error:", response.error);
return;
}

console.log("User:", response.data);

POST Request

const response = await apiRequest<CreateUserResponse>(getApiConfig(), "/v1/users", {
method: "POST",
body: {
email: "user@example.com",
name: "John Doe",
},
system: true,
});

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

PUT Request with Auth Override

const response = await apiRequest<void>(getApiConfig(), "/v1/users/123", {
method: "PUT",
body: { name: "New Name" },
accessToken: userAccessToken,
});

DELETE Request

const response = await apiRequest<void>(getApiConfig(), "/v1/users/123", {
method: "DELETE",
});

if (response.status === 204) {
console.log("User deleted");
}

Error Handling

The client handles errors gracefully and returns them in the response:

const response = await apiRequest<User>(getApiConfig(), "/v1/users/invalid");

if (response.error) {
switch (response.status) {
case 400:
console.error("Bad request:", response.error);
break;
case 401:
console.error("Unauthorized");
break;
case 404:
console.error("User not found");
break;
case 500:
console.error("Server error:", response.error);
break;
}
}

Client Info & Audit Logging

The REST client also supports client info extraction for audit logging:

import { getApiConfig, apiRequest, extractClientInfo } from "@/lib/api";
import { NextRequest } from "next/server";

export async function POST(request: NextRequest) {
const clientInfo = extractClientInfo(request);

const response = await apiRequest<Result>(getApiConfig(), "/v1/auth/login", {
method: "POST",
body: { email, password },
...clientInfo, // Includes userAgent and clientIp
});
}

config.sourceService (from getApiConfig()) is sent as X-Source-Service on every request by default. Pass sourceService in the options only to override it for a single call (e.g. a route that must attribute to a different app than its own). The library itself still reads no SOURCE_SERVICE env var — that resolution happens in each app's src/lib/config.ts. See the GraphQL Client docs for details.

Pre-built Routes

The library includes pre-built route functions for common operations. Each takes the ApiClientConfig as its first argument:

import {
// Session routes
getActiveSessions,
revokeSession,
revokeAllOtherSessions,
// Audit routes
getMyAuditLog,
reportAuditEvent,
} from "@elcto/api";
import { getApiConfig } from "@/lib/api";

// Get active sessions (second arg is the current session token, not a userId)
const { sessions } = await getActiveSessions(getApiConfig(), currentToken);

// Get activity log
const auditPage = await getMyAuditLog(getApiConfig(), { page: 1, limit: 20 });

See the Pre-built Routes documentation for complete reference.