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.
Requirements
Section titled “Requirements”- Node.js 18 or later (uses the global
fetch,AbortSignal.timeout, andnode:net) - An admin API key (
fd_admin_prefix) for Web API access
Installation
Section titled “Installation”npm install @framedash/api-clientQuick Start
Section titled “Quick Start”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=30const 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.
Configuration
Section titled “Configuration”The ApiClient constructor takes a single options object:
| Option | Type | Description |
|---|---|---|
baseUrl | string | Application host URL, e.g. https://app.framedash.dev. Must be HTTPS (HTTP allowed only for localhost/loopback). |
apiKey | string | Admin API key, sent as the X-API-Key header on every request. |
projectId | string | Default project UUID. Required for projectPath(); also sent as the X-Project-Id header for non-project paths. |
onError | (error: ApiError) => never | Called 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).
Making Requests
Section titled “Making Requests”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/...");Project-scoped paths
Section titled “Project-scoped paths”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}/statusconst status = await client.get(client.projectPath("status"));
// GET /api/v1/projects/{projectId}/retention?days=30const retention = await client.get(client.projectPath("retention?days=30"));
// GET /api/v1/projects/{projectId}/mapsconst maps = await client.get(client.projectPath("maps"));Calling projectPath() without a configured projectId throws.
Switching project context
Section titled “Switching project context”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);Examples
Section titled “Examples”Run a SQL query
Section titled “Run a SQL query”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,});Manage alerts
Section titled “Manage alerts”// List alert rulesconst alerts = await client.get(client.projectPath("alerts"));
// Create an alert ruleconst created = await client.post(client.projectPath("alerts"), { name: "FPS Alert", // ...remaining rule fields});
// Deactivate an alert ruleawait client.delete(client.projectPath(`alerts/${alertId}`));Import content registry entries
Section titled “Import content registry entries”// 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.
Error Handling
Section titled “Error Handling”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:
| Member | Type | Description |
|---|---|---|
status | number | HTTP status code. |
headers | Headers | Response headers (e.g. X-RateLimit-Reset). |
message | string | Human-readable error message. |
retryable | boolean | Whether the API marked the error as retryable. |
retryAfter | number | undefined | Suggested retry delay in seconds. |
errorCategory | string | undefined | API error category. |
problem | ProblemDetails | The 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; },});Security Behavior
Section titled “Security Behavior”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-Keyheader to a redirect target. - A 30-second request timeout per call (via
AbortSignal.timeout).
Next Steps
Section titled “Next Steps”- API Reference — full REST endpoint details
- CLI Reference — the command-line tool built on this client
- MCP Server — LLM access built on this client