// Drop-in JavaScript client library for the KI BMS HTTP API. // // Save this file alongside your source as `ats_client.js` and import // the operation functions you need: // // import { setToken, account_list, account_create } from "./ats_client.js" // setToken("pat_...") // const rows = await account_list({ limit: 20, sort: "-created_at" }) // const fresh = await account_create({ name: "Example GmbH" }) // // CommonJS works too: // // const { setToken, account_list } = require("./ats_client.js") // // Every endpoint exposed by the HTTP API is wrapped as a typed // `_` function. List endpoints take an options object; // get/update/delete endpoints take the row id as their first argument. // // Provided as-is, with no warranty. Vendor freely; modify if you need // to. Targets ES2020+; uses the global `fetch` (Node 18+ / browser). // // DO NOT EDIT THIS FILE MANUALLY - re-download from the docs site instead. // Local edits will be overwritten by the once-per-day version check. const _g = (typeof globalThis !== 'undefined') ? globalThis : (typeof self !== 'undefined' ? self : this) // `require` only exists in CommonJS, `__filename` only in CommonJS. // Reach for them through the global so a strict ESM build still loads // the file. Both are wrapped in feature checks before any actual call. let _nodeRequire = null try { _nodeRequire = (_g && _g.require) || (typeof require === 'function' ? require : null) } catch (_e) { _nodeRequire = null } let _nodeFilename = null try { _nodeFilename = (typeof __filename !== 'undefined') ? __filename : null } catch (_e) { _nodeFilename = null } // ── Identity (substituted at generation time) ──────────────────────── export const APP_SLUG = "ats" export const APP_NAME = "KI BMS" export const MODULE_NAME = "ats_client" export const CLIENT_VERSION = "0.3.13" export const LANGUAGE = "javascript" const DEFAULT_BASE = "https://www.ki-bewerber-management.de" // Per-type metadata baked at generation time. Inspect at runtime when // calling code needs to know the legal filters / sort columns / // max_limit for a model without a second round-trip. export const TYPES = JSON.parse(String.raw`{"application":{"ops":["list","read","create","update","delete"],"create_fields":["job_id","candidate_id","stage","previous_stage","position","applied_at","last_stage_at","source_id","source_label","cover_letter","cv_blob_id","cv_url","answers","fit_score","fit_reasoning","fit_flags","fit_computed_at","rejected_reason","rejected_note","tags"],"update_fields":["stage","previous_stage","position","applied_at","last_stage_at","source_id","source_label","cover_letter","cv_blob_id","cv_url","answers","fit_score","fit_reasoning","fit_flags","fit_computed_at","rejected_reason","rejected_note","tags"],"allowed_filters":["data__job_id","data__candidate_id","data__stage","data__source_id","data__rejected_reason","data__is_archived","status","is_archived","owned_by","created_by"],"allowed_sorts":["created_at","updated_at","data__applied_at","data__fit_score","data__position","data__last_stage_at"],"default_sort":"data__position","max_limit":500,"fields":[{"name":"tags","type":"tags"},{"name":"stage","type":"enum","values":["new","review","screening","interview","offer","hired","rejected","talent_pool"]},{"name":"cv_url","type":"url","max_len":2048},{"name":"job_id","type":"string","max_len":64,"ref":{"type":"job","owned":false,"optional":false}},{"name":"answers","type":"list"},{"name":"position","type":"number"},{"name":"fit_flags","type":"tags"},{"name":"fit_score","type":"number"},{"name":"source_id","type":"string","max_len":64,"ref":{"type":"source","owned":false,"optional":false}},{"name":"applied_at","type":"string","max_len":32},{"name":"cv_blob_id","type":"string","max_len":64},{"name":"candidate_id","type":"string","max_len":64,"ref":{"type":"candidate","owned":false,"optional":false}},{"name":"cover_letter","type":"string","max_len":16000},{"name":"source_label","type":"string","max_len":200},{"name":"fit_reasoning","type":"string","max_len":4000},{"name":"last_stage_at","type":"string","max_len":32},{"name":"rejected_note","type":"string","max_len":2000},{"name":"previous_stage","type":"string","max_len":32},{"name":"fit_computed_at","type":"string","max_len":32},{"name":"rejected_reason","type":"enum","values":["not_qualified","salary_mismatch","location_mismatch","culture_mismatch","withdrew","ghosted","filled_internally","duplicate","other"]}]},"application_note":{"ops":["list","read","create","update","delete"],"create_fields":["body","pinned","private","parent_kind","parent_id"],"update_fields":["body","pinned","private"],"allowed_filters":["data__parent_id","data__parent_kind","data__pinned","data__private","status","is_archived","owned_by","created_by"],"allowed_sorts":["created_at","updated_at"],"default_sort":"created_at","max_limit":200,"fields":[{"name":"body","type":"string","max_len":8000},{"name":"pinned","type":"bool"},{"name":"private","type":"bool"},{"name":"parent_id","type":"string","max_len":64},{"name":"parent_kind","type":"enum","values":["candidate","application","job"]}]},"candidate":{"ops":["list","read","create","update","delete"],"create_fields":["name","first_name","last_name","salutation","pronouns","email","phone","city","country","current_company","current_role","years_experience","available_from","salary_expectation","currency","linkedin","github","portfolio","cv_url","cv_blob_id","avatar_blob_id","summary","skills","languages","tags","source_id","source_label","pool_status","gdpr_consent","gdpr_consent_at","gdpr_retention_until","preferred_locale","last_touched_at","color"],"update_fields":["name","first_name","last_name","salutation","pronouns","email","phone","city","country","current_company","current_role","years_experience","available_from","salary_expectation","currency","linkedin","github","portfolio","cv_url","cv_blob_id","avatar_blob_id","summary","skills","languages","tags","source_id","source_label","pool_status","gdpr_consent","gdpr_consent_at","gdpr_retention_until","preferred_locale","last_touched_at","color"],"allowed_filters":["data__email","data__name","data__location","data__country","data__source_id","data__tags","data__skills","data__pool_status","data__gdpr_consent","status","is_archived","owned_by","created_by"],"allowed_sorts":["created_at","updated_at","data__name","data__last_touched_at"],"default_sort":"created_at","max_limit":200,"fields":[{"name":"city","type":"string","max_len":120},{"name":"name","type":"string","max_len":200},{"name":"tags","type":"tags"},{"name":"color","type":"string","max_len":24},{"name":"email","type":"string","max_len":320},{"name":"phone","type":"string","max_len":64},{"name":"cv_url","type":"url","max_len":2048},{"name":"github","type":"url","max_len":2048},{"name":"skills","type":"tags"},{"name":"country","type":"string","max_len":120},{"name":"summary","type":"string","max_len":4000},{"name":"currency","type":"string","max_len":8},{"name":"linkedin","type":"url","max_len":2048},{"name":"pronouns","type":"string","max_len":32},{"name":"languages","type":"tags"},{"name":"last_name","type":"string","max_len":120},{"name":"portfolio","type":"url","max_len":2048},{"name":"source_id","type":"string","max_len":64,"ref":{"type":"source","owned":false,"optional":false}},{"name":"cv_blob_id","type":"string","max_len":64},{"name":"first_name","type":"string","max_len":120},{"name":"salutation","type":"enum","values":["herr","frau","divers","neutral"]},{"name":"pool_status","type":"enum","values":["active","talent_pool","blocked","withdrawn"]},{"name":"current_role","type":"string","max_len":200},{"name":"gdpr_consent","type":"bool"},{"name":"source_label","type":"string","max_len":200},{"name":"available_from","type":"string","max_len":32},{"name":"avatar_blob_id","type":"string","max_len":64},{"name":"current_company","type":"string","max_len":200},{"name":"gdpr_consent_at","type":"string","max_len":32},{"name":"last_touched_at","type":"string","max_len":32},{"name":"preferred_locale","type":"string","max_len":16},{"name":"years_experience","type":"number"},{"name":"salary_expectation","type":"number"},{"name":"gdpr_retention_until","type":"string","max_len":32}]},"email_template":{"ops":["list","read","create","update","delete"],"create_fields":["name","category","subject","body","language","stage_trigger","auto_send","active","variables_doc"],"update_fields":["name","category","subject","body","language","stage_trigger","auto_send","active","variables_doc"],"allowed_filters":["data__name","data__category","data__stage_trigger","data__active","status","is_archived","owned_by","created_by"],"allowed_sorts":["created_at","data__name"],"default_sort":"data__name","max_limit":100,"fields":[{"name":"body","type":"string","max_len":16000},{"name":"name","type":"string","max_len":200},{"name":"active","type":"bool"},{"name":"subject","type":"string","max_len":400},{"name":"category","type":"enum","values":["acknowledge","screening_invite","interview_invite","rejection","offer","talent_pool","other"]},{"name":"language","type":"string","max_len":16},{"name":"auto_send","type":"bool"},{"name":"stage_trigger","type":"enum","values":["","new","review","screening","interview","offer","hired","rejected","talent_pool"]},{"name":"variables_doc","type":"string","max_len":2000}]},"evaluation":{"ops":["list","read","create","update","delete"],"create_fields":["application_id","interview_id","interviewer_id","skills_score","culture_score","communication_score","potential_score","overall_score","recommendation","highlights","concerns","summary"],"update_fields":["skills_score","culture_score","communication_score","potential_score","overall_score","recommendation","highlights","concerns","summary"],"allowed_filters":["data__application_id","data__interview_id","data__interviewer_id","data__recommendation","status","is_archived","owned_by","created_by"],"allowed_sorts":["created_at","data__overall_score"],"default_sort":"created_at","max_limit":200,"fields":[{"name":"summary","type":"string","max_len":4000},{"name":"concerns","type":"string","max_len":4000},{"name":"highlights","type":"string","max_len":4000},{"name":"interview_id","type":"string","max_len":64,"ref":{"type":"interview","owned":false,"optional":false}},{"name":"skills_score","type":"number"},{"name":"culture_score","type":"number"},{"name":"overall_score","type":"number"},{"name":"application_id","type":"string","max_len":64,"ref":{"type":"application","owned":false,"optional":false}},{"name":"interviewer_id","type":"string","max_len":64},{"name":"recommendation","type":"enum","values":["strong_yes","yes","neutral","no","strong_no"]},{"name":"potential_score","type":"number"},{"name":"communication_score","type":"number"}]},"interview":{"ops":["list","read","create","update","delete"],"create_fields":["application_id","candidate_id","job_id","kind","status","title","scheduled_at","duration_minutes","location","meeting_url","interviewer_id","interviewer_ids","agenda","notes","send_invite"],"update_fields":["kind","status","title","scheduled_at","duration_minutes","location","meeting_url","interviewer_id","interviewer_ids","agenda","notes","send_invite"],"allowed_filters":["data__application_id","data__candidate_id","data__job_id","data__kind","data__status","data__interviewer_id","status","is_archived","owned_by","created_by"],"allowed_sorts":["data__scheduled_at","created_at","updated_at"],"default_sort":"data__scheduled_at","max_limit":200,"fields":[{"name":"kind","type":"enum","values":["phone","video","onsite","take_home","panel","trial_day"]},{"name":"notes","type":"string","max_len":8000},{"name":"title","type":"string","max_len":200},{"name":"agenda","type":"string","max_len":4000},{"name":"job_id","type":"string","max_len":64},{"name":"status","type":"enum","values":["scheduled","completed","no_show","cancelled","rescheduled"]},{"name":"location","type":"string","max_len":200},{"name":"meeting_url","type":"url","max_len":2048},{"name":"send_invite","type":"bool"},{"name":"candidate_id","type":"string","max_len":64},{"name":"scheduled_at","type":"string","max_len":32},{"name":"application_id","type":"string","max_len":64,"ref":{"type":"application","owned":false,"optional":false}},{"name":"interviewer_id","type":"string","max_len":64},{"name":"interviewer_ids","type":"list"},{"name":"duration_minutes","type":"number"}]},"job":{"ops":["list","read","create","update","delete"],"create_fields":["title","slug","department","location","country","remote","employment_type","seniority","headcount","salary_min","salary_max","currency","salary_visibility","summary","description","responsibilities","requirements","nice_to_have","benefits","language","status","public","ai_screen_enabled","ai_screen_prompt","knockout_questions","screening_questions","tags","hiring_manager_id","team_ids","opened_at","target_close_date","closed_at","external_apply_url","color"],"update_fields":["title","slug","department","location","country","remote","employment_type","seniority","headcount","salary_min","salary_max","currency","salary_visibility","summary","description","responsibilities","requirements","nice_to_have","benefits","language","status","public","ai_screen_enabled","ai_screen_prompt","knockout_questions","screening_questions","tags","hiring_manager_id","team_ids","opened_at","target_close_date","closed_at","external_apply_url","color"],"allowed_filters":["data__title","data__department","data__location","data__employment_type","data__seniority","data__remote","data__status","data__public","data__hiring_manager_id","data__tags","status","is_archived","owned_by","created_by"],"allowed_sorts":["created_at","updated_at","data__title","data__opened_at","data__target_close_date"],"default_sort":"created_at","max_limit":200,"fields":[{"name":"slug","type":"string","max_len":120},{"name":"tags","type":"tags"},{"name":"color","type":"string","max_len":24},{"name":"title","type":"string","max_len":200},{"name":"public","type":"bool"},{"name":"remote","type":"enum","values":["onsite","hybrid","remote"]},{"name":"status","type":"enum","values":["draft","open","paused","closed","filled"]},{"name":"country","type":"string","max_len":80},{"name":"summary","type":"string","max_len":600},{"name":"benefits","type":"string","max_len":4000},{"name":"currency","type":"string","max_len":8},{"name":"language","type":"string","max_len":16},{"name":"location","type":"string","max_len":120},{"name":"team_ids","type":"list"},{"name":"closed_at","type":"string","max_len":32},{"name":"headcount","type":"number"},{"name":"opened_at","type":"string","max_len":32},{"name":"seniority","type":"enum","values":["junior","mid","senior","lead","principal"]},{"name":"department","type":"string","max_len":120},{"name":"salary_max","type":"number"},{"name":"salary_min","type":"number"},{"name":"description","type":"string","max_len":16000},{"name":"nice_to_have","type":"string","max_len":4000},{"name":"requirements","type":"string","max_len":8000},{"name":"employment_type","type":"enum","values":["full_time","part_time","internship","working_student","freelance","contract"]},{"name":"ai_screen_prompt","type":"string","max_len":4000},{"name":"responsibilities","type":"string","max_len":8000},{"name":"ai_screen_enabled","type":"bool"},{"name":"hiring_manager_id","type":"string","max_len":64},{"name":"salary_visibility","type":"enum","values":["public","team","private"]},{"name":"target_close_date","type":"string","max_len":32},{"name":"external_apply_url","type":"url","max_len":2048},{"name":"knockout_questions","type":"list"},{"name":"screening_questions","type":"list"}]},"message":{"ops":["list","read","create","update","delete"],"create_fields":["candidate_id","application_id","channel","direction","subject","body","status","sent_at","delivered_at","read_at","template_id","from_address","to_address","cc_addresses","thread_id","error"],"update_fields":["subject","body","status","sent_at","delivered_at","read_at","thread_id","error"],"allowed_filters":["data__candidate_id","data__application_id","data__channel","data__status","data__template_id","status","is_archived","owned_by","created_by"],"allowed_sorts":["created_at","data__sent_at"],"default_sort":"-data__sent_at","max_limit":200,"fields":[{"name":"body","type":"string","max_len":16000},{"name":"error","type":"string","max_len":600},{"name":"status","type":"enum","values":["draft","queued","sent","delivered","failed","bounced"]},{"name":"channel","type":"enum","values":["email","note"]},{"name":"read_at","type":"string","max_len":32},{"name":"sent_at","type":"string","max_len":32},{"name":"subject","type":"string","max_len":400},{"name":"direction","type":"enum","values":["outbound","inbound"]},{"name":"thread_id","type":"string","max_len":200},{"name":"to_address","type":"string","max_len":320},{"name":"template_id","type":"string","max_len":64,"ref":{"type":"email_template","owned":false,"optional":false}},{"name":"candidate_id","type":"string","max_len":64},{"name":"cc_addresses","type":"list"},{"name":"delivered_at","type":"string","max_len":32},{"name":"from_address","type":"string","max_len":320},{"name":"application_id","type":"string","max_len":64}]},"offer":{"ops":["list","read","create","update","delete"],"create_fields":["application_id","candidate_id","job_id","salary_gross","salary_period","currency","bonus","bonus_note","vacation_days","start_date","expires_at","term","term_until","weekly_hours","remote_policy","status","sent_at","decided_at","letter_body","letter_blob_id","decline_reason"],"update_fields":["salary_gross","salary_period","currency","bonus","bonus_note","vacation_days","start_date","expires_at","term","term_until","weekly_hours","remote_policy","status","sent_at","decided_at","letter_body","letter_blob_id","decline_reason"],"allowed_filters":["data__application_id","data__candidate_id","data__job_id","data__status","data__currency","status","is_archived","owned_by","created_by"],"allowed_sorts":["created_at","data__start_date","data__sent_at"],"default_sort":"created_at","max_limit":100,"fields":[{"name":"term","type":"enum","values":["permanent","fixed_term","trial","intern","freelance"]},{"name":"bonus","type":"number"},{"name":"job_id","type":"string","max_len":64},{"name":"status","type":"enum","values":["draft","sent","accepted","declined","withdrawn","expired"]},{"name":"sent_at","type":"string","max_len":32},{"name":"currency","type":"string","max_len":8},{"name":"bonus_note","type":"string","max_len":600},{"name":"decided_at","type":"string","max_len":32},{"name":"expires_at","type":"string","max_len":32},{"name":"start_date","type":"string","max_len":32},{"name":"term_until","type":"string","max_len":32},{"name":"letter_body","type":"string","max_len":16000},{"name":"candidate_id","type":"string","max_len":64},{"name":"salary_gross","type":"number"},{"name":"weekly_hours","type":"number"},{"name":"remote_policy","type":"string","max_len":200},{"name":"salary_period","type":"enum","values":["yearly","monthly","daily","hourly"]},{"name":"vacation_days","type":"number"},{"name":"application_id","type":"string","max_len":64,"ref":{"type":"application","owned":false,"optional":false}},{"name":"decline_reason","type":"string","max_len":600},{"name":"letter_blob_id","type":"string","max_len":64}]},"source":{"ops":["list","read","create","update","delete"],"create_fields":["name","kind","url","active","notes"],"update_fields":["name","kind","url","active","notes"],"allowed_filters":["data__name","data__kind","data__active","status","is_archived","owned_by","created_by"],"allowed_sorts":["created_at","data__name"],"default_sort":"data__name","max_limit":100,"fields":[{"name":"url","type":"url","max_len":2048},{"name":"kind","type":"enum","values":["linkedin","indeed","stepstone","xing","honeypot","kununu","careers_page","referral","active_sourcing","agency","event","other"]},{"name":"name","type":"string","max_len":200},{"name":"notes","type":"string","max_len":2000},{"name":"active","type":"bool"}]},"task":{"ops":["list","read","create","update","delete"],"create_fields":["title","description","due_date","completed","completed_at","priority","assigned_to","parent_kind","parent_id"],"update_fields":["title","description","due_date","completed","completed_at","priority","assigned_to"],"allowed_filters":["data__parent_id","data__parent_kind","data__assigned_to","data__completed","data__priority","status","is_archived","owned_by","created_by"],"allowed_sorts":["data__due_date","created_at","data__priority"],"default_sort":"data__due_date","max_limit":200,"fields":[{"name":"title","type":"string","max_len":200},{"name":"due_date","type":"string","max_len":32},{"name":"priority","type":"enum","values":["low","normal","high","urgent"]},{"name":"completed","type":"bool"},{"name":"parent_id","type":"string","max_len":64},{"name":"assigned_to","type":"string","max_len":64},{"name":"description","type":"string","max_len":4000},{"name":"parent_kind","type":"enum","values":["candidate","application","job"]},{"name":"completed_at","type":"string","max_len":32}]}}`) // ── Token + base-URL configuration ─────────────────────────────────── let _token = null export function setToken(token) { _token = (token == null ? "" : String(token)).trim() || null } export function getToken() { if (_token) return _token const env = _g.process && _g.process.env if (env && env.XCLIENT_TOKEN) return env.XCLIENT_TOKEN return null } function _baseUrl() { const env = _g.process && _g.process.env if (env && env.XCLIENT_BASE_URL) return String(env.XCLIENT_BASE_URL).replace(/\/+$/, "") return DEFAULT_BASE.replace(/\/+$/, "") } // ── Identifier persistence ─────────────────────────────────────────── function _isNode() { return !!(_g.process && _g.process.versions && _g.process.versions.node) } class _MemoryStorage { constructor() { this.map = new Map() } read(key) { return this.map.has(key) ? this.map.get(key) : null } write(key, value) { this.map.set(key, value) } } class _NodeStorage { constructor() { this.dir = null; this.fs = null; this.path = null if (!_nodeRequire) return try { const os = _nodeRequire("os") this.path = _nodeRequire("path") this.fs = _nodeRequire("fs") this.dir = this.path.join(os.homedir(), "." + MODULE_NAME) this.fs.mkdirSync(this.dir, { recursive: true, mode: 0o700 }) } catch (_e) { this.dir = null; this.fs = null; this.path = null } } available() { return this.dir !== null && this.fs !== null } read(key) { if (!this.available()) return null try { const f = this.path.join(this.dir, key + ".json") const obj = JSON.parse(this.fs.readFileSync(f, "utf-8")) return (obj && typeof obj.value === "string") ? obj.value : null } catch (_e) { return null } } write(key, value) { if (!this.available()) return try { const f = this.path.join(this.dir, key + ".json") this.fs.writeFileSync(f, JSON.stringify({ value }), { mode: 0o600 }) } catch (_e) { /* best-effort */ } } } class _WebStorage { read(key) { try { const v = _g.localStorage.getItem(MODULE_NAME + ":" + key); return v == null ? null : v } catch (_e) { return null } } write(key, value) { try { _g.localStorage.setItem(MODULE_NAME + ":" + key, value) } catch (_e) { /* best-effort */ } } } let _storage if (_isNode() && _nodeRequire) { const ns = new _NodeStorage() _storage = ns.available() ? ns : new _MemoryStorage() } else if (_g.localStorage) { _storage = new _WebStorage() } else { _storage = new _MemoryStorage() } function _uuid() { try { if (_g.crypto && typeof _g.crypto.randomUUID === "function") return _g.crypto.randomUUID() } catch (_e) { /* fall through */ } const hex = [] for (let i = 0; i < 16; i++) hex.push(Math.floor(Math.random() * 256).toString(16).padStart(2, "0")) hex[6] = ((parseInt(hex[6], 16) & 0x0f) | 0x40).toString(16).padStart(2, "0") hex[8] = ((parseInt(hex[8], 16) & 0x3f) | 0x80).toString(16).padStart(2, "0") return hex.slice(0, 4).join("") + "-" + hex.slice(4, 6).join("") + "-" + hex.slice(6, 8).join("") + "-" + hex.slice(8, 10).join("") + "-" + hex.slice(10, 16).join("") } function _deviceId() { const cur = _storage.read("device") if (cur && cur.length >= 32) return cur const fresh = _uuid() _storage.write("device", fresh) return fresh } let _sessionIdCache = null function _sessionId() { if (!_sessionIdCache) _sessionIdCache = _uuid() return _sessionIdCache } function _autoupdateEnabled() { const env = _g.process && _g.process.env if (env) { const v = String(env.XCLIENT_NO_AUTOUPDATE || "").toLowerCase() if (v === "1" || v === "true" || v === "yes") return false } return true } // ── Editor / runtime fingerprint ───────────────────────────────────── function _fingerprint() { const out = {} try { const env = _g.process && _g.process.env if (env) { out.term_program = env.TERM_PROGRAM || null out.editor_env = env.EDITOR || null out.ci = !!(env.CI || env.GITHUB_ACTIONS) out.claude_code = !!(env.CLAUDECODE || env.CLAUDE_CODE_ENTRYPOINT) out.codex = !!env.CODEX_HOME const tp = String(env.TERM_PROGRAM || "").toLowerCase() out.vscode = tp === "vscode" && !env.CURSOR_TRACE_ID out.cursor = !!env.CURSOR_TRACE_ID out.antigravity = !!env.ANTIGRAVITY_TRACE_ID out.jetbrains = tp.indexOf("jetbrains") !== -1 out.node_version = (_g.process.versions && _g.process.versions.node) || null out.platform = _g.process.platform || null } else if (_g.navigator) { out.user_agent = _g.navigator.userAgent out.language = _g.navigator.language out.platform = _g.navigator.platform } } catch (_e) { /* best-effort */ } return out } // ── HTTP transport ─────────────────────────────────────────────────── export class ApiError extends Error { constructor(status, message, body) { super("HTTP " + status + ": " + message) this.name = "ApiError" this.status = status this.bodyRaw = body == null ? null : body } } const _RETRYABLE = new Set([408, 425, 429, 500, 502, 503, 504]) const _MAX_RETRIES = 3 const _DEFAULT_TIMEOUT = 30000 function _backoff(attempt, retryAfter) { if (retryAfter !== null && retryAfter !== undefined && retryAfter >= 0) return Math.min(retryAfter, 60) * 1000 return Math.min(Math.pow(2, attempt), 60) * 1000 } function _userAgent() { const node = _g.process && _g.process.versions && _g.process.versions.node if (node) return MODULE_NAME + "/" + CLIENT_VERSION + " (lib/" + LANGUAGE + "; node/" + node + ")" return MODULE_NAME + "/" + CLIENT_VERSION + " (lib/" + LANGUAGE + "; web)" } function _sleep(ms) { return new Promise(function (r) { setTimeout(r, ms) }) } let _autoupdateAttempted = false async function _request(method, path, opts) { opts = opts || {} if (!_autoupdateAttempted) { _autoupdateAttempted = true _maybeAutoupdate().catch(function () { /* never throw into the caller */ }) } let url = _baseUrl() + path if (opts.params) { const qs = new URLSearchParams() for (const k of Object.keys(opts.params)) { const v = opts.params[k] if (v === undefined || v === null) continue qs.append(k, String(v)) } const tail = qs.toString() if (tail) url += (url.indexOf("?") !== -1 ? "&" : "?") + tail } let headers = { "Accept": "application/json", "User-Agent": _userAgent(), "X-Client-Channel": "client_" + LANGUAGE, "X-Client-Version": CLIENT_VERSION, "X-Analytics-Device-Id": _deviceId(), "X-Analytics-Session-Id": _sessionId(), } const tok = getToken() if (tok) headers.Authorization = "Bearer " + tok let body if (opts.body !== undefined) { body = JSON.stringify(opts.body) headers["Content-Type"] = "application/json" } let lastErr = null for (let attempt = 0; attempt < _MAX_RETRIES; attempt++) { const controller = new AbortController() const timer = setTimeout(function () { controller.abort() }, opts.timeout || _DEFAULT_TIMEOUT) try { const resp = await _fetchFollowingRedirects(url, { method: method.toUpperCase(), headers: headers, body: body, signal: controller.signal, }) clearTimeout(timer) _maybePersistRefresh(resp.headers) if (_RETRYABLE.has(resp.status) && attempt + 1 < _MAX_RETRIES) { const ra = parseFloat(resp.headers.get("Retry-After") || "") await _sleep(_backoff(attempt, isFinite(ra) ? ra : null)) continue } if (!resp.ok) { const ctype = (resp.headers.get("Content-Type") || "").toLowerCase() let parsed = null try { parsed = ctype.indexOf("application/json") !== -1 ? await resp.json() : await resp.text() } catch (_e) { parsed = null } const msg = (parsed && typeof parsed === "object" && (parsed.detail || parsed.message)) || resp.statusText || "request failed" _emitCallEvent(method, path, resp.status, false) throw new ApiError(resp.status, String(msg), parsed) } _emitCallEvent(method, path, resp.status, true) if (opts.expectEmpty || resp.status === 204) return null const ctype = (resp.headers.get("Content-Type") || "").toLowerCase() if (ctype.indexOf("application/json") !== -1) return await resp.json() return await resp.text() } catch (e) { clearTimeout(timer) if (e instanceof ApiError) throw e lastErr = e if (attempt + 1 < _MAX_RETRIES) { await _sleep(_backoff(attempt, null)) continue } _emitCallEvent(method, path, 0, false) throw new ApiError(0, e && e.message ? e.message : "request failed") } } _emitCallEvent(method, path, 0, false) throw new ApiError(0, lastErr && lastErr.message ? lastErr.message : "request failed") } // Manual redirect-following so we can drop `Authorization` whenever the // new URL points at a different origin. Platform fetch keeps every // header on cross-origin redirects by default - a misconfigured proxy // bouncing requests to an internal host would otherwise leak the PAT. // Cap at 5 hops. async function _fetchFollowingRedirects(url, init) { const maxHops = 5 let currentUrl = url let headers = Object.assign({}, init.headers) let method = init.method let body = init.body for (let hop = 0; hop < maxHops; hop++) { const resp = await fetch(currentUrl, { method: method, headers: headers, body: body, signal: init.signal, redirect: "manual", }) if (resp.status < 300 || resp.status >= 400 || resp.status === 304) return resp const loc = resp.headers.get("Location") if (!loc) return resp let nextUrl try { nextUrl = new URL(loc, currentUrl) } catch (_e) { return resp } let curOrigin = "" try { curOrigin = new URL(currentUrl).origin } catch (_e) { curOrigin = "" } if (nextUrl.origin !== curOrigin && headers.Authorization) { headers = Object.assign({}, headers) delete headers.Authorization } if (resp.status === 303 || ((resp.status === 301 || resp.status === 302) && method !== "GET" && method !== "HEAD")) { method = "GET" body = undefined delete headers["Content-Type"] } currentUrl = nextUrl.toString() } return fetch(currentUrl, { method: method, headers: headers, body: body, signal: init.signal, redirect: "manual" }) } function _maybePersistRefresh(headers) { try { const fresh = headers.get("x-auth-refresh-token") if (fresh) _token = fresh } catch (_e) { /* best-effort */ } } // ── Analytics ──────────────────────────────────────────────────────── let _metaSentOnce = false function _emitCallEvent(method, path, status, ok) { try { const meta = { channel: "client_" + LANGUAGE, client_version: CLIENT_VERSION, module_name: MODULE_NAME, language: LANGUAGE, } if (_g.process && _g.process.versions) { meta.os = _g.process.platform meta.node = _g.process.versions.node } else if (_g.navigator) { meta.os = _g.navigator.platform } if (!_metaSentOnce) { meta.env = _fingerprint(); _metaSentOnce = true } const evt = { type: "client.call", ts_client: Math.floor(Date.now() / 1000), meta: { method: method.toUpperCase(), path: String(path).split("?")[0].slice(0, 128), status: status, ok: !!ok, }, } const body = JSON.stringify({ device_id: _deviceId(), session_id: _sessionId(), events: [evt], meta: meta, }) fetch(_baseUrl() + "/xapi2/analytics/challenge", { method: "POST", headers: { "Content-Type": "application/json", "User-Agent": _userAgent() }, body: body, keepalive: true, }).catch(function () { /* fire and forget */ }) } catch (_e) { /* fire and forget */ } } // ── Auto-update ────────────────────────────────────────────────────── async function _maybeAutoupdate() { if (!_autoupdateEnabled()) return if (!_isNode() || !_nodeRequire) return // browser bundles can't self-rewrite const last = _storage.read("update_check") if (last && (Date.now() / 1000 - parseInt(last, 10) < 86400)) return try { const probe = await fetch(_baseUrl() + "/xapi2/clients/version", { method: "GET" }) const payload = await probe.json() _storage.write("update_check", String(Math.floor(Date.now() / 1000))) if (!payload || !payload.version || payload.version === CLIENT_VERSION) return const fresh = await fetch(_baseUrl() + "/xapi2/clients/script." + LANGUAGE) const text = await fresh.text() if (!_looksValid(text)) return const fs = _nodeRequire("fs") const here = _nodeFilename if (!here) return const tmp = here + ".tmp." + Date.now() fs.writeFileSync(tmp, text) fs.renameSync(tmp, here) } catch (_e) { /* best-effort */ } } function _looksValid(blob) { if (typeof blob !== "string" || blob.length < 2000) return false for (const m of ["MODULE_NAME", "CLIENT_VERSION", "APP_SLUG", "_request"]) { if (blob.indexOf(m) === -1) return false } return true } // ── Generated per-type wrapper functions ───────────────────────────── // Every model that exposes an op gets one `_` function // below. The runtime above does the heavy lifting; these wrappers just // pin the URL + HTTP verb. export async function application_list(opts) { opts = opts || {} const params = {} if (opts.limit !== undefined) params.limit = opts.limit if (opts.offset !== undefined) params.offset = opts.offset if (opts.sort) params.sort = opts.sort if (opts.q) params.q = opts.q if (opts.filters) for (const k of Object.keys(opts.filters)) { const v = opts.filters[k]; if (v !== undefined && v !== null) params[k] = v } return _request('GET', '/xapi2/data/application', { params }) } export async function application_get(id) { return _request('GET', '/xapi2/data/application/' + id) } export async function application_create(data) { return _request('POST', '/xapi2/data/application', { body: data }) } export async function application_update(id, data) { return _request('PATCH', '/xapi2/data/application/' + id, { body: data }) } export async function application_delete(id) { await _request('DELETE', '/xapi2/data/application/' + id, { expectEmpty: true }) return true } export async function application_note_list(opts) { opts = opts || {} const params = {} if (opts.limit !== undefined) params.limit = opts.limit if (opts.offset !== undefined) params.offset = opts.offset if (opts.sort) params.sort = opts.sort if (opts.q) params.q = opts.q if (opts.filters) for (const k of Object.keys(opts.filters)) { const v = opts.filters[k]; if (v !== undefined && v !== null) params[k] = v } return _request('GET', '/xapi2/data/application_note', { params }) } export async function application_note_get(id) { return _request('GET', '/xapi2/data/application_note/' + id) } export async function application_note_create(data) { return _request('POST', '/xapi2/data/application_note', { body: data }) } export async function application_note_update(id, data) { return _request('PATCH', '/xapi2/data/application_note/' + id, { body: data }) } export async function application_note_delete(id) { await _request('DELETE', '/xapi2/data/application_note/' + id, { expectEmpty: true }) return true } export async function candidate_list(opts) { opts = opts || {} const params = {} if (opts.limit !== undefined) params.limit = opts.limit if (opts.offset !== undefined) params.offset = opts.offset if (opts.sort) params.sort = opts.sort if (opts.q) params.q = opts.q if (opts.filters) for (const k of Object.keys(opts.filters)) { const v = opts.filters[k]; if (v !== undefined && v !== null) params[k] = v } return _request('GET', '/xapi2/data/candidate', { params }) } export async function candidate_get(id) { return _request('GET', '/xapi2/data/candidate/' + id) } export async function candidate_create(data) { return _request('POST', '/xapi2/data/candidate', { body: data }) } export async function candidate_update(id, data) { return _request('PATCH', '/xapi2/data/candidate/' + id, { body: data }) } export async function candidate_delete(id) { await _request('DELETE', '/xapi2/data/candidate/' + id, { expectEmpty: true }) return true } export async function email_template_list(opts) { opts = opts || {} const params = {} if (opts.limit !== undefined) params.limit = opts.limit if (opts.offset !== undefined) params.offset = opts.offset if (opts.sort) params.sort = opts.sort if (opts.q) params.q = opts.q if (opts.filters) for (const k of Object.keys(opts.filters)) { const v = opts.filters[k]; if (v !== undefined && v !== null) params[k] = v } return _request('GET', '/xapi2/data/email_template', { params }) } export async function email_template_get(id) { return _request('GET', '/xapi2/data/email_template/' + id) } export async function email_template_create(data) { return _request('POST', '/xapi2/data/email_template', { body: data }) } export async function email_template_update(id, data) { return _request('PATCH', '/xapi2/data/email_template/' + id, { body: data }) } export async function email_template_delete(id) { await _request('DELETE', '/xapi2/data/email_template/' + id, { expectEmpty: true }) return true } export async function evaluation_list(opts) { opts = opts || {} const params = {} if (opts.limit !== undefined) params.limit = opts.limit if (opts.offset !== undefined) params.offset = opts.offset if (opts.sort) params.sort = opts.sort if (opts.q) params.q = opts.q if (opts.filters) for (const k of Object.keys(opts.filters)) { const v = opts.filters[k]; if (v !== undefined && v !== null) params[k] = v } return _request('GET', '/xapi2/data/evaluation', { params }) } export async function evaluation_get(id) { return _request('GET', '/xapi2/data/evaluation/' + id) } export async function evaluation_create(data) { return _request('POST', '/xapi2/data/evaluation', { body: data }) } export async function evaluation_update(id, data) { return _request('PATCH', '/xapi2/data/evaluation/' + id, { body: data }) } export async function evaluation_delete(id) { await _request('DELETE', '/xapi2/data/evaluation/' + id, { expectEmpty: true }) return true } export async function interview_list(opts) { opts = opts || {} const params = {} if (opts.limit !== undefined) params.limit = opts.limit if (opts.offset !== undefined) params.offset = opts.offset if (opts.sort) params.sort = opts.sort if (opts.q) params.q = opts.q if (opts.filters) for (const k of Object.keys(opts.filters)) { const v = opts.filters[k]; if (v !== undefined && v !== null) params[k] = v } return _request('GET', '/xapi2/data/interview', { params }) } export async function interview_get(id) { return _request('GET', '/xapi2/data/interview/' + id) } export async function interview_create(data) { return _request('POST', '/xapi2/data/interview', { body: data }) } export async function interview_update(id, data) { return _request('PATCH', '/xapi2/data/interview/' + id, { body: data }) } export async function interview_delete(id) { await _request('DELETE', '/xapi2/data/interview/' + id, { expectEmpty: true }) return true } export async function job_list(opts) { opts = opts || {} const params = {} if (opts.limit !== undefined) params.limit = opts.limit if (opts.offset !== undefined) params.offset = opts.offset if (opts.sort) params.sort = opts.sort if (opts.q) params.q = opts.q if (opts.filters) for (const k of Object.keys(opts.filters)) { const v = opts.filters[k]; if (v !== undefined && v !== null) params[k] = v } return _request('GET', '/xapi2/data/job', { params }) } export async function job_get(id) { return _request('GET', '/xapi2/data/job/' + id) } export async function job_create(data) { return _request('POST', '/xapi2/data/job', { body: data }) } export async function job_update(id, data) { return _request('PATCH', '/xapi2/data/job/' + id, { body: data }) } export async function job_delete(id) { await _request('DELETE', '/xapi2/data/job/' + id, { expectEmpty: true }) return true } export async function message_list(opts) { opts = opts || {} const params = {} if (opts.limit !== undefined) params.limit = opts.limit if (opts.offset !== undefined) params.offset = opts.offset if (opts.sort) params.sort = opts.sort if (opts.q) params.q = opts.q if (opts.filters) for (const k of Object.keys(opts.filters)) { const v = opts.filters[k]; if (v !== undefined && v !== null) params[k] = v } return _request('GET', '/xapi2/data/message', { params }) } export async function message_get(id) { return _request('GET', '/xapi2/data/message/' + id) } export async function message_create(data) { return _request('POST', '/xapi2/data/message', { body: data }) } export async function message_update(id, data) { return _request('PATCH', '/xapi2/data/message/' + id, { body: data }) } export async function message_delete(id) { await _request('DELETE', '/xapi2/data/message/' + id, { expectEmpty: true }) return true } export async function offer_list(opts) { opts = opts || {} const params = {} if (opts.limit !== undefined) params.limit = opts.limit if (opts.offset !== undefined) params.offset = opts.offset if (opts.sort) params.sort = opts.sort if (opts.q) params.q = opts.q if (opts.filters) for (const k of Object.keys(opts.filters)) { const v = opts.filters[k]; if (v !== undefined && v !== null) params[k] = v } return _request('GET', '/xapi2/data/offer', { params }) } export async function offer_get(id) { return _request('GET', '/xapi2/data/offer/' + id) } export async function offer_create(data) { return _request('POST', '/xapi2/data/offer', { body: data }) } export async function offer_update(id, data) { return _request('PATCH', '/xapi2/data/offer/' + id, { body: data }) } export async function offer_delete(id) { await _request('DELETE', '/xapi2/data/offer/' + id, { expectEmpty: true }) return true } export async function source_list(opts) { opts = opts || {} const params = {} if (opts.limit !== undefined) params.limit = opts.limit if (opts.offset !== undefined) params.offset = opts.offset if (opts.sort) params.sort = opts.sort if (opts.q) params.q = opts.q if (opts.filters) for (const k of Object.keys(opts.filters)) { const v = opts.filters[k]; if (v !== undefined && v !== null) params[k] = v } return _request('GET', '/xapi2/data/source', { params }) } export async function source_get(id) { return _request('GET', '/xapi2/data/source/' + id) } export async function source_create(data) { return _request('POST', '/xapi2/data/source', { body: data }) } export async function source_update(id, data) { return _request('PATCH', '/xapi2/data/source/' + id, { body: data }) } export async function source_delete(id) { await _request('DELETE', '/xapi2/data/source/' + id, { expectEmpty: true }) return true } export async function task_list(opts) { opts = opts || {} const params = {} if (opts.limit !== undefined) params.limit = opts.limit if (opts.offset !== undefined) params.offset = opts.offset if (opts.sort) params.sort = opts.sort if (opts.q) params.q = opts.q if (opts.filters) for (const k of Object.keys(opts.filters)) { const v = opts.filters[k]; if (v !== undefined && v !== null) params[k] = v } return _request('GET', '/xapi2/data/task', { params }) } export async function task_get(id) { return _request('GET', '/xapi2/data/task/' + id) } export async function task_create(data) { return _request('POST', '/xapi2/data/task', { body: data }) } export async function task_update(id, data) { return _request('PATCH', '/xapi2/data/task/' + id, { body: data }) } export async function task_delete(id) { await _request('DELETE', '/xapi2/data/task/' + id, { expectEmpty: true }) return true }