@baref00t/sdk
v0.9.0
Published
Official TypeScript SDK for the baref00t Partner and Distributor APIs
Maintainers
Readme
@baref00t/sdk
Official TypeScript SDK for the baref00t Partner and Distributor REST APIs.
npm install @baref00t/sdk- ✅ Typed clients for Partner + Distributor APIs
- ✅ Webhook signature verification (Stripe-style HMAC-SHA256)
- ✅ Auto-retry on transient failures (429, 502, 503, 504, network errors) with exponential backoff +
Retry-Afterhonoring - ✅ Dual ESM + CJS, treeshakable subpath exports
- ✅ Zero runtime dependencies — uses
globalThis.fetch(Node 18+) - ✅ Apache-2.0 licensed
- ✅ 30 triggerable products + 1 Coming Soon across 5 categories — compliance, security packs, intelligence reports, productivity analytics, and Copilot Assessments (
copilot-agent-inventory,copilot-interaction-compliance,copilot-meeting-insights;copilot-redteam-probeships v2.4.1)
Quick start (Partner)
import { PartnerClient } from '@baref00t/sdk/partner'
const client = new PartnerClient({
apiKey: process.env.BAREF00T_PARTNER_KEY!,
})
// Profile + plan + usage
const me = await client.me.get()
console.log(`Plan ${me.plan}, ${me.runsUsed}/${me.runLimit} assessments this month`)
// Customer CRUD
const customer = await client.customers.create({
name: 'Acme Corp',
tenantId: '00000000-0000-0000-0000-000000000000',
email: '[email protected]',
})
// Trigger an assessment
const run = await client.assessments.create({
customerId: customer.customerId,
product: 'tenant-health',
})
console.log(`Triggered ${run.assessmentId} (status: ${run.status})`)
// Permanently delete an assessment + its report (irreversible, ownership-checked).
// Removes the run, any linked proposal/narrative rows, and the rendered report blobs.
await client.assessments.delete(run.assessmentId)
// Trigger with procedural attestations (v2.4.6+). Six products carry a
// procedural questionnaire — e8, ransomware, cyber-essentials, mcsb,
// mas-trm, nis2. A `true` answer upgrades the matching control's
// automated `warn` verdict to `pass` and records the attestation in
// the report audit trail. Catalogue: `PROCEDURAL_QUESTIONS_BY_SLUG` in
// `@baref00t/assessments/attestation-questions`. (#526)
await client.assessments.create({
customerId: customer.customerId,
product: 'e8',
maturityTarget: 'ML2',
attestations: {
e8_backup_restore_drills: true,
e8_backup_3_2_1: true,
e8_app_control_servers: false,
},
})
// Trigger a Copilot Assessment (v2.4.0+). Note: copilot-* products are
// scoped to professional + enterprise plans only.
// copilot-redteam-probe is currently Coming Soon (ships v2.4.1) — the
// API rejects it with 400 today.
await client.assessments.create({
customerId: customer.customerId,
product: 'copilot-agent-inventory', // or copilot-interaction-compliance / copilot-meeting-insights
})Pipeline (lead prospecting)
The client.leads.* resource drives the partner prospecting pipeline — Apollo + Hunter contact enrichment, M365 OIDC tenant discovery, and a New → Enriched → OutreachSent → Consented → ReportDelivered → Won | Lost stage machine. Requires the partner record to have prospecting in enabledFeatures (admin-granted only — no plan tier auto-includes it).
// Create one lead — sync enrichment by default
const { lead } = await client.leads.create({ domain: 'acme.com', product: 'e8' })
console.log(`Lead ${lead.id} on tenant ${lead.tenantId} (M365: ${lead.isOnM365})`)
// Promote a different contact to primary
await client.leads.update(lead.id, {
partnerPrimaryOverride: { name: 'Jane Doe', email: '[email protected]', role: 'CISO' },
})
// Kick off the partner-branded outreach cadence (costs 1 credit).
// Dispatch follows the partner's mailProvider — `resend` (default),
// `microsoft` (partner's Graph mailbox), or `off` (use skipEmail).
const sent = await client.leads.sendConsent(lead.id, {
recipientEmails: ['[email protected]', '[email protected]'],
})
console.log(`${sent.totalSent}/${sent.totalRecipients} emails dispatched`)
console.log('via', sent.recipients[0].via) // 'resend' or 'microsoft'
// → lead now at OutreachSent; lead.consented + lead.report_delivered fire as the customer progresses
// Or — mint consent URLs without sending email (still costs 1 credit;
// dispatch via your own Resend / Microsoft Graph / CRM channel).
// Required when partner.mailProvider === 'off' (otherwise: MAIL_DISABLED).
const minted = await client.leads.sendConsent(lead.id, {
recipientEmails: ['[email protected]'],
skipEmail: true,
})
const consentUrl = minted.recipients[0].consentUrl // pass to your own mailer
// Bulk import a target list (async enrichment)
await client.leads.bulkCreate({ domains: ['globex.com', 'initech.com'], product: 'e8' })
// Pipeline state
const pipeline = await client.leads.list({ stage: 'Enriched', limit: 50 })
console.log(`${pipeline.total} enriched leads; counts:`, pipeline.counts)
// Mark a lead Won / Lost (terminal).
// When the partner has the `lead-deferred-customer` feature flag, Won
// also materialises a `partner_customer` row from the lead, backfills
// any orphan run rows, and fires `customer.created`.
await client.leads.setStage(lead.id, { stage: 'Won' })Subscribe to lead lifecycle events via the standard webhook flow: lead.created, lead.enriched, lead.outreach_sent, lead.stage_changed, lead.consented, lead.report_delivered, and lead.won (#369). Under the lead-deferred-customer flag, SendLeadConsentResponse.customerId is null until the lead is Won — see docs/partners/api-reference.md for payload shapes and the full lifecycle table.
Quick start (Distributor)
import { DistributorClient } from '@baref00t/sdk/distributor'
const client = new DistributorClient({
apiKey: process.env.BAREF00T_DISTRIBUTOR_KEY!,
})
// List your sub-partners
const { partners } = await client.partners.list({ status: 'active' })
// Aggregated usage across all sub-partners
const usage = await client.usage.get({ month: '2026-04' })Webhooks
import express from 'express'
import { verifyWebhookSignature, BareF00tWebhookError } from '@baref00t/sdk/webhooks'
const app = express()
app.post(
'/hooks/baref00t',
express.raw({ type: 'application/json' }),
(req, res) => {
try {
const event = verifyWebhookSignature({
rawBody: req.body, // raw Buffer, NOT parsed JSON
signature: req.header('X-Baref00t-Signature')!,
secret: process.env.BAREF00T_WEBHOOK_SECRET!,
})
switch (event.type) {
case 'assessment.completed':
// event.data is typed
break
}
res.sendStatus(204)
} catch (err) {
if (err instanceof BareF00tWebhookError) {
res.status(400).send(err.kind)
return
}
throw err
}
},
)Supported event types (v0.1):
partner.created,partner.activated,partner.suspended,partner.plan-changedassessment.completedtest.ping(sent by the platform's "Test webhook" button)
Unknown event types fall through with data: Record<string, unknown> so your handler doesn't break when the platform adds new ones.
Configuration
Both clients accept the same options:
new PartnerClient({
apiKey: 'pk_live_...', // required; SDK validates the prefix
baseUrl: 'https://api.baref00t.io', // default
retries: 2, // default; 0 disables auto-retry
timeoutMs: 30_000, // default 30s, max 600s
fetch: globalThis.fetch, // default; override for testing
defaultHeaders: { 'X-Trace-Id': '...' }, // added to every request
})Error handling
All SDK errors extend BareF00tError:
import { BareF00tApiError, BareF00tRateLimitError, BareF00tNetworkError } from '@baref00t/sdk'
try {
await client.assessments.create({ customerId, product: 'tenant-health' })
} catch (err) {
if (err instanceof BareF00tRateLimitError) {
// Retry-After is exposed in seconds (capped at 5 min)
await sleep(err.retryAfterSeconds * 1000)
} else if (err instanceof BareF00tApiError) {
console.error(`Platform error ${err.code}: ${err.message}`)
if (err.code === 'RUN_LIMIT_REACHED') {
// err.body has { runsUsed, runLimit }
}
} else if (err instanceof BareF00tNetworkError) {
console.error('Network failure', err.cause)
} else {
throw err
}
}API key rotation
// Generate a new key — raw value is shown ONCE in response.key
const created = await client.keys.create()
console.log('Slot', created.slot, 'suffix', created.suffix)
// Save created.key to your secrets store NOW. You cannot recover it later.
// After verifying the new key works, revoke the old one:
await client.keys.revoke(1) // or 2⚠ Don't revoke the slot you're currently authenticated with before you've deployed the new key — the SDK will lock itself out and you won't be able to revoke or rotate from this client instance.
Versioning
This SDK follows semver. v0.x is considered preview — breaking changes can land in minor versions while we stabilise. v1.0 will lock the public surface.
Track changes in CHANGELOG.md.
