Documentation
VegaCap Platform API v1 — sessions, webhooks, assessments, and reports.
Quickstart
Five steps to go from zero to a scored assessment. Takes about 10 minutes.
1. Get keys
Head to API Keys and create a test pair (vegacap_pk_test_* + vegacap_sk_test_*). Test keys return deterministic mock scoring for all 6 assessment types — use them until your integration is stable, then switch to a live pair.
2. Install the SDKs
npm install @vegacap/node@0.1.0 # or: bun add @vegacap/node@0.1.0 # or: pnpm add @vegacap/node@0.1.0
npm install @vegacap/react@0.1.0 # or: bun add @vegacap/react@0.1.0 # or: pnpm add @vegacap/react@0.1.0
Latest release: @vegacap/node@0.1.0, @vegacap/react@0.1.0, @vegacap/sdk@0.1.0. All three are Apache-2.0 with zero runtime dependencies — the tarball contains everything needed to run.
Prefer no framework? Load the browser embed SDK from the CDN — no npm install: <script src="https://www.vegacapltd.com/sdk/v1/vegacap.js"></script>
3. Create a session (server)
import { VegaCap } from '@vegacap/node';
const vc = new VegaCap({ apiKey: process.env.VEGACAP_SECRET_KEY! });
const session = await vc.sessions.create({
assessmentType: 'disc',
candidate: {
externalId: 'candidate-42',
email: 'candidate@example.com',
name: 'Sam Example',
},
redirectUrl: 'https://your-app.com/assessment/done',
});
// Send session.sessionToken to your frontend — it's the only piece the browser sees.4. Embed in the browser
<div id="vc-container"></div>
<script src="https://www.vegacapltd.com/sdk/v1/vegacap.js"></script>
<script>
const vc = window.VegaCap.init({
publishableKey: 'vegacap_pk_test_...',
});
vc.assessments.embed({
sessionToken: 'vst_...', // from step 3
container: '#vc-container',
onComplete: (result) => {
console.log('done', result.sessionId, result.summary);
},
});
</script>'use client';
import { VegaCapProvider } from '@vegacap/react';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<VegaCapProvider
publishableKey={process.env.NEXT_PUBLIC_VEGACAP_PUBLISHABLE_KEY!}
>
{children}
</VegaCapProvider>
);
}import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}'use client';
import { useRouter } from 'next/navigation';
import { useEmbed } from '@vegacap/react';
export function AssessmentPage({ sessionToken }: { sessionToken: string }) {
const router = useRouter();
const { container, state } = useEmbed({
sessionToken,
onComplete: () => router.push('/thank-you'),
});
return (
<>
{state === 'loading' && <p>Loading…</p>}
<div ref={container} />
</>
);
}5. Subscribe to webhook events
Once the candidate submits, session.completed fires to any webhook endpoint you've registered. Verify the HMAC signature with the per-endpoint secret — drop any request whose signature doesn't match.
import express from 'express';
import { VegaCap } from '@vegacap/node';
// express.raw() so req.body is the raw Buffer.
// Parsing + re-serializing JSON would break the HMAC.
app.post(
'/vegacap/webhook',
express.raw({ type: 'application/json' }),
(req, res) => {
const header = req.header('x-vegacap-signature') ?? '';
const ok = VegaCap.webhooks.verify(
req.body, // raw Buffer
header, // "sha256=..."
process.env.VEGACAP_WEBHOOK_SECRET!, // whsec_...
);
if (!ok) return res.status(401).end();
const event = JSON.parse(req.body.toString('utf8'));
if (event.event === 'session.completed') {
// event.data carries sessionId, results, candidate, metadata, etc.
saveToDatabase(event.data);
}
res.status(200).end();
},
);Register the endpoint at Webhooks. Failed deliveries retry with exponential backoff up to 5 attempts; undeliverable after that lands in the dead-letter queue (visible in the admin view).
Test mode behaviour
- All 6 assessments return mock
resultSummaryinstantly. - No license credits are consumed.
- Webhooks still fire end-to-end, so partner receivers can be validated.
- Sessions are filtered out of production admin reports (appear only in the test-mode view).
Overview
The Platform API lets you create assessment sessions, stream submissions, and receive results via webhooks.
Base URL: https://api.vegacapltd.com/api/v1. All requests return JSON and use standard HTTP status codes. Errors follow a uniform { error: { code, message } } shape.
Orgs & access
How the portal decides which organizations you can see, and how to scope API requests when you belong to more than one.
Who can sign in?
Any user who is an admin or owner of at least one organization on VegaCap can sign in to the developer portal — the same credentials you use for the main dashboard. The portal reads from the same organizations and organization_members tables as the main app, so there is no separate onboarding or migration step.
Old organizations
Organizations created before the developer portal launched work exactly the same as newly created ones. If you see No organizations in the sidebar, make sure you are signed in with the email associated with your admin account (check the email on the avatar at the top of the main dashboard) and reload — the portal will pick up every org where you are an admin, owner, or portal-invited team member.
Multiple admins per org
Every admin of an org can access the portal independently — there is no single "owner seat." API keys, tokens, webhooks, and team-member invites are scoped to the org, not the individual admin, so you and your co-admins share the same view. The actor on audit entries (who created a key, who rotated a token) reflects the specific admin who performed the action.
Belonging to multiple orgs
If you are an admin of more than one organization, the sidebar shows an org switcher under the logo. The portal remembers your last selection in localStorage so you land on the same org next time. Session, key, webhook, and usage views are always scoped to the selected org.
When calling the portal API directly (e.g. from a custom script that hits /api/portal/* with your portal session cookie), multi-org users must pass the header below on every request. Single-org users can omit it.
GET /api/portal/sessions HTTP/1.1 Host: developer.vegacapltd.com Cookie: better-auth.session=... X-Organization-Id: <the-org-uuid>
Note: this only applies to the portal's own REST endpoints used by the UI. The public Platform API ( api.vegacapltd.com/api/v1/*) is scoped by the API key itself — each key belongs to exactly one org, so there is nothing to disambiguate.
Signed in but no orgs listed?
The portal recognises three states as "joined" (active, completed, joined) as well as the organization creator (organizations.admin_user_id). If you believe you should see an org but do not, confirm you are signed in with the account that was added as an admin — the match is on user ID, not email alias.
Authentication
Every request needs an API key. Publishable keys are safe for browsers; secret keys must stay server-side.
GET /api/v1/sessions HTTP/1.1 Host: api.vegacapltd.com Authorization: Bearer vegacap_sk_live_7f8a...
Rotate keys from API Keys. Test-mode keys short-circuit scoring with deterministic mock data.
Rate Limits
Per-key, per-minute sliding window. Rate limit state surfaces in response headers.
X-RateLimit-Limit: 120 X-RateLimit-Remaining: 117 X-RateLimit-Reset: 1744876800
Exceeding the limit returns 429 Too Many Requests. Default is 120 requests/minute per key; contact us to raise it.
Endpoints
Core resources you will call day-to-day.
/api/v1/sessionsCreate an assessment session/api/v1/sessionsList sessions with filters/api/v1/sessions/{id}Fetch a single session/api/v1/embed/submitSubmit assessment responses/api/v1/reports/{id}/pdfStream the generated PDF report/api/v1/org/meCurrent organization context/api/v1/keys/{id}/rotateRotate an API key/api/v1/tfd/configsList published TFD configs (occupations) for this orgWebhook Events
Verify the X-VegaCap-Signature header (HMAC-SHA256 of the raw body) before trusting any payload.
session.startedin_progressFires when the candidate first loads the assessment and the session transitions from created to in_progress.
{
"event": "session.started",
"id": "evt_3f7a9c0d82b1e54fa6c2d908",
"createdAt": "2026-04-17T08:42:11.284Z",
"data": {
"sessionId": "ses_0K7RfJ4m9xDqH3pN2vQwY8zB",
"assessmentType": "disc",
"status": "in_progress",
"candidate": {
"externalId": "cand_8a3c91",
"email": "priya@example.com",
"name": "Priya Sharma"
},
"results": null,
"metadata": {
"campaign": "summer-hiring-2026",
"source": "careers-page"
},
"completedAt": null
}
}session.completedcompletedFires when the candidate submits the assessment and scoring succeeds. data.results contains the denormalized resultSummary.
{
"event": "session.completed",
"id": "evt_3f7a9c0d82b1e54fa6c2d908",
"createdAt": "2026-04-17T08:42:11.284Z",
"data": {
"sessionId": "ses_0K7RfJ4m9xDqH3pN2vQwY8zB",
"assessmentType": "disc",
"status": "completed",
"candidate": {
"externalId": "cand_8a3c91",
"email": "priya@example.com",
"name": "Priya Sharma"
},
"results": {
"primaryType": "D",
"secondaryType": "I",
"type12": "Di",
"likertScores": {
"D": 4.2,
"I": 3.8,
"S": 2.4,
"C": 3.1
},
"bands": {
"D": "high",
"I": "moderate",
"S": "low",
"C": "moderate"
},
"reliabilityScore": 0.87,
"reliabilityLevel": "high"
},
"metadata": {
"campaign": "summer-hiring-2026",
"source": "careers-page"
},
"completedAt": "2026-04-17T08:42:11.284Z"
}
}session.failedfailedFires if scoring fails (malformed responses, engine error). data.results may carry an error object.
{
"event": "session.failed",
"id": "evt_3f7a9c0d82b1e54fa6c2d908",
"createdAt": "2026-04-17T08:42:11.284Z",
"data": {
"sessionId": "ses_0K7RfJ4m9xDqH3pN2vQwY8zB",
"assessmentType": "disc",
"status": "failed",
"candidate": {
"externalId": "cand_8a3c91",
"email": "priya@example.com",
"name": "Priya Sharma"
},
"results": null,
"metadata": {
"campaign": "summer-hiring-2026",
"source": "careers-page"
},
"completedAt": "2026-04-17T08:42:11.284Z"
}
}session.expiredexpiredFires when the session expiresAt passes without a successful submission.
{
"event": "session.expired",
"id": "evt_3f7a9c0d82b1e54fa6c2d908",
"createdAt": "2026-04-17T08:42:11.284Z",
"data": {
"sessionId": "ses_0K7RfJ4m9xDqH3pN2vQwY8zB",
"assessmentType": "disc",
"status": "expired",
"candidate": {
"externalId": "cand_8a3c91",
"email": "priya@example.com",
"name": "Priya Sharma"
},
"results": null,
"metadata": {
"campaign": "summer-hiring-2026",
"source": "careers-page"
},
"completedAt": null
}
}Submit Payloads
POST /api/v1/embed/submit accepts a different body per assessment type. The session token is read from the Authorization header.
assessmentType: discliveEach of the 15 questions needs one response (1–5 Likert) tagged with its DiSC dimension (D, I, S, or C).
{
"responses": [
{
"questionId": "q1",
"dimension": "D",
"response": 4
},
{
"questionId": "q2",
"dimension": "I",
"response": 3
},
{
"questionId": "q3",
"dimension": "S",
"response": 2
},
{
"questionId": "q4",
"dimension": "C",
"response": 5
}
]
}TFD Sessions
TFD and TFD+ require a published occupation config. Use GET /api/v1/tfd/configs to list available configs and pass the chosen occupationId when creating a session.
1. Fetch published configs
const res = await fetch('https://api.vegacapltd.com/api/v1/tfd/configs', {
headers: { Authorization: 'Bearer vegacap_sk_live_...' },
});
const { configs } = await res.json();
// configs: Array<{ id, occupationId, occupationName, mode, language, totalQuestions }>
// Example entry:
// { id: "cfg_...", occupationId: "occ_...", occupationName: "Welder",
// mode: "tfd", language: "en", totalQuestions: 32 }2. Create session with occupationId
const session = await vc.sessions.create({
assessmentType: 'tfd', // or 'tfd_plus'
candidate: { externalId: 'user-123', name: 'Sam' },
config: {
occupationId: configs[0].occupationId, // required — from step 1
},
});Omitting config.occupationId returns 400 invalid_config. Every org config maps to exactly one occupation and question pool.
Error codes
Every non-2xx response has { error: { code, message, doc_url?, details? } }. Map on code, not message.
invalid_api_key401Missing or malformed Authorization header.
origin_not_allowed403Publishable key used from an origin not in the allowlist.
insufficient_permissions403Key does not have the required scope for this action or assessment.
no_available_licenses402Organization is at capacity for this assessment type. Top up or switch types.
assessment_not_enabled403Assessment type is not enabled for this organization.
session_not_found404Session ID does not belong to this organization or has been soft-deleted.
session_already_completed409Submit was called on a session already in the completed state.
session_expired410Session ran past its expiresAt before submit was called.
idempotency_conflict409Idempotency-Key matched a soft-deleted session — pick a new key.
validation_error400Submit payload failed Zod validation or the per-assessment validity gate (e.g. FRA requires ≥90% of items answered).
not_implemented501Assessment type is not yet scored in live mode. Use a test key for end-to-end integration.
rate_limit_exceeded429Per-key sliding window exceeded. Honor X-RateLimit-Reset before retrying.
internal_error500Unexpected server error — retry with backoff, then contact support if persistent.
Stable contract: we won't rename an existing code. New codes may appear on the same HTTP status — handle the status as a fallback, then refine on the string code.
API Playground
Interactive, live reference rendered from our OpenAPI spec — try requests with your test key.
Changelog
Each entry is an additive release of the Platform API.
- @vegacap/node@0.1.0 — typed server-side client for the Platform API. Zero runtime deps, Apache-2.0.
- @vegacap/react@0.1.0 — VegaCapProvider context + useSession / useCreateSession / useEmbed hooks.
- @vegacap/sdk@0.1.0 — browser embed SDK. Install via npm or load from CDN at https://www.vegacapltd.com/sdk/v1/vegacap.js.
- FRA live scoring — the ≥90% items-answered validity gate surfaces as HTTP 400 (invalid_responses) instead of 500 when submissions are incomplete.
- Platform API sessions now visible in the org admin under the new "Platform API" sidebar item (test-mode sessions excluded).
- Added GET /api/v1/sessions list endpoint with filtering (status, type, candidate, date range).
- Added GET /api/v1/org/me for retrieving the authenticated org context.
- Added GET /api/v1/reports/{id}/pdf for streaming generated PDF reports.
- Added POST /api/v1/keys/{id}/rotate for zero-downtime API key rotation.
- Docs portal: event payload schemas, per-assessment submit examples, embedded Scalar playground.
- Platform API v1 public beta: 13 routes across sessions, reports, webhooks, and keys.
- Seven assessments wired (DiSC, TFD, TFD+ live; AAPA, FRA, LPM-VT, LPM-U behind 501 until scoring lands).
- Webhooks with HMAC-SHA256 signing, exponential retry (1m, 5m, 30m, 2h, 12h), and SSRF protection.
- Publishable + secret key pairs with test/live environments and per-key rate limits.