@bjhunt/sdk
v2.0.0
Published
Official JavaScript/TypeScript client for the BJHUNT Enterprise API
Maintainers
Readme
@bjhunt/sdk
Official JavaScript / TypeScript client for the BJHUNT Enterprise API.
The API is available on the Enterprise plan only. Create an API key from the dashboard → Settings → API Keys. It is shown once, starts with
bjh_, and is confined to/api/v1/**.
Install
npm install @bjhunt/sdkZero runtime dependencies — uses the global fetch (Node 18+, Bun, Deno, browsers).
Quickstart
import { BjhuntClient } from '@bjhunt/sdk'
const bjhunt = new BjhuntClient({ apiKey: process.env.BJHUNT_API_KEY! })
// Launch an autonomous audit. `authorization` is REQUIRED — an auditable
// attestation that you are allowed to test the target (the API is fail-closed).
const scan = await bjhunt.scans.create({
target: 'https://acme.example.com',
authorization: { attested: true, authorized_by: '[email protected]' },
compliances_required: ['owasp-asvs-5', 'pci-dss-v4'],
})
// It runs asynchronously — poll the lightweight status endpoint (or subscribe to
// the scan.completed webhook).
let status = await bjhunt.scans.status(scan.id)
while (status.status === 'running' || status.status === 'pending') {
await new Promise((r) => setTimeout(r, 10_000))
status = await bjhunt.scans.status(scan.id)
}
// Pull findings (cursor-paginated — iterate them all), export for a SIEM, report.
for await (const f of bjhunt.scans.findingsAll(scan.id, { severity: 'critical' })) {
console.log(f.severity, f.title, f.cvss.v4_score)
}
const sarif = await bjhunt.scans.exportFindings(scan.id, 'sarif')
const report = await bjhunt.scans.report(scan.id, { format: 'md' })Steer a running scan
// Send a follow-up instruction to an in-flight scan (text-only; the engagement
// scope is frozen at launch). Returns immediately (202) — poll status() for the effect.
await bjhunt.scans.sendMessage(scan.id, 'Also probe the password-reset flow for host-header injection.')API
| Method | Returns |
|---|---|
| scans.create(input, { idempotencyKey? }) | Scan — input requires authorization: { attested: true, authorized_by } |
| scans.list({ status?, limit?, starting_after? }) | Page<Scan> ({ object, data, has_more, next_cursor }) |
| scans.listAll(opts?) | AsyncGenerator<Scan> — auto-paginates |
| scans.get(scanId) | Scan |
| scans.status(scanId) | ScanStatusInfo — state + severity roll-up (CI/CD polling) |
| scans.sendMessage(scanId, message, { idempotencyKey? }) | ScanMessageAck — follow-up instruction |
| scans.delete(scanId) | DeleteResult — abort + soft-delete |
| scans.findings(scanId, { severity?, limit?, starting_after? }) | Page<Finding> |
| scans.findingsAll(scanId, opts?) | AsyncGenerator<Finding> |
| scans.finding(scanId, findingId) | FindingDetail — repro, impact, remediation, mappings |
| scans.exportFindings(scanId, 'sarif' \| 'csv' \| 'json') | string — SIEM/CI artifact |
| scans.report(scanId, { compliance?, format? }) | string — Markdown or HTML |
| scans.evidence(scanId) | EvidenceList — chain-of-custody metadata |
| scans.downloadEvidence(scanId, evidenceId) | ArrayBuffer — integrity re-verified server-side |
| usage({ days? }) | Usage — your org's API usage |
new BjhuntClient({ apiKey, baseUrl?, timeoutMs?, fetch? }).
Every response is snake_case. Lists are cursor-paginated — pass a page's
next_cursor back as starting_after, or use the *All() async iterators.
Errors
Non-2xx responses throw BjhuntApiError, parsed from the RFC 9457
problem+json body:
try {
await bjhunt.scans.create({ target: 'https://acme.example.com', authorization: { attested: true, authorized_by: 'me' } })
} catch (err) {
if (err instanceof BjhuntApiError) {
console.error(err.status) // 402
console.error(err.code) // 'QUOTA_EXCEEDED' (stable machine code)
console.error(err.detail) // human-readable message
console.error(err.requestId) // quote to support
}
}Idempotency: pass { idempotencyKey } to create / sendMessage so a retried
request replays the original result instead of running twice.
Webhooks
Verify the X-BJHUNT-Signature header against the raw request body before
trusting a delivery (finding.created, scan.completed, report.generated):
import { parseWebhook } from '@bjhunt/sdk'
// e.g. inside an Express raw-body handler:
const event = parseWebhook(rawBody, req.headers['x-bjhunt-signature'], WEBHOOK_SECRET)
if (event.event === 'scan.completed') { /* … */ }Build
npm run build # tsc → dist/