The same pipeline, behind a REST API.
Base URL: https://audit-api.bjole.dev. Authentication is a header-borne API key per tenant. Read on.
quick reference
Five endpoints, no surprises.
/auditsKick off a new audit. Returns 202 with the audit id.
/auditsList audits for the calling tenant.
/audits/:idSingle audit status + metadata (polling).
/audits/:id/eventsServer-sent events stream — chunk events, done, error, ping.
/audits/:id/chunks/:namePull one chunk as JSON. Names: company, financial, technical, risks, recommendations, score, executive-summary, industry-analysis, validation.
Authentication
Every request needs an x-api-key header. SSE and chunk endpoints also accept ?api_key= as a query param so native EventSource works.
You get an API key when you sign up. Keys are tenant-scoped, are stored as a sha-256 hash on our side, and can be revoked from the dashboard. We log key prefix + last-used-at — never the raw key.
Create an audit
Returns immediately with 202 Accepted and an id. The audit runs in the background; subscribe to the SSE stream below to watch progress.
curl -X POST https://audit-api.bjole.dev/audits \
-H "x-api-key: $AUDIT_API_KEY" \
-H "content-type: application/json" \
-d '{
"company": "Spotify",
"industry": "Music Streaming",
"location": "Sweden",
"size": "9000+"
}'
# 202 Accepted
# {
# "id": "8b3f...c3ce",
# "status": "queued",
# "storage_prefix": "audit-runs/<tenant>/<audit-id>/",
# "created_at": "2026-05-23T10:34:27Z"
# }const res = await fetch("https://audit-api.bjole.dev/audits", {
method: "POST",
headers: {
"x-api-key": process.env.AUDIT_API_KEY!,
"content-type": "application/json",
},
body: JSON.stringify({
company: "Spotify",
industry: "Music Streaming",
location: "Sweden",
size: "9000+",
}),
});
const { id } = await res.json();import os, requests
res = requests.post(
f"https://audit-api.bjole.dev/audits",
headers={"x-api-key": os.environ["AUDIT_API_KEY"]},
json={
"company": "Spotify",
"industry": "Music Streaming",
"location": "Sweden",
"size": "9000+",
},
timeout=30,
)
res.raise_for_status()
audit_id = res.json()["id"]Watch progress with SSE
Each chunk emits a chunk event as it lands. When all nine chunks complete, you get a done event with the overall score. The stream also pings every 15 s to keep proxies happy.
// Listen for progress as chunks land
const events = new EventSource(
`https://audit-api.bjole.dev/audits/${id}/events?api_key=${apiKey}`
);
events.addEventListener("chunk", (e) => {
const { name, sizeBytes, step } = JSON.parse(e.data);
console.log(`chunk ${name} ready (${sizeBytes}B) — step ${step}`);
});
events.addEventListener("done", (e) => {
const { overallScore } = JSON.parse(e.data);
console.log("audit complete, score:", overallScore);
events.close();
});Pull a single chunk
Each section ships as its own JSON blob — pull one at a time, or assemble the whole audit by fetching all nine names. Cross-tenant access returns 404 (same as "not found").
curl https://audit-api.bjole.dev/audits/$AUDIT_ID/chunks/risks \
-H "x-api-key: $AUDIT_API_KEY"chunk names
metadatacompanyfinancialtechnicalrisksrecommendationsscoreexecutive-summaryindustry-analysisvalidationthe small print
Rate limits, errors, and webhooks.
Rate limits. Soft cap of one concurrent audit per tenant for free-tier accounts; paid tenants get up to ten. Burst above your tier returns 429 with retry-after.
Errors. JSON shape: { error, issues? }. 400 for validation, 401 for missing/bad key, 404 for non-existent or cross-tenant, 429 for rate limit, 5xx for our problems.
Webhooks. Configure a URL in the dashboard and we POST audit.completed / audit.failed events with HMAC-SHA256 signatures (X-Audit-Signature header). Retries with exponential backoff for 24 h.