icrack.ai · Developers

Recruiter API

Push applicants in, rank and approve them, send AI interview invites, and read structured results — the same engine the recruiter dashboard uses, from your own system. Machine-readable spec: /api/v1/openapi.json.

Authentication

Create a key under Settings → Integrations (account owners). Send it as a bearer token. Keys see only their own account; 240 requests/minute per key; errors are { "error": { "code", "message" } }.

curl https://YOUR-HOST/api/v1/campaigns \
  -H "Authorization: Bearer ick_live_…"

The flow

  1. Create a campaign (a hiring drive: JD + interview config).
  2. Push applicants — idempotent on external_id, so retries are safe.
  3. Rank → each resume gets a score with evidence (1 credit).
  4. Approve the shortlist — nothing is invited until you do.
  5. Invite → each approved candidate gets a unique link by email/SMS/WhatsApp (10 credits reserved, refunded if unused).
  6. Receive interview.completed webhooks, or poll the candidates endpoint.

Endpoints

GET
/api/v1/campaigns

List campaigns.

POST
/api/v1/campaigns

Create a campaign: title, job_title, company_name, job_description (+ ranking_criteria, language, invite_channels, invite_ttl_days, credit_budget).

GET
/api/v1/campaigns/{id}

Campaign detail with funnel counts.

POST
/api/v1/campaigns/{id}/candidates

Push up to 100 applicants. Each needs external_id and consent:true plus resume_url | resume_text | resume_base64.

GET
/api/v1/campaigns/{id}/candidates

Ranked results and interview outcomes. Filters: funnel, pipeline_stage; paginate with limit & cursor.

POST
/api/v1/campaigns/{id}/rank

Rank everything unranked. Returns what was done and, if credits ran out, exactly how many were left undone.

POST
/api/v1/campaigns/{id}/approve

Body { "candidate_ids": [...] } — only ranked candidates can be approved.

POST
/api/v1/campaigns/{id}/invite

Invite all approved candidates.

GET
/api/v1/candidates/{id}

One candidate with ranking evidence and interview outcome.

Push applicants

curl -X POST https://YOUR-HOST/api/v1/campaigns/$CAMPAIGN/candidates \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{
    "consent_attested": true,
    "candidates": [{
      "external_id": "foundit-8841",
      "name": "Aisha Khan",
      "email": "aisha@example.com",
      "phone": "+919811100001",
      "resume_url": "https://files.example.com/aisha.pdf"
    }]
  }'

Per-item result: created, duplicate (already ingested — safe to ignore), needs_review (no email could be found), or failed. A row with an email but no resume is a roster entry and goes straight to the approval queue.

Webhooks

Add an endpoint under Settings → Integrations. Events: candidate.ingested, candidate.ranked, candidate.approved, candidate.invited, interview.started, interview.completed, candidate.handoff. Delivery is at-least-once: dedupe on the X-Icrack-Delivery id. Respond 2xx within 8 s; failures retry at 1 min, 5 min, 30 min, 2 h, 12 h.

// Verify (Node)
const [t, v1] = req.headers['x-icrack-signature'].split(',').map(p => p.split('=')[1]);
const expected = crypto.createHmac('sha256', SECRET).update(t + '.' + rawBody).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected))) return res.sendStatus(400);
if (Math.abs(Date.now()/1000 - Number(t)) > 300) return res.sendStatus(400); // replay window

Embed the apply widget

Candidates apply without leaving your site; applications land straight in the campaign.

<script src="https://YOUR-HOST/embed.js" data-client="your-slug" data-role="campaign-slug" async></script>

Scheduled feed (pull)

Prefer we poll you? Expose a read-only endpoint and register it on a campaign. We call GET <url>?since=<cursor> every 15 minutes (optional auth header, stored encrypted):

{ "candidates": [{ "external_id": "…", "name": "…", "email": "…", "phone": "…",
                  "resume_url": "…" | "resume_text": "…" }],
  "next_cursor": "…" }