@vegacap/node
v0.1.0
Published
VegaCap Node.js SDK — typed server-side client for the VegaCap Platform API
Maintainers
Readme
@vegacap/node
Typed TypeScript server SDK for the VegaCap Platform API.
Create assessment sessions, fetch results, manage webhook endpoints, download PDF reports, and verify incoming webhook signatures — all with end-to-end types and typed errors.
- Native
fetch, no axios. Node.js 18 or newer. - Typed errors — every error code has its own
instanceof-friendly class. - Automatic retries — 5xx retried once, 429 honours
Retry-After. - Webhook verification without spinning up a client instance.
Install
npm install @vegacap/node
# or
bun add @vegacap/node
# or
pnpm add @vegacap/nodeQuickstart
import { VegaCap } from '@vegacap/node';
const vc = new VegaCap({
apiKey: process.env.VEGACAP_SECRET_KEY!, // vegacap_sk_live_...
});
const session = await vc.sessions.create({
assessmentType: 'disc',
candidate: {
externalId: 'user_42',
email: '[email protected]',
name: 'Alice',
},
metadata: { campaign: 'Q2-hiring' },
});
// Redirect your candidate to this URL to take the assessment.
console.log(session.assessmentUrl);Configuration
new VegaCap({
apiKey: process.env.VEGACAP_SECRET_KEY!,
baseUrl: 'https://vegacapltd.com/api/v1', // default
timeout: 30_000, // ms, default 30s
fetch: myFetchOverride, // optional (testing/proxies)
});The SDK emits User-Agent: @vegacap/node/<version> on every request.
Resources
Sessions
// Create a session (returns an `assessmentUrl` you redirect candidates to).
const session = await vc.sessions.create(
{
assessmentType: 'disc',
candidate: { externalId: 'user_42', email: '[email protected]' },
metadata: { campaign: 'Q2-hiring' },
},
{ idempotencyKey: 'req_create_user_42_disc' },
);
// Retrieve a session by ID (includes results + signed reportUrl once complete).
const result = await vc.sessions.retrieve(session.id);
if (result.status === 'completed') {
console.log(result.results);
console.log(result.reportUrl); // short-lived signed URL
}
// List sessions, newest first. `cursor` is a numeric offset.
const page1 = await vc.sessions.list({
status: 'completed',
assessmentType: 'disc',
limit: 20,
});
const page2 = await vc.sessions.list({
limit: 20,
cursor: page1.offset + page1.data.length,
});
// Server-to-server submissions (embed iframe submits on its own).
await vc.sessions.submit(session.id, {
responses: { /* per-assessment payload */ },
language: 'en',
});
// Cancel before completion.
await vc.sessions.cancel(session.id);About
cursor: the API is currently offset-based. The SDK acceptscursoras a number or numeric string for forward compatibility and passes it asoffseton the wire. Read the returnedoffset/total/limitto paginate.
Organization
// Returns the org tied to your API key, including license counts.
const me = await vc.organizations.me();
console.log(me.slug, me.assessments);Webhook endpoints
// Create an endpoint. The `secret` is returned ONCE — store it securely.
const endpoint = await vc.webhooks.endpoints.create({
url: 'https://hooks.acme.com/vegacap',
events: ['session.completed', 'session.failed'],
description: 'Prod webhook',
});
console.log(endpoint.secret); // whsec_...
// List, retrieve, update, delete.
const { data } = await vc.webhooks.endpoints.list();
const single = await vc.webhooks.endpoints.retrieve(endpoint.id);
await vc.webhooks.endpoints.delete(endpoint.id);Reports
import { createWriteStream } from 'node:fs';
import { Writable } from 'node:stream';
const stream = await vc.reports.downloadPdf(session.id); // ReadableStream<Uint8Array>
// Pipe straight to disk without buffering the whole PDF.
await stream.pipeTo(Writable.toWeb(createWriteStream('./report.pdf')));The method returns a native ReadableStream<Uint8Array> so you can pipe it to
a file, an HTTP response, or an S3 upload without ever materialising the full
PDF in memory.
Webhook signature verification
Every delivery from VegaCap includes an X-VegaCap-Signature header of the
form sha256=<hex>, computed as HMAC_SHA256(webhook_secret, raw_body).
VegaCap.webhooks.verify(...) is static — no client instance required.
import express from 'express';
import { VegaCap } from '@vegacap/node';
const app = express();
// IMPORTANT: the verifier operates on the RAW body, so capture bytes BEFORE
// any JSON parser touches them.
app.post(
'/webhooks/vegacap',
express.raw({ type: 'application/json' }),
(req, res) => {
const signature = req.header('x-vegacap-signature') ?? '';
const valid = VegaCap.webhooks.verify(
req.body as Buffer, // raw bytes
signature,
process.env.VEGACAP_WEBHOOK_SECRET!,
);
if (!valid) return res.status(400).send('invalid signature');
const event = JSON.parse((req.body as Buffer).toString('utf8'));
// Handle `session.completed`, `session.failed`, etc.
res.status(200).send('ok');
},
);Next.js Route Handler example:
// app/api/webhooks/vegacap/route.ts
import { VegaCap } from '@vegacap/node';
export async function POST(req: Request): Promise<Response> {
const raw = await req.text(); // raw string, unparsed
const signature = req.headers.get('x-vegacap-signature') ?? '';
const valid = VegaCap.webhooks.verify(
raw,
signature,
process.env.VEGACAP_WEBHOOK_SECRET!,
);
if (!valid) return new Response('invalid signature', { status: 400 });
const event = JSON.parse(raw);
// ...
return new Response('ok');
}Errors
Every non-2xx response throws a subclass of VegaCapError:
import {
VegaCap,
VegaCapError,
NoAvailableLicensesError,
RateLimitedError,
ValidationError,
} from '@vegacap/node';
try {
await vc.sessions.create({ /* ... */ });
} catch (err) {
if (err instanceof NoAvailableLicensesError) {
// Nudge billing, surface a friendly UI message, etc.
return;
}
if (err instanceof RateLimitedError) {
console.log(`retry after ${err.retryAfter}s`);
return;
}
if (err instanceof ValidationError) {
console.log('details:', err.details);
return;
}
if (err instanceof VegaCapError) {
console.log(err.code, err.status, err.requestId);
}
throw err;
}Each error carries:
| Field | Description |
|--------------|-------------------------------------------------------|
| code | Machine-readable code (e.g. "invalid_api_key"). |
| status | HTTP status. |
| message | Human-readable message from the API. |
| details | Per-field validation issues (when present). |
| requestId | X-Request-Id echoed by the API, useful in support. |
| docUrl | Link to the matching docs entry. |
| retryAfter | Seconds (429 only). |
Available subclasses:
InvalidApiKeyError, ExpiredApiKeyError, RevokedApiKeyError,
InsufficientPermissionsError, InvalidSessionTokenError,
SessionNotFoundError, SessionExpiredError, SessionAlreadyCompletedError,
InvalidAssessmentTypeError, AssessmentNotEnabledError,
NoAvailableLicensesError, WebhookNotFoundError, InvalidWebhookUrlError,
RateLimitedError, ValidationError, IdempotencyConflictError,
InternalServerError, NotImplementedError, ScoringError,
PdfGenerationFailedError.
All codes are also exported as API_ERROR_CODES for when you want to branch
on a string instead of an instanceof check.
Retries
- 5xx: one retry after 500 ms.
- 429: one retry after the server's
Retry-Afterheader (capped at 60 s). - 4xx: never retried.
If the retry also fails, the final response is thrown as usual.
Idempotency
Pass an Idempotency-Key to sessions.create to make retries safe:
await vc.sessions.create(params, { idempotencyKey: 'order_42:disc' });Retrying with the same key returns the original session; retrying with a
different body returns IdempotencyConflictError.
TypeScript
The SDK ships with full types — including shared request/response shapes, assessment types, webhook events, session statuses, and the error catalog. Import them directly:
import type {
AssessmentType,
WebhookEvent,
SessionStatus,
SessionResponse,
SessionResultResponse,
CreateSessionParams,
} from '@vegacap/node';License
Apache-2.0
