Developers

Connect Screenwell to the tools you already use

Webhooks for every scorecard and stage change, a REST API for your jobs and candidates, and an MCP server so Claude, ChatGPT and other AI assistants can work with Screenwell for you.

On every plan and in the free trial

API keys

Webhooks, the API and the MCP server for scripts use your account’s API key. You’ll find it in the app under Settings → API & webhooks. Claude and ChatGPT connectors don’t need a key: you sign in with Screenwell instead.

  • You have a primary and a secondary key, so you can switch to a new key without downtime.
  • A key acts as you: it sees your jobs and your team’s, and nothing else.
  • Keep it on your server. Never put it in a web page or a mobile app.

Zapier

Connect Screenwell to your ATS, Slack, Google Sheets and 7,000+ other apps, without writing code. Every trigger fires the moment it happens in Screenwell.

Set it up

  1. 1Copy your API key from the app: Settings → API & webhooks.
  2. 2Open Screenwell on Zapier with the button below and accept the invite.
  3. 3Create a Zap, pick Screenwell and paste your API key when Zapier asks for it.
  4. 4Choose a trigger or an action, test it and turn the Zap on.
Open Screenwell on Zapier

Triggers

Interview Scored
Luna finished an interview and the scorecard is ready: score, recommendation, summary and a link to the scorecard.
Candidate Moved to Stage
A candidate moved between board stages. Optionally only for one stage, such as Shortlisted.
New Candidate
A candidate was added, in the app, through the API or from the interview link.
New Job
A job was created.
Interview Completed
An interview ended, before the scorecard is ready.

Actions and searches

Add Candidate to Job
Name, email, phone and an optional CV file.
Send Interview Invite
Emails the candidate the interview link. Finds the candidate by email, or adds them.
Schedule Meet/Teams Interview
Books Luna into a Google Meet or Microsoft Teams interview.
Update Candidate Stage
Moves a candidate to New, Invited, Interviewed, Shortlisted, Hired or Not a fit.
Create Job
Title, description, company and interview language.
Find Candidate
By email, optionally within one job.
Find Job
By title.

Ideas

  • New applicant in Workable, Recruitee, Teamtailor or Greenhouse → Send Interview Invite
  • Interview Scored → add a note and move the stage in your ATS
  • Interview Scored → message the hiring channel in Slack
  • Interview Scored → a new row in Google Sheets

Webhooks

Screenwell sends a signed POST with a JSON body to your URL when something happens. Point it at your ATS, a Zapier catch hook or your own endpoint.

Set it up

  1. 1In the app, open Settings → API & webhooks and add your webhook URL. It must be a public https address.
  2. 2Copy the signing secret that appears after the first save. You need it to check signatures.
  3. 3Press “Send test event” to get a sample interview.scored with test: true.

You can also set the URL with the API: PUT /api/v1/settings/webhook {"webhookUrl": "https://…"}.

Events

interview.scored
Luna finished an interview and the scorecard is ready: the score out of 100, her decision and how sure she is, her reasoning and a link to the scorecard.
candidate.stage_changed
A candidate moved between board stages, with where from, where to and who moved them: luna after the interview, or recruiter when someone on your team did.

Accounts that used CV Parsing keep receiving its older events (cv_parsed, candidate_created and others).

What a delivery looks like

Every delivery has the same envelope: id, eventType, timestamp, userId and data. Test events also carry test: true.

interview.scored
{
  "id": "evt_01J9ZK4T7Q2M8C6V",
  "eventType": "interview.scored",
  "timestamp": "2026-09-26T09:44:12.000Z",
  "userId": "usr_4f1c",
  "data": {
    "candidate": { "id": "cand_8f2c", "name": "Maya Kowalski", "email": "maya@example.com" },
    "job": { "id": "job_31a0", "title": "Senior Frontend Engineer" },
    "interview": { "id": "int_77b1", "finishedAt": "2026-09-26T09:43:50.000Z", "durationSeconds": 1920 },
    "score": 84,
    "decision": "hire",
    "confidence": 82,
    "summary": "Strong frontend engineer who led a design system migration.",
    "reasoning": "Explains her trade-offs clearly and meets 4 of 5 requirements.",
    "scorecardUrl": "https://<app-host>/interviews/int_77b1",
    "integrity": { "level": "none" }
  }
}
candidate.stage_changed
{
  "id": "evt_01J9ZK4VB1XK0D3R",
  "eventType": "candidate.stage_changed",
  "timestamp": "2026-09-26T09:44:13.000Z",
  "userId": "usr_4f1c",
  "data": {
    "candidate": { "id": "cand_8f2c", "name": "Maya Kowalski", "email": "maya@example.com" },
    "job": { "id": "job_31a0", "title": "Senior Frontend Engineer" },
    "from": "invited",
    "to": "shortlisted",
    "movedBy": "luna",
    "changedBy": "rule",
    "reason": "Moved by rule: score 84 ≥ 75",
    "at": "2026-09-26T09:44:13.000Z"
  }
}

Headers

X-Screenwell-Event
The event type, e.g. interview.scored.
X-Screenwell-Event-Id
The event id. It stays the same on every retry, so use it to skip events you’ve already handled.
X-Screenwell-Signature
t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>">, keyed with your signing secret.

Check the signature

  1. 1Read the raw request body, before any JSON parsing.
  2. 2Compute the HMAC-SHA256 of "<t>.<raw body>" with your signing secret, as hex.
  3. 3Compare it with v1 in constant time, and reject requests whose t is more than 5 minutes old.
Node.js (Express)
import crypto from 'node:crypto'
import express from 'express'

const app = express()
const SECRET = process.env.SCREENWELL_WEBHOOK_SECRET // whsec_...

// Keep the raw body: the signature is over the exact bytes we sent.
app.post('/webhooks/screenwell', express.raw({ type: 'application/json' }), (req, res) => {
  const header = req.get('X-Screenwell-Signature') ?? ''
  const { t, v1 } = Object.fromEntries(header.split(',').map((part) => part.split('=')))
  const expected = crypto.createHmac('sha256', SECRET).update(`${t}.${req.body}`).digest('hex')

  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 5 * 60
  const valid = v1?.length === expected.length && crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))
  if (!fresh || !valid) return res.status(400).send('bad signature')

  const event = JSON.parse(req.body)
  // Skip events you have already handled: event.id stays the same on retries.
  console.log(event.eventType, event.data)
  res.sendStatus(200)
})

app.listen(3000)
Python (Flask)
import hashlib, hmac, json, os, time
from flask import Flask, request, abort

app = Flask(__name__)
SECRET = os.environ["SCREENWELL_WEBHOOK_SECRET"].encode()  # whsec_...

@app.post("/webhooks/screenwell")
def screenwell():
    header = request.headers.get("X-Screenwell-Signature", "")
    parts = dict(p.split("=", 1) for p in header.split(",") if "=" in p)
    t, v1 = parts.get("t", "0"), parts.get("v1", "")

    body = request.get_data()  # the raw bytes, before any JSON parsing
    expected = hmac.new(SECRET, f"{t}.".encode() + body, hashlib.sha256).hexdigest()

    if abs(time.time() - int(t)) > 300 or not hmac.compare_digest(v1, expected):
        abort(400)

    event = json.loads(body)
    # Skip events you have already handled: event["id"] stays the same on retries.
    print(event["eventType"], event["data"])
    return "", 200

Retries

Answer with any 2xx status within 10 seconds. If your endpoint fails or doesn’t answer, Screenwell tries again after 1 minute, 5 minutes, 30 minutes and 2 hours. Settings → API & webhooks shows the latest deliveries with their status and your endpoint’s reply.

  1. 0
  2. +1m
  3. +5m
  4. +30m
  5. +2h

REST API

A JSON API for the jobs, candidates and interviews in your account. Send your key in the X-API-KEY header. Answers look like {"status": "success", "data": …}.

Base URL
https://api.screenwell.ai/api/v1
Authentication
X-API-KEY: <API key>

Jobs

  • GET/jobsYour jobs and your team’s.
  • GET/jobs/:idOne job with its description and skills.
  • POST/jobsCreate a job: title, company, place, description and more.
  • PUT/jobs/:idChange a job’s details.

Candidates

  • POST/jobs/:id/candidatesAdd a candidate to a job from their CV: a file upload (file), a link (fileUrl) or base64.
  • POST/jobs/:id/candidates/batchAdd several CVs to a job at once (fileUrls or base64Files).
  • GET/candidates/:idA candidate with their CV details, status and Luna’s analysis.
  • PUT/candidates/:id/statusMove a candidate, e.g. to selected (Shortlisted), hired or rejected.
  • GET/candidates/:id/cvA link to the candidate’s CV file.

Interviews and scorecards

  • GET/meetingsInterviews and calls, newest first. Filter with isInterview=true, limit and offset.
  • GET/meetings/:id/interview-insightsThe scorecard of an interview: score, recommendation, strengths, concerns and evidence.
  • GET/meetings/:id/transcriptThe interview transcript.

Account

  • GET/quotaHow many interviews you have left this month.
  • GET · PUT/settings/webhookRead or set your webhook URL.

Candidate statuses

The board stages map to these statuses: New = applied, Invited = interview_scheduled, Interviewed = interviewed, Shortlisted = selected, Hired = hired, Not a fit = rejected. Changing a status never emails the candidate.

Example: add a candidate from a CV

curl
curl -X POST https://api.screenwell.ai/api/v1/jobs/job_31a0/candidates \
  -H "X-API-KEY: $SCREENWELL_API_KEY" \
  -F "file=@maya-kowalski-cv.pdf"

# or from a link
curl -X POST https://api.screenwell.ai/api/v1/jobs/job_31a0/candidates \
  -H "X-API-KEY: $SCREENWELL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"fileUrl": "https://example.com/cv/maya.pdf"}'
curl
curl -X PUT https://api.screenwell.ai/api/v1/candidates/cand_8f2c/status \
  -H "X-API-KEY: $SCREENWELL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"status": "selected"}'

MCP server

Work with Screenwell from Claude, ChatGPT, Cursor or any MCP client: “Who did Luna shortlist this week?”, “Add these 5 CVs to the Backend role”, “Send Tomas the interview link”. The assistant acts as you, with the same access you have in the app.

Endpoint
POST https://api.screenwell.ai/mcp
Authorization
Bearer <sign-in token or API key>

From claude.ai or ChatGPT: sign in with Screenwell

No key to copy. Add Screenwell as a connector with the server URL, then sign in with your Screenwell account and approve the access.

claude.ai (web, desktop and mobile)

  1. 1Open Settings → Connectors → Add custom connector. On Team and Enterprise plans, an owner adds it first under Organization settings.
  2. 2Name it Screenwell and enter the server URL. Leave the OAuth client ID and secret empty.
  3. 3Click Connect. Screenwell opens: log in, check the access and click Allow. Then turn Screenwell on from the tools menu in a chat.

ChatGPT

  1. 1Open Settings → Apps & Connectors → Advanced settings and turn on Developer mode. Business, Enterprise and Edu workspaces need an admin to allow it.
  2. 2Choose Create, name it Screenwell, enter the server URL and pick OAuth as the authentication.
  3. 3Screenwell opens: log in, check the access and click Allow. Then pick the connector in a chat.
Endpointhttps://api.screenwell.ai/mcp

The Screenwell page shows which app is asking, the address it sends you back to, and the access it asks for: read only, or read and act (add, move and email candidates, always after your yes).

Every connected app is listed in Settings → API & webhooks → Connected apps. Disconnect cuts it off on its next request.

With an API key: Claude Code, Cursor and scripts

Tools that let you set a header can use your API key instead. Claude Code can also sign in: add the server without a header, then run /mcp and choose Authenticate.

Claude Code
# sign in with Screenwell: add the server, then run /mcp and choose Authenticate
claude mcp add --transport http screenwell https://api.screenwell.ai/mcp

# or with an API key
claude mcp add --transport http screenwell https://api.screenwell.ai/mcp \
  --header "Authorization: Bearer <API key>"

# read-only
claude mcp add --transport http screenwell-read https://api.screenwell.ai/mcp \
  --header "Authorization: Bearer <API key>" \
  --header "X-Screenwell-Scope: read"
Claude Desktop · claude_desktop_config.json
{
  "mcpServers": {
    "screenwell": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://api.screenwell.ai/mcp", "--header", "Authorization: Bearer ${SCREENWELL_KEY}"],
      "env": { "SCREENWELL_KEY": "<API key>" }
    }
  }
}

Claude Desktop connects through mcp-remote, which adds the key header for you.

Cursor, VS Code, the MCP Inspector and scripts work with the same URL, using an API key header or signing in.

With an API key, add the header X-Screenwell-Scope: read for a read-only connection. The assistant can then look things up, but not add, move or email anyone.

Tools

Look things up

  • list_jobsYour jobs with the candidates per stage and whether the interview link is on.
  • get_jobA job with Luna’s interview plan, the interview link and the pipeline.
  • list_candidatesCandidates by job, stage or name, with their latest score and who moved them last.
  • get_candidateOne candidate: contact details, Luna’s briefing, CV summary, scorecard and hiring-manager feedback.
  • get_scorecardThe full scorecard: score out of 100, decision, strengths, gaps and key moments.
  • ask_lunaAsk Luna, your hiring advisor, a question about your candidates.

Take action

  • add_candidateAdd a candidate from their details or a CV (PDF or Word). Luna reads the CV first.
  • move_candidateNeeds your yesMove a candidate to another stage.
  • add_noteAdd a note for your team to a candidate.
  • send_interview_linkNeeds your yesEmail the job’s interview link to up to 50 candidates.

Nothing reaches a candidate without your yes

  • Sending the interview link, and moving someone to Hired or Not a fit, first returns a preview (“This will email 3 candidates: …”). Nothing happens until the assistant asks you and you agree.
  • The assistant only sees what you see in the app: your jobs and your team’s. It works on any plan and during the trial, the same as the API.
  • Up to 60 tool calls a minute per key. Reading CVs and Ask Luna have their own hourly limits.

Building something bigger?

Tell us what you’d like to connect. We’ll help you set it up.

Talk to us