npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@maced/api-client

v0.9.1

Published

Typed TypeScript client for the Maced API (api.maced.ai).

Downloads

1,708

Readme

@maced/api-client

Typed TypeScript SDK for the Maced API.

npm types

⚠️ Backend only

Do not use this SDK in browsers, React Native, Electron, or any client that ships to end users. API keys (mc_live_* / mc_dev_*) are secrets: they trigger paid pentests and return private report content. Anyone who sees your bundle can read the key.

If you want to surface Maced data in a frontend, put a thin endpoint on your backend that calls @maced/api-client, and have your frontend call that endpoint. Standard BFF pattern:

React / Mobile  →  your backend (uses @maced/api-client)  →  api.maced.ai
     public                    private                          private

Same reason stripe is a separate SDK from @stripe/stripe-js.

  • Fully typed — every endpoint, param, response, and error
  • Ergonomicmaced.pentests.create(...) + full-verb escape hatch
  • Reliable — built-in retry, timeout, and request cancellation
  • Observable — pluggable onRequest / onResponse / onError hooks
  • Safe — throws MacedApiError with structured body + status helpers
  • Small — ~4kB packed, zero runtime deps beyond openapi-fetch
  • Works everywhere — Node 18+, Bun, Deno, Cloudflare Workers, browsers

Install

bun add @maced/api-client
# or
pnpm add @maced/api-client
# or
npm install @maced/api-client

Quickstart

import { createMacedClient } from "@maced/api-client";

const maced = createMacedClient({ apiKey: process.env.MACED_API_KEY! });

// Create a pentest
const run = await maced.pentests.create({
  targetUrl: "https://target.example.com",
});

// Wait for completion (polls /v1/pentests/:id until terminal)
const done = await maced.pentests.waitForCompletion(run.id, {
  onProgress: (r) =>
    console.log(`${r.progress.completedAgents}/${r.progress.totalAgents} agents`),
});

// Fetch the report
if (done.status === "completed") {
  const report = await maced.pentests.report(done.id);
  console.log(report.markdown);
}

Get an API key at https://maced.ai/settings/api.

API

Client factory

createMacedClient(options: MacedClientOptions): MacedClient

| option | type | default | notes | |--------------|-----------------------|-----------------------|-------| | apiKey | string required | — | mc_live_* or mc_dev_* | | baseUrl | string | https://api.maced.ai| Point at staging, a fork, a proxy | | timeoutMs | number | 30_000 | Per-request AbortSignal. 0 disables | | retry | RetryOptions | 3 attempts, 250ms base| 408/425/429/5xx with exp. backoff + jitter. Honors Retry-After | | logger | LoggingHooks | — | onRequest / onResponse / onError | | userAgent | string | — | Appended to the SDK User-Agent | | fetch | typeof fetch | globalThis.fetch | Custom transport / test override |

Pentests

maced.pentests.create(body)             // → PentestCreated
maced.pentests.list({ status?, limit? }) // → Pentest[]
maced.pentests.get(id)                  // → PentestWithProgress
maced.pentests.report(id)               // → PentestReport (markdown + cost + duration)
maced.pentests.events(id)               // → PentestEvent[] (live agent events)
maced.pentests.issues(id)               // → Issue[]
maced.pentests.progress(id)             // → PentestProgress
maced.pentests.cancel(id)               // → Pentest
maced.pentests.waitForCompletion(id, opts?) // polls until terminal
maced.pentests.streamEvents(id, opts?)       // AsyncIterable<PentestEvent>

Issues

maced.issues.list({ severity?, status?, pentestId? }) // → Issue[]
maced.issues.update(issueId, { status })              // → Issue

Domains

maced.domains.list()                    // → VerifiedDomain[]
maced.domains.verify(domain)            // → DomainVerificationChallenge
maced.domains.check(domain)             // → { verified: boolean }

Webhooks

maced.webhooks.getSecret()                             // → { secret }
verifyMacedWebhook(rawBody, signature, secret, opts?)  // → MacedWebhookEvent (throws on bad sig)

Usage

maced.usage.get()                       // → UsageSummary (calendar month-to-date, UTC)
maced.usage.get({ period: "week" })     // → last 7 days
maced.usage.get({ from: "...", to: "..." })  // → arbitrary ISO window

Escape hatch — raw typed fetch

const { data, error, response } = await maced.http.GET("/v1/pentests", {
  params: { query: { status: "running" } },
});

Returns an openapi-fetch { data, error, response } tuple — never throws. Use this when you want to inspect the raw Response, forward it, or prefer Result patterns over exceptions.

Error handling

Namespaced methods throw MacedApiError on non-2xx responses:

import { MacedApiError } from "@maced/api-client";

try {
  await maced.pentests.create({ targetUrl: "javascript:alert(1)" });
} catch (err) {
  if (err instanceof MacedApiError) {
    console.log(err.status);          // 400
    console.log(err.body);            // { error: "targetUrl must be http(s)" }
    console.log(err.method);          // "POST"
    console.log(err.endpoint);        // "/v1/pentests"
    if (err.isUnauthorized) { /* rotate key */ }
    if (err.isRateLimited)   { /* back off — SDK already retried with backoff */ }
    if (err.isServerError)   { /* alert ops */ }
  }
  throw err;
}

Recipes

Stream agent events live

for await (const event of maced.pentests.streamEvents(runId)) {
  console.log(`[${event.agent}] ${event.description ?? event.tool}`);
}
// Terminates automatically when the run reaches completed / failed / cancelled.

Built on polling + ID-based dedup. Each event yielded at most once. Cancel with an AbortSignal:

const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 5 * 60 * 1000);
for await (const event of maced.pentests.streamEvents(id, { signal: ctrl.signal })) {
  //
}

Idempotent pentest creation

If your caller might crash between receiving the run id and persisting it, pass an idempotencyKey so retries return the same run instead of creating duplicates. The server holds the replay window for 24h.

const run = await maced.pentests.create(
  { targetUrl: "https://target" },
  { idempotencyKey: crypto.randomUUID() },
);

Build your own UI from structured findings

Every issue ships with structured reproduction + attack-path data so you can render a custom report, build a triage UI, or feed a SIEM — no markdown parsing required:

const issues = await maced.pentests.issues(runId);

for (const i of issues) {
  console.log(`${i.severity.toUpperCase()} · ${i.title}`);
  console.log(`  CVSS: ${i.cvssScore ?? "—"} | CWE: ${i.cweId ?? "—"}`);
  console.log(`  Endpoint: ${i.affectedEndpoint ?? "—"}`);

  // Step-by-step repro, each marked verified by the agent
  for (const step of i.validationSteps ?? []) {
    console.log(`    ${step.verified ? "✓" : "·"} ${step.description}`);
  }

  // Narrative attack path
  if (i.attackPath) {
    console.log(`  Likelihood: ${i.attackPath.likelihood} (${i.attackPath.likelihoodRationale})`);
    console.log(`  Impact:     ${i.attackPath.impact} (${i.attackPath.impactRationale})`);
  }

  // Polymorphic evidence — HTTP transcripts, screenshot URLs, code snippets
  for (const ev of i.evidence ?? []) {
    switch (ev.type) {
      case "http":       console.log(`  [HTTP] ${ev.method} ${ev.url} → ${ev.responseStatus}`); break;
      case "screenshot": console.log(`  [IMG]  ${ev.screenshotUrl}`); break;
      case "code":       console.log(`  [CODE] ${ev.filePath}:${ev.startLine}-${ev.endLine}`); break;
    }
  }
}

Use i.summary for a 2-3 paragraph human-readable explanation, or maced.pentests.report(id).markdown for the full narrative report ready to render to PDF with your own branding.

Reseller pattern — attribute runs + meter end-customers

// On create — stash your customer ID + their webhook on each run
const run = await maced.pentests.create({
  targetUrl: customerTarget,
  webhookUrl: `https://my-backend.example.com/webhooks/maced`,
  metadata: { customerId: "acme_123", plan: "enterprise" },
});

// When fetched back, metadata is typed + returned verbatim
const detail = await maced.pentests.get(run.id);
console.log(detail.metadata?.customerId); // "acme_123"

// Bill end-customers against actual Maced cost
const { totalCostUsd, completedRuns } = await maced.usage.get({ period: "month" });
const markup = 2.5;
chargeCustomer({ usd: totalCostUsd * markup });

Receive signed webhooks

Fetch your org's signing secret once, store it somewhere safe (env var, secret manager), then verify every inbound webhook:

import { MacedClient, verifyMacedWebhook, MacedWebhookSignatureError } from "@maced/api-client";

// One-time, from a dev shell or deploy script
const maced = createMacedClient({ apiKey: process.env.MACED_API_KEY! });
const { secret } = await maced.webhooks.getSecret();
// → "whsec_5a8d..." — persist to your secret manager

// In your webhook handler (Next.js app router shown)
export async function POST(req: Request) {
  const raw = await req.text();
  const sig = req.headers.get("x-maced-signature");
  try {
    const event = await verifyMacedWebhook(raw, sig, process.env.MACED_WEBHOOK_SECRET!);
    switch (event.event) {
      case "pentest.completed":
        await sendCustomerReport(event.runId, event.report);
        break;
      case "pentest.failed":
        await alertOps(event.runId, event.error);
        break;
    }
    return new Response(null, { status: 204 });
  } catch (err) {
    if (err instanceof MacedWebhookSignatureError) {
      return new Response(err.code, { status: 400 });
    }
    throw err;
  }
}

Verification is HMAC-SHA256 over ${timestamp}.${rawBody}, timing-safe comparison, 5-minute clock tolerance (replay protection).

Guard against accidental production calls

const maced = createMacedClient({ apiKey: process.env.MACED_API_KEY! });

if (process.env.NODE_ENV === "test" && maced.environment !== "test") {
  throw new Error("Refusing to run tests against a live Maced key");
}

Create a pentest and wait for the report

const run = await maced.pentests.create({
  targetUrl: "https://target.example.com",
  scope: "black-box",
  notificationEmail: "[email protected]",
});

const done = await maced.pentests.waitForCompletion(run.id, {
  intervalMs: 10_000,
  timeoutMs: 60 * 60 * 1000, // 1h
  onProgress: (r) => {
    console.log(`${r.status}: ${r.progress.completedAgents}/${r.progress.totalAgents}`);
  },
});

if (done.status !== "completed") {
  throw new Error(`Pentest finished as ${done.status}`);
}

const report = await maced.pentests.report(done.id);
await Bun.write("./report.md", report.markdown);

Triage critical issues across all pentests

const open = await maced.issues.list({ severity: "critical", status: "open" });

for (const issue of open) {
  console.log(`[${issue.severity}] ${issue.title} — ${issue.runId}`);
}

// Close issues you've already fixed
await Promise.all(
  resolvedIds.map((id) => maced.issues.update(id, { status: "resolved" })),
);

Verify a domain

const { txtRecord, txtValue, verified } = await maced.domains.verify("acme.com");
if (!verified) {
  console.log(`Publish a TXT record at ${txtRecord} with value ${txtValue}`);
}

// Later, after DNS propagates:
const { verified: ok } = await maced.domains.check("acme.com");

Plug Sentry / PostHog for observability

import * as Sentry from "@sentry/node";

const maced = createMacedClient({
  apiKey: process.env.MACED_API_KEY!,
  logger: {
    onError: ({ method, url, status }) =>
      Sentry.captureMessage(`Maced API ${method} ${url} ${status}`),
    onResponse: ({ durationMs, url }) => {
      if (durationMs > 2_000) Sentry.captureMessage(`slow: ${url} ${durationMs}ms`);
    },
  },
});

Disable retries for idempotency-sensitive flows

const maced = createMacedClient({
  apiKey: process.env.MACED_API_KEY!,
  retry: { maxAttempts: 1 },
});

Cancel an in-flight request

const controller = new AbortController();
setTimeout(() => controller.abort(), 5_000);

const { data, error } = await maced.http.GET("/v1/pentests", {
  signal: controller.signal,
});

Environments

mc_live_* keys hit production pentests and consume credits. mc_dev_* keys run simulated pentests against the same API surface — use them in CI and tests.

The client cares only about the key prefix — both hit api.maced.ai.

Runtime support

| Runtime | Status | |--------------------|--------| | Node 18+ | ✅ | | Node 20 / 22 | ✅ | | Bun | ✅ | | Deno | ✅ (via npm:@maced/api-client) | | Cloudflare Workers | ✅ | | Edge / Vercel Edge | ✅ | | Browsers | ❌ — see "Backend only" banner above | | React Native | ❌ — same | | Electron renderer | ❌ — same |

Versioning

Types are regenerated from the live OpenAPI spec on every API change via a GitHub Action. Each regeneration that produces a diff bumps a patch version and publishes with npm provenance.

Major versions (breaking changes to the SDK surface itself) follow semver.

Links

  • API docs: https://api.maced.ai/docs
  • OpenAPI spec: https://api.maced.ai/openapi.json
  • Dashboard: https://maced.ai
  • Generate keys: https://maced.ai/settings/api
  • Issues: https://github.com/goatedventures/maced/issues

License

MIT