@cmdash/api-sdk — CMDash Public API client (v1 + v2)
Official TypeScript client for the CMDash Public API — v1 under
client.<namespace>, and v2 (current — #575) under
client.v2.<namespace> (ROADMAP-V50 item 33). Zero dependencies — uses the
global fetch every supported Node runtime (18+) ships, same choice
packages/gate-cli already made.
Hand-written, not generated. No OpenAPI code-generator is a dependency
anywhere in this monorepo, and none may be added — so this is a small,
fully reviewed client for the documented surface instead of a generated
one. test/method-coverage.test.ts is what keeps it honest: it statically
parses every route out of the real public-*.controller.ts (v1) and
v2/public-*.controller.ts (v2) files and fails CI if a route exists with
no SDK method, or an SDK method exists for a route that's gone — for either
version. See src/index.ts's header for the full reasoning.
For what "v1" commits to (and what forced v2), see
docs/developers/public-api-versioning.md.
For the live, always-current interactive docs, see /api/v1/reference /
/api/v2/reference (ReDoc) on your CMDash instance.
Install
This package is not yet published to npm — there is no sdk-v* tag
yet (see .github/workflows/publish-sdk.yml, which publishes only when
one is cut), so npm i @cmdash/api-sdk does not resolve anything today.
Install from this repository (works today)
npm i github:bgovanlu/cmdash#path:packages/sdk-js does not work —
npm's path: fragment only resolves a subdirectory for a git dependency
that itself contains a package.json with no monorepo-relative build step,
and this one needs tsc run first. Pack a real tarball instead (this
repository is currently private, so cloning it requires repository
access):
git clone https://github.com/bgovanlu/cmdash.git
cd cmdash/packages/sdk-js
npm install
npm run build
npm pack # writes cmdash-api-sdk-<version>.tgz in this directory (the version in package.json)
# then, from the project that wants the SDK:
npm install /path/to/cmdash-api-sdk-<version>.tgz
When published
Once sdk-v* has a tag and .github/workflows/publish-sdk.yml has
pushed it to npm:
npm i @cmdash/api-sdk
Get an API key
Settings → API Keys → Create API Key, with whichever permission scopes the
calls you plan to make require (see
docs/developers/public-api-reference.md
for the scope each endpoint needs). Store it securely — never commit it.
Usage
import { CmdashClient } from '@cmdash/api-sdk';
const client = new CmdashClient({
apiKey: process.env.CMDASH_API_KEY!,
// baseUrl: 'https://app.cmdash.net/api', // default; override for on-prem
});
// The unprefixed namespace is API v2 — the current version.
const posture = await client.posture.getLatest();
console.log(posture?.score, posture?.grade);
const { risks, total } = await client.risks.list({ status: 'OPEN', limit: 20 });
const gate = await client.gate.evaluate();
if (!gate.pass) {
console.error('Posture gate failed:', gate.failures.map((f) => f.reason));
process.exitCode = 1;
}
// v2-only resources, same unprefixed namespace:
const { secret } = await client.webhooks.create({ url: 'https://example.com/hook', events: ['posture.changed'] });
// `client.v2.*` is the same object, spelled explicitly.
// `client.v1.*` is the deprecated version (Sunset: 24 Feb 2027).
Migrating to the v2 default
Before ROADMAP-V100 I4, client.<resource> was v1 and v2 lived at
client.v2.<resource>. It is now the other way round: the unprefixed
namespace is v2, and v1 moved — intact — to client.v1.<resource>.
-
Keep exactly the previous behaviour:
client.risks→client.v1.risks. A find-and-replace; nothing else changes. The client prints oneconsole.warnper instance the first time it issues a/v1/request, naming the sunset date; pass{ suppressDeprecationWarnings: true }once you have read it. -
Move to v2 instead (recommended): leave your call sites alone. On the five resources that exist in both versions (
posture,compliance,risks,integrations,gate) the request, the key scopes and the response shapes are identical, with exactly two exceptions — and TypeScript will point at both of them for you, because they are the only two places the return TYPE changed:call v1 v2 compliance.getFrameworkControls(unknownSlug)resolves { error: 'Framework not found' }, HTTP 200rejects CmdashApiError(404)integrations.getHealth(unknownId)resolves { error: 'Integration not found' }, HTTP 200rejects CmdashApiError(404)If your project still compiles after upgrading, there is no behavioural difference left to find.
client.v2.*keeps working and is the same object as the unprefixed name, so existing v2 call sites need no edit.
// v2 (current — prefer this for new integrations, see #575): const v2posture = await client.v2.posture.getLatest(); const { secret } = await client.v2.webhooks.create({ url: 'https://example.com/hook', events: ['posture.changed'] });
// ROADMAP-V100 I13 — verify a brand-new endpoint before any real event reaches it. await client.v2.webhooks.test(webhookId);
### Verifying an inbound webhook's signature (ROADMAP-V100 I12)
`verifyWebhookSignature` is a standalone export — it runs entirely on the
RECEIVING end, over a delivery your server got, not over a call this client
makes — so it is not a method on `CmdashClient`:
```ts
import { verifyWebhookSignature } from '@cmdash/api-sdk';
// req.rawBody MUST be the raw bytes received, not a re-serialized JSON.parse.
const ok = verifyWebhookSignature(
req.rawBody,
process.env.CMDASH_WEBHOOK_SECRET!,
req.headers['x-cmdash-signature'] as string,
);
if (!ok) return res.status(401).send('invalid signature');
Does the timing-safe compare and the replay-window check for you — do not
hand-roll this. See docs/webhook-events.md for the full wire contract.
Every method throws CmdashApiError (a real HTTP error response — status
code, error, message, requestId from the server's actual error envelope)
or CmdashClientError (couldn't reach the server, or got back something
that isn't the documented envelope shape) — never a silent undefined.
For CI pipelines specifically, prefer @cmdash/gate-cli (a purpose-built
CLI/GitHub Action over POST /v2/gate/evaluate) unless you need the result
inside a larger JS/TS program — client.gate.evaluate() here exists for
that latter case.
Coverage of the v1 and v2 surfaces
Every endpoint in docs/developers/public-api-reference.md (generated,
drift-checked — npm run docs:public-api:check) has a corresponding
method here, in both client.v1.<namespace> (v1) and client.<namespace>
/ client.v2.<namespace> (v2 — the same object under two names), verified
by test/method-coverage.test.ts. Two v1 endpoints resolve rather than
reject on a not-found ID (compliance.getFrameworkControls,
integrations.getHealth) — this mirrors real, current v1 server behavior
(HTTP 200 with an { error } body) rather than papering over it; their v2
counterparts reject with a real CmdashApiError (404) instead. See
docs/developers/public-api-versioning.md's "Error shapes" section. v2
also adds five resources with no v1 equivalent — client.webhooks,
client.reports, client.auditLogs, client.collector and
client.evidence.