Skip to content

API Client

The Framedash API Client (@framedash/api-client) is a typed TypeScript/JavaScript client for the Framedash Developer Platform REST API. It is the same client that powers @framedash/cli and @framedash/mcp-server, published as a standalone package so you can integrate Framedash telemetry directly into your own in-house tools, dashboards, and automation.

It handles authentication, project-scoped paths, response unwrapping, and structured errors, plus a few security guardrails (HTTPS-only base URLs, no credential-leaking redirects, request timeouts), so you can focus on the data.

  • Node.js 18 or later (uses the global fetch, AbortSignal.timeout, and node:net)
  • An admin API key (fd_admin_ prefix) for Web API access
Terminal window
npm install @framedash/api-client
import { ApiClient, ApiError } from "@framedash/api-client";
const client = new ApiClient({
baseUrl: "https://app.framedash.dev",
apiKey: process.env.FRAMEDASH_API_KEY ?? "",
projectId: process.env.FRAMEDASH_PROJECT_ID ?? "",
onError: (err: ApiError) => {
// onError is called for any non-success response. It MUST throw (or exit);
// its return type is `never`. The thrown value propagates to your await.
throw err;
},
});
// Project-scoped GET -> /api/v1/projects/{projectId}/dashboard?days=30
const dashboard = await client.get(client.projectPath("dashboard?days=30"));
console.log(dashboard);

The client unwraps the API envelope automatically: a successful { "success": true, "data": ... } response resolves to just data.

The ApiClient constructor takes a single options object:

OptionTypeDescription
baseUrlstringApplication host URL, e.g. https://app.framedash.dev. Must be HTTPS (HTTP allowed only for localhost/loopback).
apiKeystringAdmin API key, sent as the X-API-Key header on every request.
projectIdstringDefault project UUID. Required for projectPath(); also sent as the X-Project-Id header for non-project paths.
onError(error: ApiError) => neverCalled on any non-success response. Must throw or exit the process.

The base URL is validated when the client is constructed; an insecure or malformed URL throws immediately. You can run the same check yourself with the exported assertSafeBaseUrl(baseUrl).

Four HTTP helpers cover the Web API. Each returns the unwrapped data payload, typed via the generic parameter:

const data = await client.get<MyType>("/api/v1/...");
await client.post("/api/v1/...", body);
await client.patch("/api/v1/...", body);
await client.delete("/api/v1/...");

Most endpoints are scoped to a project. projectPath(suffix) builds /api/v1/projects/{projectId}/{suffix} from the client’s configured projectId:

// GET /api/v1/projects/{projectId}/status
const status = await client.get(client.projectPath("status"));
// GET /api/v1/projects/{projectId}/retention?days=30
const retention = await client.get(client.projectPath("retention?days=30"));
// GET /api/v1/projects/{projectId}/maps
const maps = await client.get(client.projectPath("maps"));

Calling projectPath() without a configured projectId throws.

withProject(projectId) returns a new client bound to a different project, reusing the same base URL, API key, and error handler. The original client is unchanged:

const other = client.withProject("another-project-uuid");
const otherStatus = await other.get(other.projectPath("status"));
// The currently bound project ID:
console.log(client.currentProjectId);
const rows = await client.post("/api/v1/query", {
sql: "SELECT event_name, count() FROM events GROUP BY event_name",
project_id: client.currentProjectId,
limit: 100,
});
// List alert rules
const alerts = await client.get(client.projectPath("alerts"));
// Create an alert rule
const created = await client.post(client.projectPath("alerts"), {
name: "FPS Alert",
// ...remaining rule fields
});
// Deactivate an alert rule
await client.delete(client.projectPath(`alerts/${alertId}`));
// Content endpoints are not project-scoped in the path; the client adds the
// X-Project-Id header automatically from the configured projectId.
// Bulk upsert wraps the array in an "entries" property (max 500 per request).
await client.post("/api/v1/content", { entries });
const content = await client.get("/api/v1/content");

See the API reference for the full list of endpoints, query parameters, and payload shapes.

Any non-success response (network error, non-2xx status, or a body without success: true) is turned into an ApiError and passed to your onError callback. ApiError carries the parsed RFC 9457 Problem Details:

MemberTypeDescription
statusnumberHTTP status code.
headersHeadersResponse headers (e.g. X-RateLimit-Reset).
messagestringHuman-readable error message.
retryablebooleanWhether the API marked the error as retryable.
retryAfternumber | undefinedSuggested retry delay in seconds.
errorCategorystring | undefinedAPI error category.
problemProblemDetailsThe raw parsed Problem Details object.

A rate-limit-aware handler:

const client = new ApiClient({
baseUrl: "https://app.framedash.dev",
apiKey: process.env.FRAMEDASH_API_KEY ?? "",
projectId: process.env.FRAMEDASH_PROJECT_ID ?? "",
onError: (err: ApiError): never => {
if (err.status === 429) {
const retryAfter = err.retryAfter ?? err.headers.get("X-RateLimit-Reset");
throw new Error(`Rate limited; retry after ${retryAfter}`);
}
throw err;
},
});

The client enforces a few safeguards so an admin key is never leaked:

  • HTTPS-only base URLs. Plain HTTP is rejected except for localhost/loopback dev endpoints, and embedded credentials (https://...@host) are rejected.
  • No redirects. The API never redirects a programmatic JSON request, so any 3xx is treated as an error — the client never re-sends the X-API-Key header to a redirect target.
  • A 30-second request timeout per call (via AbortSignal.timeout).