Developers
The public API and webhooks: what they cover, how they behave and what we promise about change.
OpenAPI document (JSON) · v0.537.0-beta
Authentication
Every request carries an API key as a bearer token: Authorization: Bearer <key>. An administrator of your organisation creates keys under Integrations, each with an explicit list of scopes and an optional expiry. A key only ever sees the data of the organisation it belongs to; an expired or revoked key answers 401, a key without the operation’s scope answers 403.
Limits
Each key has its own window of 120 requests a minute, and writes a tighter one of 30 a minute. Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; a 429 adds Retry-After.
Sandbox key
Try the API against synthetic data: a sandbox key reads every resource of the public demo, holds every read scope and nothing that writes, expires after 24 hours and allows 1000 requests a day.
Sandbox keys are issued on the public demo, which holds only synthetic data. Open the demo’s developer page
Conventions
- Every list answers { data, page: { nextCursor, hasMore } } and takes limit (default 50, at most 200), cursor and updatedSince. The walk is ascending by last change, so a client deduplicates on id.
- Writes accept an Idempotency-Key header: the same key with the same body replays the first answer, with a different body it is a 409. An invalid body answers 422 with one entry per field.
- Every failure is an RFC 9457 problem document (application/problem+json) with a stable type. Switch on type, never on the title or the detail text.
Endpoints
| Method | Path | Scope | What it does |
|---|---|---|---|
| GET | /api/v1/candidates | candidates:read | List candidates (mentees) |
| POST | /api/v1/candidates | candidates:write | Create a candidate |
| GET | /api/v1/candidates/{id} | candidates:read | Get a candidate |
| PATCH | /api/v1/candidates/{id} | candidates:write | Update a candidate |
| PATCH | /api/v1/relationships/{id}/stage | relationships:write | Move a relationship’s pipeline stage |
| GET | /api/v1/me | — | Who am I (connector auth test) |
| GET | /api/v1/hooks | hooks:write | List this key’s subscriptions |
| POST | /api/v1/hooks | hooks:write | Subscribe (REST hook) |
| DELETE | /api/v1/hooks/{id} | hooks:write | Unsubscribe |
| POST | /api/v1/exports | — | Export a whole resource (asynchronous) |
| GET | /api/v1/exports/{id} | — | Get an export |
| POST | /api/v1/imports/candidates | candidates:write | Import candidates in bulk (queued) |
| GET | /api/v1/imports/{id} | candidates:write | Get an import and its report |
| GET | /api/v1/relationships | relationships:read | List relationships |
| GET | /api/v1/relationships/{id} | relationships:read | Get a relationship |
| GET | /api/v1/interactions | interactions:read | List interactions |
| POST | /api/v1/interactions | interactions:write | Log an interaction |
| GET | /api/v1/interactions/{id} | interactions:read | Get an interaction |
| GET | /api/v1/meetings | meetings:read | List meetings |
| GET | /api/v1/meetings/{id} | meetings:read | Get a meeting |
| GET | /api/v1/companies | companies:read | List companies |
| GET | /api/v1/companies/{id} | companies:read | Get a company |
| GET | /api/v1/requisitions | hiring:read | List requisitions |
| GET | /api/v1/requisitions/{id} | hiring:read | Get a requisition |
| GET | /api/v1/offers | hiring:read | List offers |
| GET | /api/v1/offers/{id} | hiring:read | Get an offer |
| GET | /api/v1/interview-requests | hiring:read | List interview requests |
| GET | /api/v1/interview-requests/{id} | hiring:read | Get an interview request |
Error types
| type | HTTP status |
|---|---|
| invalid_api_key | 401 |
| insufficient_scope | 403 |
| org_unbound | 403 |
| feature_locked | 403 |
| invalid_link | 403 |
| company_key_not_allowed | 403 |
| not_found | 404 |
| validation_failed | 400 / 422 |
| idempotency_key_reused | 409 |
| idempotency_in_progress | 409 |
| limit_reached | 409 |
| duplicate_candidate | 409 |
| invalid_transition | 409 |
| stage_conflict | 409 |
| rate_limited | 429 |
| quota_exceeded | 429 |
Webhooks
A webhook is a POST of { event, data, sentAt } with an X-Signature header — the hex HMAC-SHA256 of the exact request body under the hook’s secret — plus X-Event and X-Delivery-Id. Verify the signature against the raw body before parsing it, and deduplicate on X-Delivery-Id: delivery is at least once.
Verifying a delivery (Node.js)
const { createHmac, timingSafeEqual } = require('node:crypto');
// rawBody: the request body exactly as received (string or Buffer), before JSON.parse.
function verifyWebhook(secret, rawBody, signatureHeader) {
const expected = Buffer.from(createHmac('sha256', secret).update(rawBody).digest('hex'), 'hex');
const given = Buffer.from(String(signatureHeader || ''), 'hex');
return given.length === expected.length && timingSafeEqual(given, expected);
}Events
application.created@1
Payload schema of data
{
"version": 1,
"description": "Someone applied to a mentor through the public application form.",
"schema": {
"type": "object",
"properties": {
"mentorId": {
"type": "string",
"minLength": 1
},
"menteeName": {
"type": "string"
},
"email": {
"type": "string"
}
},
"required": [
"mentorId",
"menteeName",
"email"
],
"additionalProperties": false
}
}pipeline.stage_change@1
Payload schema of data
{
"version": 1,
"description": "A mentorship moved to another pipeline stage (`from` and `to` are stage keys).",
"schema": {
"type": "object",
"properties": {
"relationId": {
"type": "string",
"minLength": 1
},
"from": {
"type": "string"
},
"to": {
"type": "string"
}
},
"required": [
"relationId",
"from",
"to"
],
"additionalProperties": false
}
}mentorship.created@1
Payload schema of data
{
"version": 1,
"description": "A mentor was assigned to a mentee.",
"schema": {
"type": "object",
"properties": {
"relationId": {
"type": "string",
"minLength": 1
},
"mentorId": {
"type": "string",
"minLength": 1
},
"menteeId": {
"type": "string",
"minLength": 1
},
"companyId": {
"type": [
"string",
"null"
],
"minLength": 1
}
},
"required": [
"relationId",
"mentorId",
"menteeId",
"companyId"
],
"additionalProperties": false
}
}interaction.logged@1
Payload schema of data
{
"version": 1,
"description": "An interaction (meeting, feedback, e-mail …) was logged on a mentorship.",
"schema": {
"type": "object",
"properties": {
"relationId": {
"type": "string",
"minLength": 1
},
"type": {
"type": "string"
},
"date": {
"type": "string",
"format": "date-time"
},
"autoLogged": {
"const": true
}
},
"required": [
"relationId",
"type",
"date"
],
"additionalProperties": false
}
}evaluation.added@1
Payload schema of data
{
"version": 1,
"description": "An evaluation was submitted for a mentorship.",
"schema": {
"type": "object",
"properties": {
"relationId": {
"type": "string",
"minLength": 1
},
"type": {
"type": "string"
},
"authorId": {
"type": "string",
"minLength": 1
}
},
"required": [
"relationId",
"type",
"authorId"
],
"additionalProperties": false
}
}meeting.scheduled@1
Payload schema of data
{
"version": 1,
"description": "One or more meetings were scheduled (a batch, an instant meeting or a series occurrence).",
"schema": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"scheduledAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"count": {
"type": "integer",
"minimum": 0
},
"instant": {
"const": true
},
"seriesId": {
"type": "string",
"minLength": 1
}
},
"required": [
"title",
"scheduledAt",
"count"
],
"additionalProperties": false
}
}meeting.rescheduled@1
Payload schema of data
{
"version": 1,
"description": "A meeting moved to another time.",
"schema": {
"type": "object",
"properties": {
"meetingId": {
"type": "string",
"minLength": 1
},
"scheduledAt": {
"type": "string",
"format": "date-time"
},
"previousScheduledAt": {
"type": [
"string",
"null"
],
"format": "date-time"
}
},
"required": [
"meetingId",
"scheduledAt",
"previousScheduledAt"
],
"additionalProperties": false
}
}meeting.cancelled@1
Payload schema of data
{
"version": 1,
"description": "A meeting (or `count` meetings of one batch) was called off.",
"schema": {
"type": "object",
"properties": {
"meetingId": {
"type": "string",
"minLength": 1
},
"scheduledAt": {
"type": [
"string",
"null"
],
"format": "date-time"
},
"count": {
"type": "integer",
"exclusiveMinimum": 0
}
},
"required": [
"meetingId",
"scheduledAt",
"count"
],
"additionalProperties": false
}
}interview_panel.completed@1
Payload schema of data
{
"version": 1,
"description": "Every interviewer on a panel has scored, or an admin closed it.",
"schema": {
"type": "object",
"properties": {
"panelId": {
"type": "string",
"minLength": 1
},
"subjectId": {
"type": "string",
"minLength": 1
}
},
"required": [
"panelId",
"subjectId"
],
"additionalProperties": false
}
}interview_panel.outcome@1
Payload schema of data
{
"version": 1,
"description": "A closed panel's decision was recorded.",
"schema": {
"type": "object",
"properties": {
"panelId": {
"type": "string",
"minLength": 1
},
"applicationId": {
"type": "string",
"minLength": 1
},
"outcome": {
"type": "string",
"enum": [
"ADVANCE",
"REJECT",
"OFFER"
]
}
},
"required": [
"panelId",
"applicationId",
"outcome"
],
"additionalProperties": false
}
}interview_request.scheduled@1
Payload schema of data
{
"version": 1,
"description": "An approved interview request got a meeting.",
"schema": {
"type": "object",
"properties": {
"requestId": {
"type": "string",
"minLength": 1
},
"meetingId": {
"type": "string",
"minLength": 1
},
"scheduledAt": {
"type": "string",
"format": "date-time"
},
"panelId": {
"type": [
"string",
"null"
],
"minLength": 1
}
},
"required": [
"requestId",
"meetingId",
"scheduledAt",
"panelId"
],
"additionalProperties": false
}
}interview_request.cancelled@1
Payload schema of data
{
"version": 1,
"description": "An interview request was called off.",
"schema": {
"type": "object",
"properties": {
"requestId": {
"type": "string",
"minLength": 1
},
"meetingId": {
"type": [
"string",
"null"
],
"minLength": 1
}
},
"required": [
"requestId",
"meetingId"
],
"additionalProperties": false
}
}offer.accepted@1
Payload schema of data
{
"version": 1,
"description": "The candidate accepted an offer.",
"schema": {
"type": "object",
"properties": {
"offerId": {
"type": "string",
"minLength": 1
},
"applicationId": {
"type": [
"string",
"null"
],
"minLength": 1
},
"requisitionId": {
"type": [
"string",
"null"
],
"minLength": 1
}
},
"required": [
"offerId",
"applicationId",
"requisitionId"
],
"additionalProperties": false
}
}offer.declined@1
Payload schema of data
{
"version": 1,
"description": "The candidate declined an offer.",
"schema": {
"type": "object",
"properties": {
"offerId": {
"type": "string",
"minLength": 1
},
"applicationId": {
"type": [
"string",
"null"
],
"minLength": 1
},
"requisitionId": {
"type": [
"string",
"null"
],
"minLength": 1
}
},
"required": [
"offerId",
"applicationId",
"requisitionId"
],
"additionalProperties": false
}
}requisition.filled@1
Payload schema of data
{
"version": 1,
"description": "An acceptance took the last opening of a requisition.",
"schema": {
"type": "object",
"properties": {
"requisitionId": {
"type": "string",
"minLength": 1
},
"offerId": {
"type": "string",
"minLength": 1
}
},
"required": [
"requisitionId",
"offerId"
],
"additionalProperties": false
}
}mentorship.lifecycle_changed@1
Payload schema of data
{
"version": 1,
"description": "A mentorship was paused, resumed, ended or reopened (`from` and `to` are lifecycle states).",
"schema": {
"type": "object",
"properties": {
"relationId": {
"type": "string",
"minLength": 1
},
"from": {
"type": "string"
},
"to": {
"type": "string"
},
"reasonCode": {
"type": [
"string",
"null"
]
}
},
"required": [
"relationId",
"from",
"to",
"reasonCode"
],
"additionalProperties": false
}
}application.stage_changed@1
Payload schema of data
{
"version": 1,
"description": "A candidate's application for a requisition moved to another stage (`from` and `to` are stage keys, e.g. SHORTLISTED → INTERVIEW).",
"schema": {
"type": "object",
"properties": {
"applicationId": {
"type": "string",
"minLength": 1
},
"requisitionId": {
"type": "string",
"minLength": 1
},
"from": {
"type": "string"
},
"to": {
"type": "string"
}
},
"required": [
"applicationId",
"requisitionId",
"from",
"to"
],
"additionalProperties": false
}
}mentorship.transferred@1
Payload schema of data
{
"version": 1,
"description": "A mentee's mentor changed — corrected in place, or a new pairing that carries the stage over.",
"schema": {
"type": "object",
"properties": {
"relationId": {
"type": "string",
"minLength": 1
},
"previousRelationId": {
"type": [
"string",
"null"
],
"minLength": 1
},
"menteeId": {
"type": "string",
"minLength": 1
},
"fromMentorId": {
"type": "string",
"minLength": 1
},
"toMentorId": {
"type": "string",
"minLength": 1
},
"mode": {
"type": "string",
"enum": [
"corrected",
"transferred"
]
},
"reasonCode": {
"type": "string"
}
},
"required": [
"relationId",
"previousRelationId",
"menteeId",
"fromMentorId",
"toMentorId",
"mode",
"reasonCode"
],
"additionalProperties": false
}
}offer.sent@1
Payload schema of data
{
"version": 1,
"description": "An offer was sent to the candidate.",
"schema": {
"type": "object",
"properties": {
"offerId": {
"type": "string",
"minLength": 1
},
"applicationId": {
"type": [
"string",
"null"
],
"minLength": 1
},
"requisitionId": {
"type": [
"string",
"null"
],
"minLength": 1
}
},
"required": [
"offerId",
"applicationId",
"requisitionId"
],
"additionalProperties": false
}
}offer.withdrawn@1
Payload schema of data
{
"version": 1,
"description": "A sent offer was withdrawn before the candidate decided.",
"schema": {
"type": "object",
"properties": {
"offerId": {
"type": "string",
"minLength": 1
},
"applicationId": {
"type": [
"string",
"null"
],
"minLength": 1
},
"requisitionId": {
"type": [
"string",
"null"
],
"minLength": 1
}
},
"required": [
"offerId",
"applicationId",
"requisitionId"
],
"additionalProperties": false
}
}offer.expired@1
Payload schema of data
{
"version": 1,
"description": "A sent offer passed its expiry date without a decision.",
"schema": {
"type": "object",
"properties": {
"offerId": {
"type": "string",
"minLength": 1
},
"applicationId": {
"type": [
"string",
"null"
],
"minLength": 1
},
"requisitionId": {
"type": [
"string",
"null"
],
"minLength": 1
}
},
"required": [
"offerId",
"applicationId",
"requisitionId"
],
"additionalProperties": false
}
}interview_request.created@1
Payload schema of data
{
"version": 1,
"description": "A company asked to interview a shortlisted candidate for a requisition.",
"schema": {
"type": "object",
"properties": {
"requestId": {
"type": "string",
"minLength": 1
},
"requisitionId": {
"type": "string",
"minLength": 1
},
"menteeId": {
"type": "string",
"minLength": 1
},
"companyId": {
"type": "string",
"minLength": 1
}
},
"required": [
"requestId",
"requisitionId",
"menteeId",
"companyId"
],
"additionalProperties": false
}
}interview_request.approved@1
Payload schema of data
{
"version": 1,
"description": "The programme approved an interview request; scheduling comes next.",
"schema": {
"type": "object",
"properties": {
"requestId": {
"type": "string",
"minLength": 1
},
"requisitionId": {
"type": "string",
"minLength": 1
},
"menteeId": {
"type": "string",
"minLength": 1
}
},
"required": [
"requestId",
"requisitionId",
"menteeId"
],
"additionalProperties": false
}
}interview_request.declined@1
Payload schema of data
{
"version": 1,
"description": "The programme declined an interview request (`reasonCode` is a decline code, never the note).",
"schema": {
"type": "object",
"properties": {
"requestId": {
"type": "string",
"minLength": 1
},
"requisitionId": {
"type": "string",
"minLength": 1
},
"menteeId": {
"type": "string",
"minLength": 1
},
"reasonCode": {
"type": "string"
}
},
"required": [
"requestId",
"requisitionId",
"menteeId",
"reasonCode"
],
"additionalProperties": false
}
}company_interest.changed@1
Payload schema of data
{
"version": 1,
"description": "A company flagged a candidate as interesting, shortlisted or passed (`from` and `to` are interest states).",
"schema": {
"type": "object",
"properties": {
"interestId": {
"type": "string",
"minLength": 1
},
"companyId": {
"type": "string",
"minLength": 1
},
"menteeId": {
"type": "string",
"minLength": 1
},
"requisitionId": {
"type": [
"string",
"null"
],
"minLength": 1
},
"from": {
"type": [
"string",
"null"
]
},
"to": {
"type": "string"
}
},
"required": [
"interestId",
"companyId",
"menteeId",
"requisitionId",
"from",
"to"
],
"additionalProperties": false
}
}Versioning and deprecation
Within v1 a change is additive only: new endpoints, new optional parameters, new fields and new error types may appear at any time, so ignore what you do not know. Removing or renaming a field, parameter or endpoint, changing a type or tightening validation is breaking: it is announced at least 6 months ahead and listed below, and a deprecated operation answers with Deprecation and Sunset headers. A change that cannot keep that promise ships as an opt-in parameter within v1, or as v2.
Announced deprecations
| What | Since | Removed on |
|---|---|---|
| `candidates` key on `GET /api/v1/candidates` (alias of `data`) | #1988 | 2027-01-31 |
Every shipped change, the API included, is dated in the release notes. Release notes