;; Drop-in Clojure client library for the KI BMS HTTP API. ;; ;; Save this file under your project as `src/ats_client.clj` and ;; require the namespace from your code: ;; ;; (require '[ats_client :as api]) ;; (def c (api/new-client "pat_...")) ;; (def rows (api/account-list c {:limit 20 :sort "-created_at"})) ;; (def fresh (api/account-create c {"name" "Example GmbH"})) ;; ;; Every endpoint exposed by the HTTP API is wrapped as a typed ;; `-` function. List functions take an opts map; get / ;; update / delete take the row id as their second positional ;; argument. ;; ;; Provided as-is, with no warranty. Vendor freely; modify as needed. ;; Targets Clojure 1.11+ on JDK 11+; uses only the JDK stdlib ;; (`java.net.http` for HTTP) plus a tiny inline JSON encoder / ;; decoder (no Cheshire / data.json dependency). ;; ;; DO NOT EDIT THIS FILE MANUALLY - re-download from the docs site. ;; Local edits will be overwritten by the once-per-day version check. (ns ats_client (:require [clojure.string :as str]) (:import (java.io File) (java.net URI URLEncoder) (java.net.http HttpClient HttpClient$Redirect HttpClient$Version HttpRequest HttpRequest$BodyPublishers HttpRequest$Builder HttpResponse HttpResponse$BodyHandlers) (java.nio.charset StandardCharsets) (java.security SecureRandom) (java.time Duration) (java.util UUID))) ;; ── Identity (substituted at generation time) ──────────────────────── (def ^:const app-slug "ats") (def ^:const app-name "KI BMS") (def ^:const module-name "ats_client") (def ^:const client-version "0.3.13") (def ^:const language "clojure") (def ^:const ^:private default-base "https://ki-bewerber-management.de") ;; ── Tiny JSON encoder / decoder ────────────────────────────────────── ;; Recursive descent. Maps with string keys round-trip cleanly with the ;; rest of the toolchain. Decoded objects come out as Clojure maps ;; with string keys; arrays as vectors; null as nil; true / false as ;; the booleans. (declare encode-json) (defn- encode-string [^StringBuilder sb ^String s] (.append sb \") (doseq [^Character c s] (let [ch (int c)] (cond (= ch 0x22) (.append sb "\\\"") (= ch 0x5C) (.append sb "\\\\") (= ch 0x08) (.append sb "\\b") (= ch 0x0C) (.append sb "\\f") (= ch 0x0A) (.append sb "\\n") (= ch 0x0D) (.append sb "\\r") (= ch 0x09) (.append sb "\\t") (< ch 0x20) (.append sb (format "\\u%04x" ch)) :else (.append sb (char ch))))) (.append sb \")) (defn- encode-into [^StringBuilder sb v] (cond (nil? v) (.append sb "null") (true? v) (.append sb "true") (false? v) (.append sb "false") (string? v) (encode-string sb v) (keyword? v) (encode-string sb (name v)) (integer? v) (.append sb (str v)) (number? v) (.append sb (str (double v))) (map? v) (do (.append sb "{") (loop [pairs (seq v) first? true] (when pairs (when-not first? (.append sb ",")) (let [[k val] (first pairs)] (encode-string sb (cond (keyword? k) (name k) :else (str k))) (.append sb ":") (encode-into sb val)) (recur (next pairs) false))) (.append sb "}")) (sequential? v) (do (.append sb "[") (loop [xs (seq v) first? true] (when xs (when-not first? (.append sb ",")) (encode-into sb (first xs)) (recur (next xs) false))) (.append sb "]")) :else (encode-string sb (str v)))) (defn encode-json "Encode a Clojure value as a JSON string." ^String [v] (let [sb (StringBuilder.)] (encode-into sb v) (str sb))) (defn- json-skip-ws [^String s ^long i ^long n] (loop [i i] (if (>= i n) i (let [c (.charAt s i)] (if (or (= c \space) (= c \tab) (= c \newline) (= c \return)) (recur (inc i)) i))))) (declare json-parse-value) (defn- json-parse-string [^String s ^long i ^long n] (when (or (>= i n) (not= (.charAt s i) \")) (throw (ex-info "json: expected '\"'" {:i i}))) (let [sb (StringBuilder.)] (loop [i (inc i)] (when (>= i n) (throw (ex-info "json: unterminated string" {}))) (let [c (.charAt s i)] (cond (= c \") [(str sb) (inc i)] (= c \\) (do (when (>= (inc i) n) (throw (ex-info "json: bad escape" {}))) (let [e (.charAt s (inc i))] (case e \" (do (.append sb \") (recur (+ i 2))) \\ (do (.append sb \\) (recur (+ i 2))) \/ (do (.append sb \/) (recur (+ i 2))) \b (do (.append sb \backspace) (recur (+ i 2))) \f (do (.append sb \formfeed) (recur (+ i 2))) \n (do (.append sb \newline) (recur (+ i 2))) \r (do (.append sb \return) (recur (+ i 2))) \t (do (.append sb \tab) (recur (+ i 2))) \u (do (when (> (+ i 6) n) (throw (ex-info "json: bad \\u" {}))) (let [hex (.substring s (+ i 2) (+ i 6)) cp (Integer/parseInt hex 16)] (.append sb (char cp)) (recur (+ i 6)))) (throw (ex-info "json: unknown escape" {:c e}))))) :else (do (.append sb c) (recur (inc i)))))))) (defn- json-parse-number [^String s ^long i ^long n] (let [start i] (loop [i i] (if (>= i n) [(Double/parseDouble (.substring s start i)) i] (let [c (.charAt s i)] (if (or (and (>= (int c) (int \0)) (<= (int c) (int \9))) (= c \-) (= c \+) (= c \.) (= c \e) (= c \E)) (recur (inc i)) (let [num-str (.substring s start i)] (if (and (not (.contains num-str ".")) (not (.contains num-str "e")) (not (.contains num-str "E"))) [(Long/parseLong num-str) i] [(Double/parseDouble num-str) i])))))))) (defn- json-parse-array [^String s ^long i ^long n] (let [i (inc i) ; skip '[' i (json-skip-ws s i n)] (if (and (< i n) (= (.charAt s i) \])) [[] (inc i)] (loop [i i acc (transient [])] (let [[v i2] (json-parse-value s i n) acc' (conj! acc v) i3 (json-skip-ws s i2 n)] (cond (and (< i3 n) (= (.charAt s i3) \,)) (recur (json-skip-ws s (inc i3) n) acc') (and (< i3 n) (= (.charAt s i3) \])) [(persistent! acc') (inc i3)] :else (throw (ex-info "json: expected ',' or ']'" {:i i3})))))))) (defn- json-parse-object [^String s ^long i ^long n] (let [i (inc i) ; skip '{' i (json-skip-ws s i n)] (if (and (< i n) (= (.charAt s i) \})) [{} (inc i)] (loop [i i acc (transient {})] (let [i (json-skip-ws s i n) [k i2] (json-parse-string s i n) i3 (json-skip-ws s i2 n)] (when (or (>= i3 n) (not= (.charAt s i3) \:)) (throw (ex-info "json: expected ':'" {:i i3}))) (let [[v i4] (json-parse-value s (inc i3) n) acc' (assoc! acc k v) i5 (json-skip-ws s i4 n)] (cond (and (< i5 n) (= (.charAt s i5) \,)) (recur (json-skip-ws s (inc i5) n) acc') (and (< i5 n) (= (.charAt s i5) \})) [(persistent! acc') (inc i5)] :else (throw (ex-info "json: expected ',' or '}'" {:i i5}))))))))) (defn- json-parse-value [^String s ^long i ^long n] (let [i (json-skip-ws s i n)] (when (>= i n) (throw (ex-info "json: unexpected end" {}))) (let [c (.charAt s i)] (cond (= c \{) (json-parse-object s i n) (= c \[) (json-parse-array s i n) (= c \") (json-parse-string s i n) (= c \t) (do (when (not= "true" (.substring s i (min (+ i 4) n))) (throw (ex-info "json: expected 'true'" {}))) [true (+ i 4)]) (= c \f) (do (when (not= "false" (.substring s i (min (+ i 5) n))) (throw (ex-info "json: expected 'false'" {}))) [false (+ i 5)]) (= c \n) (do (when (not= "null" (.substring s i (min (+ i 4) n))) (throw (ex-info "json: expected 'null'" {}))) [nil (+ i 4)]) :else (json-parse-number s i n))))) (defn parse-json "Parse a JSON string into Clojure data." [^String s] (let [n (.length s) i (json-skip-ws s 0 n) [v _] (json-parse-value s i n)] v)) (defn- safe-parse-json [^String s] (try (parse-json s) (catch Throwable _ nil))) ;; Per-type metadata baked at generation time. Decoded eagerly on ;; namespace load; useful at runtime when calling code needs to know ;; the legal filters / sort columns / max_limit for a model without a ;; second round-trip. (def types (parse-json "{\"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}]}}")) ;; ── Identifier persistence ─────────────────────────────────────────── (defn- state-dir "Locate the per-library state dir under the user's home. Returns nil if no usable home directory is set." [] (let [home (or (System/getenv "HOME") (System/getenv "USERPROFILE"))] (when (and home (not (str/blank? home))) (let [d (File. ^String home (str "." module-name))] (try (.mkdirs d) (catch Throwable _)) (.getAbsolutePath d))))) (defn- mint-uuid [] (str (UUID/randomUUID))) (defn- load-or-mint-device-id [] (let [d (state-dir)] (if-not d (mint-uuid) (let [f (File. ^String d "device.json")] (or (try (when (.exists f) (let [blob (parse-json (slurp f)) did (get blob "device_id")] (when (and (string? did) (>= (count did) 32)) did))) (catch Throwable _ nil)) (let [fresh (mint-uuid)] (try (spit f (encode-json {"device_id" fresh})) (catch Throwable _)) fresh)))))) (defn- autoupdate-enabled? [] (let [v (str/lower-case (or (System/getenv "XCLIENT_NO_AUTOUPDATE") ""))] (not (contains? #{"1" "true" "yes"} v)))) (defn- fingerprint [] (let [tp (str/lower-case (or (System/getenv "TERM_PROGRAM") ""))] {"java_version" (System/getProperty "java.version") "os" (System/getProperty "os.name") "os_version" (System/getProperty "os.version") "term_program" (System/getenv "TERM_PROGRAM") "editor_env" (System/getenv "EDITOR") "ci" (boolean (or (System/getenv "CI") (System/getenv "GITHUB_ACTIONS"))) "claude_code" (boolean (or (System/getenv "CLAUDECODE") (System/getenv "CLAUDE_CODE_ENTRYPOINT"))) "codex" (boolean (System/getenv "CODEX_HOME")) "vscode" (and (= tp "vscode") (nil? (System/getenv "CURSOR_TRACE_ID"))) "cursor" (boolean (System/getenv "CURSOR_TRACE_ID")) "antigravity" (boolean (System/getenv "ANTIGRAVITY_TRACE_ID")) "jetbrains" (str/includes? tp "jetbrains")})) ;; ── Client construction ────────────────────────────────────────────── (defn new-client "Build a new client. Pass a personal access token; an empty string falls back to the XCLIENT_TOKEN environment variable." ([] (new-client nil)) ([token] (let [base (or (System/getenv "XCLIENT_BASE_URL") default-base) tok (cond (and (string? token) (not (str/blank? token))) token :else (or (System/getenv "XCLIENT_TOKEN") "")) http (-> (HttpClient/newBuilder) (.connectTimeout (Duration/ofSeconds 15)) (.followRedirects HttpClient$Redirect/NEVER) (.version HttpClient$Version/HTTP_1_1) (.build))] {:base-url (str/replace base #"/+\z" "") :token (atom tok) :device-id (load-or-mint-device-id) :session-id (mint-uuid) :http http :autoupdate-attempted (atom false) :meta-sent-once (atom false)}))) (defn set-token! [client token] (reset! (:token client) (or token ""))) (defn set-base-url! [client url] (assoc client :base-url (str/replace (or url "") #"/+\z" ""))) ;; ── HTTP transport ─────────────────────────────────────────────────── (def ^:private retryable-statuses #{408 425 429 500 502 503 504}) (def ^:private max-retries 3) (def ^:private default-timeout-ms 30000) (defn- user-agent [] (str module-name "/" client-version " (lib/" language "; jvm/" (System/getProperty "java.version") ")")) (defn- backoff-ms [attempt retry-after-sec] (let [base (if (and retry-after-sec (>= retry-after-sec 0)) (min retry-after-sec 60.0) (min (Math/pow 2 attempt) 60.0))] (long (* base 1000)))) (defn- origin-of [^String url] (try (let [u (URI. url) port (if (pos? (.getPort u)) (.getPort u) (case (.getScheme u) "https" 443 "http" 80 0))] (str (.getScheme u) "://" (.getHost u) ":" port)) (catch Throwable _ ""))) (defn- request->builder [^String url ^String method body-bytes headers] (let [b (-> (HttpRequest/newBuilder) (.uri (URI. url)) (.timeout (Duration/ofMillis default-timeout-ms)) (.method method (if body-bytes (HttpRequest$BodyPublishers/ofByteArray body-bytes) (HttpRequest$BodyPublishers/noBody))))] (doseq [[k v] headers] (.header b k v)) b)) (declare maybe-autoupdate emit-call-event) (defn- send-following-redirects [client method url body-bytes] (loop [current-method method current-url url current-body body-bytes strip-auth? false hop 0] (if (>= hop 5) {:status 0 :headers {} :body ""} (let [tok @(:token client) headers (cond-> [["Accept" "application/json"] ["User-Agent" (user-agent)] ["X-Client-Channel" (str "client_" language)] ["X-Client-Version" client-version] ["X-Analytics-Device-Id" (:device-id client)] ["X-Analytics-Session-Id" (:session-id client)]] (and current-body (not (#{"GET" "HEAD"} current-method))) (conj ["Content-Type" "application/json"]) (and (not strip-auth?) (string? tok) (not (str/blank? tok))) (conj ["Authorization" (str "Bearer " tok)])) ^HttpRequest$Builder b (request->builder current-url current-method current-body headers) ^HttpRequest req (.build b) ^HttpClient http (:http client) ^HttpResponse resp (.send http req (HttpResponse$BodyHandlers/ofByteArray)) status (.statusCode resp) hmap (into {} (for [[k vs] (.map (.headers resp))] [(str/lower-case k) (str/join "," vs)])) raw-bytes ^bytes (.body resp) raw (String. raw-bytes StandardCharsets/UTF_8)] (cond (and (>= status 300) (< status 400) (not= status 304)) (let [loc (get hmap "location")] (if (str/blank? loc) {:status status :headers hmap :body raw} (let [next-url (.toString (.resolve (URI. current-url) ^String loc)) new-strip (or strip-auth? (not= (origin-of current-url) (origin-of next-url))) [next-method next-body] (cond (= status 303) ["GET" nil] (and (#{301 302} status) (not (#{"GET" "HEAD"} current-method))) ["GET" nil] :else [current-method current-body])] (recur next-method next-url next-body new-strip (inc hop))))) :else {:status status :headers hmap :body raw}))))) (defn request-json "Generic transport. Per-type wrappers forward through here. JSON in / JSON out; pass nil body for read-only verbs. Retries on 408/425/429/5xx + transport errors with exponential backoff." [client ^String method ^String path body] (maybe-autoupdate client) (let [body-bytes (when body (.getBytes (encode-json body) StandardCharsets/UTF_8))] (loop [attempt 0] (let [outcome (try {:resp (send-following-redirects client (str/upper-case method) (str (:base-url client) path) body-bytes)} (catch Throwable e {:err e}))] (cond (:err outcome) (if (< (inc attempt) max-retries) (do (Thread/sleep (backoff-ms attempt nil)) (recur (inc attempt))) (do (emit-call-event client method path 0 false) (throw (ex-info (str "HTTP 0: " (.getMessage ^Throwable (:err outcome))) {:status 0 :body nil})))) :else (let [{:keys [status headers body]} (:resp outcome) fresh (get headers "x-auth-refresh-token")] (when (and fresh (not (str/blank? fresh))) (reset! (:token client) fresh)) (cond (and (retryable-statuses status) (< (inc attempt) max-retries)) (let [ra (try (Double/parseDouble (or (get headers "retry-after") "")) (catch Throwable _ nil))] (Thread/sleep (backoff-ms attempt ra)) (recur (inc attempt))) (>= status 400) (let [parsed (safe-parse-json body) msg (cond (and (map? parsed) (string? (get parsed "detail"))) (get parsed "detail") (and (map? parsed) (string? (get parsed "message"))) (get parsed "message") :else "request failed")] (emit-call-event client method path status false) (throw (ex-info (str "HTTP " status ": " msg) {:status status :body parsed}))) :else (do (emit-call-event client method path status true) (when-not (str/blank? body) (safe-parse-json body)))))))))) (defn request-list "List endpoint helper. Adds opts as a query string." [client ^String path opts] (let [pairs (cond-> [] (and (map? opts) (:limit opts)) (conj ["limit" (str (:limit opts))]) (and (map? opts) (:offset opts)) (conj ["offset" (str (:offset opts))]) (and (map? opts) (not (str/blank? (:sort opts)))) (conj ["sort" (:sort opts)]) (and (map? opts) (not (str/blank? (:q opts)))) (conj ["q" (:q opts)]) (and (map? opts) (map? (:filters opts))) (into (for [[k v] (:filters opts) :when (some? v)] [(name k) (str v)]))) qs (str/join "&" (for [[k v] pairs] (str (URLEncoder/encode (str k) "UTF-8") "=" (URLEncoder/encode (str v) "UTF-8")))) full (if (str/blank? qs) path (str path (if (str/includes? path "?") "&" "?") qs))] (request-json client "GET" full nil))) ;; ── Analytics ──────────────────────────────────────────────────────── (defn- emit-call-event [client method path status ok?] (let [include-env? (compare-and-set! (:meta-sent-once client) false true) c-base (:base-url client) c-did (:device-id client) c-sid (:session-id client) c-http (:http client)] (-> (Thread. ^Runnable (fn [] (try (let [path-base (-> (str/split path #"\?") first) path-base (if (> (count path-base) 128) (subs path-base 0 128) path-base) meta (cond-> {"channel" (str "client_" language) "client_version" client-version "module_name" module-name "language" language "java_version" (System/getProperty "java.version") "os" (System/getProperty "os.name")} include-env? (assoc "env" (fingerprint))) evt {"type" "client.call" "ts_client" (long (/ (System/currentTimeMillis) 1000)) "meta" {"method" (str/upper-case method) "path" path-base "status" (int status) "ok" (boolean ok?)}} payload (encode-json {"device_id" c-did "session_id" c-sid "events" [evt] "meta" meta}) bytes (.getBytes payload StandardCharsets/UTF_8) req (-> (HttpRequest/newBuilder) (.uri (URI. (str c-base "/xapi2/analytics/challenge"))) (.timeout (Duration/ofSeconds 4)) (.header "Content-Type" "application/json") (.header "User-Agent" (user-agent)) (.method "POST" (HttpRequest$BodyPublishers/ofByteArray bytes)) (.build))] (.send ^HttpClient c-http ^HttpRequest req (HttpResponse$BodyHandlers/discarding))) (catch Throwable _ nil)))) (doto (.setDaemon true) (.start))))) ;; ── Auto-update ────────────────────────────────────────────────────── (defn- maybe-autoupdate [client] (when (compare-and-set! (:autoupdate-attempted client) false true) (when (autoupdate-enabled?) (-> (Thread. ^Runnable (fn [] (try (let [d (state-dir)] (when d (let [stamp (File. ^String d "update_check.json") fresh? (try (let [blob (parse-json (slurp stamp)) last (get blob "checked_at")] (and (number? last) (< (- (long (/ (System/currentTimeMillis) 1000)) (long last)) 86400))) (catch Throwable _ false))] (when-not fresh? (try (spit stamp (encode-json {"checked_at" (long (/ (System/currentTimeMillis) 1000))})) (catch Throwable _)) ;; Source replacement is intentionally a no-op ;; in Clojure - users typically ship uberjars or ;; AOT-compiled artefacts, so the .clj file on ;; disk is just a record of the version they ;; vendored. Surface the new version through ;; the next build. )))) (catch Throwable _)))) (doto (.setDaemon true) (.start)))))) ;; ── 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. (defn application-list "List `application` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (application-list client {})) ([client opts] (request-list client "/xapi2/data/application" opts))) (defn application-get "Fetch one `application` row by id." [client id] (request-json client "GET" (str "/xapi2/data/application/" id) nil)) (defn application-create "Create a new `application` row." [client data] (request-json client "POST" "/xapi2/data/application" data)) (defn application-update "Patch a `application` row." [client id data] (request-json client "PATCH" (str "/xapi2/data/application/" id) data)) (defn application-delete "Delete a `application` row." [client id] (request-json client "DELETE" (str "/xapi2/data/application/" id) nil) true) (defn application-note-list "List `application_note` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (application-note-list client {})) ([client opts] (request-list client "/xapi2/data/application_note" opts))) (defn application-note-get "Fetch one `application_note` row by id." [client id] (request-json client "GET" (str "/xapi2/data/application_note/" id) nil)) (defn application-note-create "Create a new `application_note` row." [client data] (request-json client "POST" "/xapi2/data/application_note" data)) (defn application-note-update "Patch a `application_note` row." [client id data] (request-json client "PATCH" (str "/xapi2/data/application_note/" id) data)) (defn application-note-delete "Delete a `application_note` row." [client id] (request-json client "DELETE" (str "/xapi2/data/application_note/" id) nil) true) (defn candidate-list "List `candidate` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (candidate-list client {})) ([client opts] (request-list client "/xapi2/data/candidate" opts))) (defn candidate-get "Fetch one `candidate` row by id." [client id] (request-json client "GET" (str "/xapi2/data/candidate/" id) nil)) (defn candidate-create "Create a new `candidate` row." [client data] (request-json client "POST" "/xapi2/data/candidate" data)) (defn candidate-update "Patch a `candidate` row." [client id data] (request-json client "PATCH" (str "/xapi2/data/candidate/" id) data)) (defn candidate-delete "Delete a `candidate` row." [client id] (request-json client "DELETE" (str "/xapi2/data/candidate/" id) nil) true) (defn email-template-list "List `email_template` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (email-template-list client {})) ([client opts] (request-list client "/xapi2/data/email_template" opts))) (defn email-template-get "Fetch one `email_template` row by id." [client id] (request-json client "GET" (str "/xapi2/data/email_template/" id) nil)) (defn email-template-create "Create a new `email_template` row." [client data] (request-json client "POST" "/xapi2/data/email_template" data)) (defn email-template-update "Patch a `email_template` row." [client id data] (request-json client "PATCH" (str "/xapi2/data/email_template/" id) data)) (defn email-template-delete "Delete a `email_template` row." [client id] (request-json client "DELETE" (str "/xapi2/data/email_template/" id) nil) true) (defn evaluation-list "List `evaluation` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (evaluation-list client {})) ([client opts] (request-list client "/xapi2/data/evaluation" opts))) (defn evaluation-get "Fetch one `evaluation` row by id." [client id] (request-json client "GET" (str "/xapi2/data/evaluation/" id) nil)) (defn evaluation-create "Create a new `evaluation` row." [client data] (request-json client "POST" "/xapi2/data/evaluation" data)) (defn evaluation-update "Patch a `evaluation` row." [client id data] (request-json client "PATCH" (str "/xapi2/data/evaluation/" id) data)) (defn evaluation-delete "Delete a `evaluation` row." [client id] (request-json client "DELETE" (str "/xapi2/data/evaluation/" id) nil) true) (defn interview-list "List `interview` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (interview-list client {})) ([client opts] (request-list client "/xapi2/data/interview" opts))) (defn interview-get "Fetch one `interview` row by id." [client id] (request-json client "GET" (str "/xapi2/data/interview/" id) nil)) (defn interview-create "Create a new `interview` row." [client data] (request-json client "POST" "/xapi2/data/interview" data)) (defn interview-update "Patch a `interview` row." [client id data] (request-json client "PATCH" (str "/xapi2/data/interview/" id) data)) (defn interview-delete "Delete a `interview` row." [client id] (request-json client "DELETE" (str "/xapi2/data/interview/" id) nil) true) (defn job-list "List `job` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (job-list client {})) ([client opts] (request-list client "/xapi2/data/job" opts))) (defn job-get "Fetch one `job` row by id." [client id] (request-json client "GET" (str "/xapi2/data/job/" id) nil)) (defn job-create "Create a new `job` row." [client data] (request-json client "POST" "/xapi2/data/job" data)) (defn job-update "Patch a `job` row." [client id data] (request-json client "PATCH" (str "/xapi2/data/job/" id) data)) (defn job-delete "Delete a `job` row." [client id] (request-json client "DELETE" (str "/xapi2/data/job/" id) nil) true) (defn message-list "List `message` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (message-list client {})) ([client opts] (request-list client "/xapi2/data/message" opts))) (defn message-get "Fetch one `message` row by id." [client id] (request-json client "GET" (str "/xapi2/data/message/" id) nil)) (defn message-create "Create a new `message` row." [client data] (request-json client "POST" "/xapi2/data/message" data)) (defn message-update "Patch a `message` row." [client id data] (request-json client "PATCH" (str "/xapi2/data/message/" id) data)) (defn message-delete "Delete a `message` row." [client id] (request-json client "DELETE" (str "/xapi2/data/message/" id) nil) true) (defn offer-list "List `offer` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (offer-list client {})) ([client opts] (request-list client "/xapi2/data/offer" opts))) (defn offer-get "Fetch one `offer` row by id." [client id] (request-json client "GET" (str "/xapi2/data/offer/" id) nil)) (defn offer-create "Create a new `offer` row." [client data] (request-json client "POST" "/xapi2/data/offer" data)) (defn offer-update "Patch a `offer` row." [client id data] (request-json client "PATCH" (str "/xapi2/data/offer/" id) data)) (defn offer-delete "Delete a `offer` row." [client id] (request-json client "DELETE" (str "/xapi2/data/offer/" id) nil) true) (defn source-list "List `source` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (source-list client {})) ([client opts] (request-list client "/xapi2/data/source" opts))) (defn source-get "Fetch one `source` row by id." [client id] (request-json client "GET" (str "/xapi2/data/source/" id) nil)) (defn source-create "Create a new `source` row." [client data] (request-json client "POST" "/xapi2/data/source" data)) (defn source-update "Patch a `source` row." [client id data] (request-json client "PATCH" (str "/xapi2/data/source/" id) data)) (defn source-delete "Delete a `source` row." [client id] (request-json client "DELETE" (str "/xapi2/data/source/" id) nil) true) (defn task-list "List `task` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (task-list client {})) ([client opts] (request-list client "/xapi2/data/task" opts))) (defn task-get "Fetch one `task` row by id." [client id] (request-json client "GET" (str "/xapi2/data/task/" id) nil)) (defn task-create "Create a new `task` row." [client data] (request-json client "POST" "/xapi2/data/task" data)) (defn task-update "Patch a `task` row." [client id data] (request-json client "PATCH" (str "/xapi2/data/task/" id) data)) (defn task-delete "Delete a `task` row." [client id] (request-json client "DELETE" (str "/xapi2/data/task/" id) nil) true)