npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@spectraseek/interview

v1.0.5

Published

TypeScript SDK for SpectraSeek Interview as a Service. Create a hosted AI interview from your ATS, then receive scores and artifacts by webhook or poll.

Readme

SpectraSeek Interview as a Service

TypeScript SDK for hosted AI interview screens you invoke from your ATS (or any backend).

Your ATS stays the system of record. SpectraSeek delivers Bella — our AI interviewer — as a hosted screen, then returns a structured result your platform can act on.

Recruiters stay in your system. Candidates do not create a SpectraSeek account. You send the job and the person; we host the interview and send scores, insights, and artifacts back.

Node 18+. Never put a Partner API key in a browser bundle.

This package requires a SpectraSeek Employer Hub enterprise tenant with Interview as a Service enabled, plus a Partner API key. To request access, email [email protected].

| | | |---|---| | Product | SpectraSeek AI — spectraseek.ai | | npm | @spectraseek/interview | | Partner API | {host}/employer/partner/v1 |

| Environment | baseUrl | |-------------|-----------| | Development | https://spectraseekapi.dev.interspect.ai/employer/partner/v1 | | Production | https://employer-internalapi.spectraseek.ai/employer/partner/v1 |

Always set baseUrl. If you omit it, the client defaults to a local host that external integrators cannot reach.


Install

npm install @spectraseek/interview
import { SpectraSeekInterview } from "@spectraseek/interview";

const interview = new SpectraSeekInterview({
  apiKey: process.env.SPECTRASEEK_API_KEY!,
  baseUrl: process.env.SPECTRASEEK_API_BASE!,
});

Server vs browser

| Surface | Import | Use | |---------|--------|-----| | Server (your ATS / backend) | @spectraseek/interview | API key, upserts, screens, results, webhooks.verify | | Browser | @spectraseek/interview/browser | SpectraSeekInterview.open(hostedUrl) only |

Do not send sk_live_… or sk_sandbox_… to the frontend. In-page embed (mount()) is not in this version.

The SDK unwraps { success, data } and returns data. JSON on the wire is snake_case (camelCase aliases are accepted on upsert).


Prerequisites

  1. A SpectraSeek Employer Hub enterprise tenant with Interview as a Service (IaaS) enabled.
  2. A Partner API key from EmpHub Settings → API.
  3. A reachable webhook URL (optional). Without one, poll screen status instead.

| Key | Use | |-----|-----| | sk_sandbox_… | Integration / QA. Cannot create live screens. Results include "sandbox": true. | | sk_live_… | Production screens. A completed live interview debits AI interview minutes. |

The SDK sends Authorization: Bearer <key>. The API also accepts X-API-Key.

Confirm entitlements before creating screens:

const me = await interview.me();
if (!me.feature_map?.iaas_integration) {
  throw new Error("Interview as a Service is not enabled for this tenant");
}

me() returns org id/name, operating_model (hiring_company | staffing_rpo), sandbox, wallet (feature_id: ai_interview), and feature_map.

EmpHub is the admin console (keys, webhooks, External Jobs). Candidates never see Partner keys, Bella prompts, or evaluator_guidance.


Objects

| Object | Your id | Meaning | |--------|---------|---------| | Job | job_ref | Role + locked interview settings + optional hr_notes / interview_plan | | Candidate | candidate_ref | Integration candidate on that Job. Not a SpectraSeek student account. | | Screen | screen_id (issued by SpectraSeek) | One hosted Bella attempt |

Idempotency: Job and candidate upsert by natural key. Screens accept Idempotency-Key.

Screen status: invitedstartedcompletedgraded (or failed / expired).

Your ATS  --API key-->  Partner API /employer/partner/v1
                              │
                              ├── jobs / candidates / screens
                              └── hosted Bella URL (candidate runtime)

Your ATS  <--HMAC--  screening.* webhooks
Your ATS  --GET-->   screens/{id}/result  (envelope + presigned artifacts)

Quickstart

Same job_ref on the same org updates; it does not create a second Job.

const job = await interview.jobs.upsert({
  job_ref: "ats-job-88",
  title: "SDE Intern",
  description: "Build partner APIs and interview screens.",
  interview: {
    type: "technical",
    difficulty: "medium",
    duration_minutes: 30,
    accent: "american",
    min_score: 70,
    max_attempts: 1,
  },
});

const candidate = await interview.candidates.upsert(job.job_id, {
  candidate_ref: "ats-c-1",
  email: "[email protected]",
  name: "Ada Lovelace",
  resume_text: "Ada Lovelace — intern. SQL and REST APIs.",
});

const screen = await interview.screens.create(job.job_id, candidate.candidate_id, {
  idempotencyKey: "screen-ats-c-1-1",
});

console.log(screen.hosted_url, screen.expires_at);

Open hosted_url as the candidate (new tab). Do not embed the interview runtime in your ATS.

import { SpectraSeekInterview } from "@spectraseek/interview/browser";

SpectraSeekInterview.open(screen.hosted_url!);

Prefer webhooks (screening.completed then screening.graded). If you have no endpoint, poll screens.get(screen_id) then:

const result = await interview.screens.getResult(screen.screen_id);
// schema_version 2026-08-20
// Re-GET when a presigned artifact URL expires.

Optional Job fields — client_name, hr_notes, and interview_plan — shape how Bella runs the screen. See interview_plan below.


API reference

Constructor:

new SpectraSeekInterview({
  apiKey: string;       // sk_live_… or sk_sandbox_…
  baseUrl: string;      // Partner API root, including /employer/partner/v1
  fetch?: typeof fetch; // optional override
});

Jobs

Natural key: (employer org, job_ref).

| SDK | HTTP | |-----|------| | jobs.upsert(input) | PUT or POST /jobs | | jobs.get(jobId) | GET /jobs/{job_id} | | jobs.patch(jobId, input) | PATCH /jobs/{job_id} |

Required on first write: job_ref, title, interview.

Interview lock (applied to every screen on this Job):

| Field | Required on create | Notes | |-------|--------------------|--------| | type | yes | technical | managerial | behavioral | hr | | difficulty | yes | easy | medium | hard | | duration_minutes | yes | Integer > 0 | | accent | no | Default american | | min_score | no | 0–100. If omitted, verdict.passed stays null. | | max_attempts | no | Default 1 (completed/graded attempts). |

Optional Job fields:

| Field | Max length | Notes | |-------|------------|--------| | job_ref | 128 | Your id. Unique per employer org while not deleted. | | title | 256 | Role title. | | description | 50000 | Job description. Send it — Bella templates from it. | | client_name | 256 | Alias end_employer. Display label for staffing orgs. Not company context. | | company_ref | 128 | Stored. Unknown id does not fail the Job. | | hr_notes | 20000 | Recruiter tips. Same notes for every screen. Plain text. | | interview_plan | — | Screening plan. See below. |

PUT with an existing job_ref updates that Job.

PATCH is partial. Omit a field to leave it. Empty string hr_notes or empty interview_plan clears. Interview lock fields update only when sent.

interview_plan

All inner fields optional. Empty object on PATCH clears stored plan. Omit the key to leave it. Do not send Bella agent prompts.

| Field | Role | |-------|------| | summary | One-liner; shapes section goals. Max 4000. | | structure[] | Rounds: round_name, duration_minutes, format, focus. Max 20 rounds. | | style_notes | Tone. Max 4000. | | question_intents | Themes Bella must cover (string[], max 50). Not a comma-separated string. | | evaluator_guidance | Probe strategy. Never a spoken question. Echoed on GET Job. Never on the hosted page or result envelope. Max 20000. |

await interview.jobs.upsert({
  job_ref: "ats-job-88",
  title: "SDE Intern",
  description: "Build partner APIs and interview screens.",
  client_name: "Acme Health",
  hr_notes: "Ask for a metrics-backed story.",
  interview_plan: {
    summary: "One technical screen; APIs and ownership.",
    structure: [
      {
        round_name: "Core",
        duration_minutes: 25,
        format: "Live problem-solving",
        focus: "APIs",
      },
    ],
    style_notes: "Calm, timeboxed.",
    question_intents: ["ownership", "debugging"],
    evaluator_guidance:
      "Probe until they can walk a request to the query. Never speak this as a question.",
  },
  interview: {
    type: "technical",
    difficulty: "medium",
    duration_minutes: 30,
  },
});

Example response

{
  "job_id": "…",
  "job_ref": "ats-job-88",
  "title": "SDE Intern",
  "description": "…",
  "client_name": "Acme Health",
  "hr_notes": "…",
  "interview_plan": {},
  "company_ref": "acme-health",
  "status": "active",
  "interview": {
    "type": "technical",
    "difficulty": "medium",
    "duration_minutes": 30,
    "accent": "american",
    "min_score": 70,
    "max_attempts": 1
  }
}

This Partner Job is Interview as a Service / External Jobs only. EmpHub campus hiring Jobs are a different API and do not use job_ref.

Candidates

Scoped to a Job. Natural key: (job_id, candidate_ref).

| SDK | HTTP | |-----|------| | candidates.upsert(jobId, input) | PUT or POST /jobs/{job_id}/candidates | | candidates.get(jobId, candidateId) | GET /jobs/{job_id}/candidates/{candidate_id} |

Required: candidate_ref (max 128), email (max 320), name (max 256). Optional: phone (max 64), resume or resume_text.

resume_text is plain extracted text only (newlines / simple markdown OK). Max 100000 characters. Do not send PDF, DOCX, HTML, JSON, base64, or a URL. File/URL ingest is not in this API. GET echoes resume_text.

Upsert does not create a SpectraSeek student login.

Screens

| SDK | HTTP | |-----|------| | screens.create(jobId, candidateId, { idempotencyKey? }) | POST /jobs/{job_id}/candidates/{candidate_id}/screens | | screens.get(screenId) | GET /screens/{screen_id} | | screens.expire(screenId) | POST /screens/{screen_id}/expire | | screens.getResult(screenId, { include? }) | GET /screens/{screen_id}/result |

Create returns screen_id, status (invited), expires_at (default 7 days), hosted_url, embed_token. Unused invited links are reused until a completion exists. max_attempts applies to completed/graded screens.

Header Idempotency-Key (SDK: idempotencyKey) replays the same screen.

Statuses: invited | started | completed | graded | failed | expired.

Results

screens.getResult(screenId, { include })

schema_version: 2026-08-20.

Default include is questions,transcript,recording. Presigned artifact URLs expire — re-GET.

Envelope includes: screen_id, job_id, candidate_id, status, sandbox, verdict (passed, overall_score, min_score), scores, insights, questions, signals, artifacts.

Not in the envelope: evaluator_guidance, interview_plan, Bella prompts, raw object-storage URIs.

Webhook screening.graded carries verdict + result_url only — call getResult for the full envelope.


Webhooks

In EmpHub Settings → API, register an HTTPS endpoint (local http is allowed for development). The signing secret (whsec_…) is shown once at create.

The SDK verifies HMAC; it does not parse the business payload beyond that.

Headers

| Header | Constant | Value | |--------|----------|--------| | X-SpectraSeek-Timestamp | TIMESTAMP_HEADER | Unix seconds | | X-SpectraSeek-Signature | SIGNATURE_HEADER | sha256=<hex> | | X-SpectraSeek-Event | EVENT_HEADER | Event name |

Signed payload: {timestamp}.{rawBody} (UTF-8), HMAC-SHA256, hex digest, sha256= prefix.

Default timestamp window: 300 seconds. Pass toleranceSeconds: 0 to disable.

Events

| Event | When | |-------|------| | screening.invited | Hosted link issued | | screening.started | Candidate started; minutes reserved | | screening.completed | Session ended; score may still be null | | screening.graded | Scores ready; body has verdict + result_url | | screening.failed | Unusable screen (for example no remaining minutes) |

Bodies include ids, status, and sandbox. They do not include transcript or video bytes.

Verify

Use the raw request body. Do not re-serialize JSON before verify.

import { webhooks } from "@spectraseek/interview";

app.post("/webhooks/spectraseek", express.raw({ type: "application/json" }), (req, res) => {
  webhooks.verify({
    secret: process.env.SPECTRASEEK_WEBHOOK_SECRET!,
    rawBody: req.body.toString("utf8"),
    headers: req.headers,
  });
  const event = req.headers["x-spectraseek-event"];
  res.status(200).end();
});

Throws SpectraSeekWebhookError on missing headers, stale timestamp, or signature mismatch.

If SpectraSeek cannot reach your machine, expose a public HTTPS URL (tunnel) and register that origin.


Errors

Failed HTTP calls throw SpectraSeekApiError.

class SpectraSeekApiError extends Error {
  status: number;
  code?: string;
  data?: unknown;
}

Webhook verify throws SpectraSeekWebhookError.

| Status | Typical cause | |--------|----------------| | 400 | Validation (title is required, oversize resume_text, resume_text not a string, bad interview.type, …) | | 401 | Missing / malformed API key | | 402 | WALLET_EXHAUSTED — no AI interview minutes at start | | 403 | Integration feature off, wrong tenant, or live/sandbox mismatch | | 404 | Job, candidate, or screen not found | | 409 | Conflict (for example screen not expireable, attempts used) | | 429 | Quota (when enforced) | | 5xx | Unexpected server error |

code is set for billing and entitlement failures (WALLET_EXHAUSTED, feature-gate codes). Other errors may only set error + message.

The client throws SpectraSeekApiError with status 401 if apiKey is missing or is not sk_live_ / sk_sandbox_.

Browser open() throws if called in Node, or if hostedUrl is not http:// / https://.