Skip to content

For developers

API documentation.

Connect Evaluro to your ATS: create candidates and screenings, fetch results and receive them by webhook.

Updated 29 September 2026

Quick start.

  1. In the Evaluro app, go to Integrations and create an API key (company owners and admins only). Then copy a position ID from GET /positions.
  2. Do a dry run first: POST /intake with "dryRun": true runs every check and tells you what would happen. Nothing is created or sent.
  3. Add your webhook URL and click Send test. Every delivery attempt appears under “Webhook deliveries”, with the reason if it failed.
curl https://hireup.inflexion.cz/api/v1/intake \
  -H "x-api-key: hu_…" -H "content-type: application/json" \
  -d '{ "positionId": "…", "dryRun": true,
        "candidate": { "fullName": "Eva Novak", "email": "eva@example.com", "externalRef": "ats-123" },
        "cvText": "…CV text, at least 100 characters…" }'

200 { "dryRun": true, "wouldCreate": "candidate_and_screening", "cv": "text",
      "candidate": { "existing": false, "candidateId": null, "profileReady": false },
      "warnings": [] }

Basics.

  • Base URL: https://hireup.inflexion.cz/api/v1, JSON over HTTPS. Machine-readable specification: OpenAPI 3.1.
  • Authenticate with the x-api-key header. Keys are issued by the company owner or an admin in the Evaluro app (Integrations) and belong to the company, not to a person. Each key sees only its own company’s data.
  • Rate limit: 10,000 requests per 24 hours per key (after that, 429). Don’t poll for results; use a webhook.
  • Errors return JSON { "error": "code" } with status 400, 401, 404 or 409. Error codes are in English; human-readable texts in responses (validation messages and dry-run warnings) are in Czech.
curl https://hireup.inflexion.cz/api/v1/positions \
  -H "x-api-key: hu_…"

The fastest way in: one call.

Your ATS webhook calls POST /intake as soon as a candidate applies through a job ad. Send the position, the candidate and the CV as exactly one of cvText, cvPdfBase64 (PDF up to 3 MB) and cvDocxBase64 (Word .docx up to 3 MB; we use only the text and do not store the file). We do the rest: the CV profile, the screening and the email invitation ("sendInvite": false turns the email off).

POST https://hireup.inflexion.cz/api/v1/intake
{ "positionId": "…",
  "candidate": { "fullName": "Eva Novak", "email": "eva@example.com", "externalRef": "ID in your ATS" },
  "cvPdfBase64": "JVBERi0x…" }

202 { "candidateId": "…", "status": "waiting_for_profile", "existingCandidate": false }
    // or "screening_created" + screeningId (+ inviteUrl when newly created)
  • The CV profile usually takes up to 30 seconds. Until then, the status is waiting_for_profile. We create the screening as soon as the profile is ready and let you know with the screening.created webhook.
  • Repeating the call with the same externalRef does not create a duplicate. It returns the existing candidate (and their live screening). inviteUrl is returned only when something is newly created.
  • The whole request can be at most 4.5 MB, so keep PDF and Word files under 3 MB. Send a larger CV as cvText.
  • "dryRun": true turns on test mode: the same checks (key, position, rubric, CV) and a 200 response describing what would happen (wouldCreate, warnings). Nothing is saved or sent, which makes it useful for debugging the connection from your ATS.

No code: Make, Zapier, n8n.

If your ATS can send a webhook or has an app in Make or Zapier, you can connect it without a developer:

  1. Trigger in your ATS: a new candidate, or a move to a “Screening” stage (Recruitis: the answer.add / answer.flow_change webhook; Datacruit: Zapier “New/Updated Status”; Teamtailor, Workable: a webhook or app).
  2. HTTP module: POST https://hireup.inflexion.cz/api/v1/intake, the x-api-key header and the body as above. Use the application ID from your ATS as externalRef, so a repeated run does not create a duplicate.
  3. Results back: create a “custom webhook” in Make or Zapier, add its URL in the Evaluro app, and use the screening.completed event to write a note to your ATS with the recommendation, the summary and the reportUrl link.

If your tool cannot verify the HMAC signature, at least check each message by calling GET /screenings/{screeningId} with your key. That way you catch forged messages. Write the recommendation to your ATS, not an automatic rejection: a person decides on the candidate.

Step by step.

  1. GET /positions: active positions with an approved rubric. Each one also includes visualMode (avatar = AI avatar, orb = animated assistant with no face), the interview format a new screening for the position gets. It cannot be set through the API. You change it on the position in the Evaluro app, and only until candidates have been invited to the position (every candidate for a position gets the same conditions).
  2. POST /candidates: a candidate with their CV text. The profile is usually ready within 30 seconds (profileReady).
  3. POST /screenings: a screening and the candidate’s interview link (inviteUrl, returned only in this response). With "sendInvite": true, we email the invitation for you.
  4. When the interview is complete, the screening.completed webhook arrives with the recommendation and a link to the report. A recruiter makes the decision in Evaluro.

Endpoints.

get/api/v1/me

200 The company the API key belongs to

get/api/v1/positions

The company’s active positions (with an approved rubric)

200 List of positions

post/api/v1/candidates

Create a candidate (a profile is prepared from the CV text, usually within 30 s)

FieldTypeDescription
fullName*string
email*string (email)
cvText*stringCV text (min. 100 characters). Send a PDF or Word file in base64 through POST /intake.
externalRefstring

201 Candidate created409 A candidate with the same externalRef already exists

post/api/v1/intake

One-step intake from your ATS: candidate + CV → screening and invitation

One call for an ATS webhook. Creates the candidate (or finds them by `externalRef`), prepares a profile from the CV and creates a screening with an email invitation. If the profile is ready, the screening is created immediately (the response includes `inviteUrl`). Otherwise the status is `waiting_for_profile` and the screening is created within a few minutes; your ATS is notified by the `screening.created` webhook. The position must be active and have an approved rubric.

FieldTypeDescription
positionId*string (uuid)
candidate*object
cvTextstringCV text. Send exactly one of `cvText`, `cvPdfBase64` and `cvDocxBase64`.
cvPdfBase64stringCV as a PDF in base64 (max. 5 MB after decoding; stored in encrypted storage).
cvDocxBase64stringCV as a Word file (.docx) in base64, max. 5 MB. We use only its text and do not store the file. The older .doc format is not supported.
variantdemo | short | medium | long
languagecs | en
sendInvitebooleanWe email the invitation to the candidate (default). Otherwise, the API returns the link only when the screening is created synchronously.
dryRunbooleanTest mode: runs every check and returns what would happen (200), but creates and sends nothing. For debugging your integration.

200 Test mode (`dryRun`): what would happen202 Accepted400 Invalid input or file (invalid_pdf, pdf_too_large, invalid_docx, docx_too_large, docx_empty)403 Screening length (`variant`) is not included in the company’s plan (variant_not_in_tariff)404 Position not found409 Position is not active or has no approved rubric503 PDF upload is unavailable (pdf_unavailable); send cvText

get/api/v1/candidates/{id}

A candidate and the status of their profile

Path parameters: id

200 Candidate404 Candidate not found

delete/api/v1/candidates/{id}

Delete a candidate and all their data (CV, screenings, recordings, assessments)

For candidates’ erasure requests (Article 17 GDPR) and clean-up in your ATS. Irreversible. Otherwise, data is deleted automatically after the retention period set by the company.

Path parameters: id

204 Deleted404 Candidate not found

post/api/v1/screenings

Create a screening and get the candidate’s interview link

The `inviteUrl` link is returned only in this response (we store only its hash). Send it to the candidate. When the interview is complete, the `screening.completed` webhook is sent.

FieldTypeDescription
positionId*string (uuid)
candidateId*string (uuid)
variantdemo | short | medium | long
languagecs | en
reportLanguagecs | en
answerModevoice | textvoice = interview with the AI avatar (default), text = written answers (reasonable adjustment)
extendedTimebooleanReasonable adjustment: double time for answers (max. 180 s per answer)
sendInvitebooleanWe email the invitation to the candidate (in the interview language). Otherwise, send it yourself.

201 Screening created403 Screening length (`variant`) is not included in the company’s plan (variant_not_in_tariff)404 Position or candidate not found409 Position has no approved rubric, or the candidate’s profile is not ready503 Interview preparation could not be queued (plan_not_queued). Retry the call.

get/api/v1/screenings

The company’s screenings (newest first, paginated with before)

For syncing with your ATS when a webhook did not arrive. Filters: status, candidate, completed since. To get the next page, set before = nextBefore from the previous response.

200 Screenings

get/api/v1/screenings/{id}

Screening status, recommendation and the candidate’s practical information

Path parameters: id

200 Screening404 Screening not found

post/api/v1/screenings/{id}/decision

Record a recruiter decision made in your ATS

Your ATS runs the candidate pipeline: a decision made in the ATS is recorded in Evaluro (source `ats`). A decision identical to the latest one is not recorded (`changed: false`), so writing back your own webhook does not duplicate anything. A rejection before the interview starts cancels the invitation. A decision with `changed: true` is sent out by the `screening.decided` webhook with `source: "ats"`.

Path parameters: id

FieldTypeDescription
decision*advance | hold | reject
notestringReason (internal). Required when `decision` = `reject`; otherwise optional.
decidedBy*stringRequired. Who made the decision in the ATS (name or email of an authorised person).

200 Decision recorded (or unchanged)400 Invalid input; `note` is missing for `reject` (invalid_request, issues[].path = note)404 Screening not found

post/api/v1/screenings/{id}/cancel

Cancel the invitation (only before the candidate starts)

Path parameters: id

204 Cancelled404 Screening not found409 The interview has already started or ended

post/api/v1/screenings/{id}/invite

New interview link for the candidate (the old one stops working; valid for 7 days)

For expired invitations or a lost link. Only possible before the interview starts. The optional body `{ "sendInvite": true }` emails the new link to the candidate.

Path parameters: id

200 New link404 Screening not found409 The interview has already started or ended

Webhooks.

An admin adds the webhook URL (https) in the Evaluro app under Integrations. We send these events: screening.created (a screening created through /intake; data contains only screeningId, candidate and position with their IDs, status and inviteSent), screening.completed, screening.decided, screening.expired, screening.failed and screening.accommodation_requested (see below). A URL subscribes to the events that existed when it was added, so to receive a new event, add the URL again. When the assessment is complete, we send a POST with this body:

{
  "id": "…",                      // delivery ID (for deduplication)
  "type": "screening.completed",
  "createdAt": "2026-09-26T10:00:00.000Z",
  "data": {
    "screeningId": "…",
    "candidate": { "id": "…", "externalRef": "ID in your ATS" },
    "position": { "id": "…", "title": "…" },
    "recommendation": "advance" | "consider" | "reject" | "insufficient",
    "summary": "…",
    "competencies": [{ "key": "…", "name": "…", "score": 1-5 | null, "confidence": "…" }],
    "logistics": { "salary": { "min", "max", "currency" } | null, "start": { "kind", "date" } | null,
                   "employment", "note", "skipped", "answeredAt" } | null,  // from the candidate, not assessed by the AI
    "reportUrl": "https://hireup.inflexion.cz/dashboard/screenings/…",
    "decisionRequired": true
  }
}

Every message has a Evaluro-Signature: t=<unix>,v1=<hex> header, where v1 = HMAC-SHA256(secret, "t.body"). Verify the signature and reject messages older than 5 minutes. If a delivery fails (any status other than 2xx, or a 10-second timeout), we retry up to 5 times.

Recruiter decisions: your ATS runs the pipeline

We send decisions made in the Evaluro app (invite to next round, put on hold, reject) as the screening.decided event, and your integration moves the candidate on in your ATS accordingly. Record decisions made in your ATS in Evaluro with POST /screenings/{id}/decision. The report shows them as ATS decisions, and a rejection before the interview starts cancels the invitation. A decision identical to the latest one is not recorded (changed: false). The decidedBy field (the name or email of the person who made the decision in the ATS) is required, so that human review can be demonstrated. A webhook with source: "ats" was triggered by your own write, so do not write it back to your ATS. note is an internal reason for recruiters, not text for the candidate. If your ATS emails candidates after a rejection, do not send the outcome email from Evaluro.

{
  "id": "…",
  "type": "screening.decided",
  "createdAt": "2026-09-30T10:00:00.000Z",
  "data": {
    "screeningId": "…",
    "decisionId": "…",
    "candidate": { "id": "…", "externalRef": "ID in your ATS" },
    "position": { "id": "…", "title": "…" },
    "status": "completed",
    "decision": "advance" | "hold" | "reject",
    "note": "…" | null,
    "source": "hireup" | "ats",
    "decidedBy": "Recruiter name" | null,
    "decidedAt": "2026-09-30T10:00:00.000Z",
    "reportUrl": "https://hireup.inflexion.cz/dashboard/screenings/…"
  }
}

The candidate did not do the interview

screening.expired arrives when an invitation expires without an interview (you can renew it with POST /screenings/{id}/invite). screening.failed arrives when the interview or its preparation failed; create a new screening. The technical reason for the failure is only in the report.

"data": {
  "screeningId": "…",
  "candidate": { "id": "…", "externalRef": "ID in your ATS" },
  "position": { "id": "…", "title": "…" },
  "status": "expired" | "failed",
  "started": false,          // the candidate started the interview
  "expiresAt": "…",
  "renewable": true,         // expired only
  "reportUrl": "…"
}

Requests for another interview format

screening.accommodation_requested arrives when a candidate asks for more time, written answers or an interview with a person. We send only the types of adjustment requested. The candidate’s own text may contain health information, so it stays in the report (hasNote tells you there is one). Turn it into a task for a recruiter in your ATS.

"data": {
  "screeningId": "…",
  "requestId": "…",
  "candidate": { "id": "…", "externalRef": "ID in your ATS" },
  "position": { "id": "…", "title": "…" },
  "kinds": ["more_time" | "text_answers" | "human_interview" | "other" | "new_link"],
  "hasNote": true,
  "requestedAt": "…",
  "reportUrl": "…"
}
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyEvaluro(secret, rawBody, header, toleranceSec = 300) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === expected.length && timingSafeEqual(given, expected);
}

Versioning and changes.

  • Version v1 is stable. Without notice, we only add: new fields in responses and webhooks, new optional parameters, new events and endpoints. So ignore unknown fields.
  • Any change that would break an existing integration (removing or renaming a field, or changing its meaning) is released only as v2. v1 then keeps running alongside it for at least 6 months, and we email the owners of companies with an active key.
30 September 2026 (evening)
Recruiter decisions in both directions: the screening.decided webhook, POST /screenings/{id}/decision (decisions from your ATS) and the decision field in GET /screenings/{id}. The screening.expired, screening.failed and screening.accommodation_requested webhooks.
30 September 2026
POST /intake: Word CVs (cvDocxBase64) and test mode (dryRun). Webhook deliveries overview in the Evaluro app.
29 September 2026
POST /intake (one call from your ATS), the screening.created webhook, and practical information from the candidate in screening.completed (the logistics field).
26 September 2026
GET /screenings with pagination, POST /screenings/{id}/cancel and /invite, DELETE /candidates/{id}, consistent errors, and a limit of 10,000 requests per day.
24 September 2026
First version of API v1 and the screening.completed webhook.

Personal data through the API.

  • When a candidate asks for erasure, call DELETE /candidates/{id}. It deletes the CV, screenings, recordings and assessments.
  • Otherwise, data is deleted automatically after the retention period set by the company.
  • Webhooks carry no transcript or quotes. Those stay in the report in Evaluro.