;; 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\",\"cover_letter_url\",\"cover_letter_blob_id\",\"cv_blob_id\",\"cv_url\",\"video_url\",\"answers\",\"fit_score\",\"fit_reasoning\",\"fit_flags\",\"fit_computed_at\",\"fit_breakdown\",\"fit_strengths\",\"fit_gaps\",\"fit_interview_questions\",\"rejected_reason\",\"rejected_note\",\"tags\"],\"update_fields\":[\"stage\",\"previous_stage\",\"position\",\"applied_at\",\"last_stage_at\",\"source_id\",\"source_label\",\"cover_letter\",\"cover_letter_url\",\"cover_letter_blob_id\",\"cv_blob_id\",\"cv_url\",\"video_url\",\"answers\",\"fit_score\",\"fit_reasoning\",\"fit_flags\",\"fit_computed_at\",\"fit_breakdown\",\"fit_strengths\",\"fit_gaps\",\"fit_interview_questions\",\"rejected_reason\",\"rejected_note\",\"tags\",\"starred\"],\"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\",\"withdrawn\"]},{\"name\":\"cv_url\",\"type\":\"url\",\"max_len\":2048},{\"name\":\"job_id\",\"type\":\"string\",\"max_len\":64,\"ref\":{\"type\":\"job\",\"owned\":false,\"optional\":false}},{\"name\":\"skills\",\"type\":\"tags\",\"max_count\":100,\"item_max\":160},{\"name\":\"source\",\"type\":\"string\",\"max_len\":64},{\"name\":\"answers\",\"type\":\"dict\",\"max_keys\":80},{\"name\":\"fit_gaps\",\"type\":\"list\"},{\"name\":\"position\",\"type\":\"number\"},{\"name\":\"education\",\"type\":\"list\"},{\"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\":\"video_url\",\"type\":\"url\",\"max_len\":2048},{\"name\":\"applied_at\",\"type\":\"string\",\"max_len\":32},{\"name\":\"cv_blob_id\",\"type\":\"string\",\"max_len\":64},{\"name\":\"experience\",\"type\":\"list\"},{\"name\":\"source_uid\",\"type\":\"number\"},{\"name\":\"attachments\",\"type\":\"list\"},{\"name\":\"cv_feedback\",\"type\":\"enum\",\"values\":[\"ok\",\"edited\",\"incomplete\"]},{\"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\":\"status_token\",\"type\":\"string\",\"max_len\":64},{\"name\":\"fit_breakdown\",\"type\":\"dict\"},{\"name\":\"fit_reasoning\",\"type\":\"string\",\"max_len\":4000},{\"name\":\"fit_strengths\",\"type\":\"list\"},{\"name\":\"last_stage_at\",\"type\":\"string\",\"max_len\":32},{\"name\":\"rejected_note\",\"type\":\"string\",\"max_len\":2000},{\"name\":\"source_folder\",\"type\":\"string\",\"max_len\":200},{\"name\":\"stage_history\",\"type\":\"list\"},{\"name\":\"previous_stage\",\"type\":\"string\",\"max_len\":32},{\"name\":\"source_subject\",\"type\":\"string\",\"max_len\":500},{\"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\"]},{\"name\":\"source_inbox_id\",\"type\":\"string\",\"max_len\":64},{\"name\":\"cover_letter_url\",\"type\":\"url\",\"max_len\":2048},{\"name\":\"source_followups\",\"type\":\"list\"},{\"name\":\"source_from_addr\",\"type\":\"string\",\"max_len\":320},{\"name\":\"source_from_name\",\"type\":\"string\",\"max_len\":200},{\"name\":\"source_account_id\",\"type\":\"string\",\"max_len\":64},{\"name\":\"source_message_id\",\"type\":\"string\",\"max_len\":400},{\"name\":\"source_match_score\",\"type\":\"number\"},{\"name\":\"source_received_at\",\"type\":\"string\",\"max_len\":32},{\"name\":\"source_body_preview\",\"type\":\"string\",\"max_len\":2000},{\"name\":\"source_match_reason\",\"type\":\"string\",\"max_len\":400},{\"name\":\"cover_letter_blob_id\",\"type\":\"string\",\"max_len\":64},{\"name\":\"last_inbound_email_at\",\"type\":\"string\",\"max_len\":32},{\"name\":\"fit_interview_questions\",\"type\":\"list\"}]},\"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\"]}]},\"audit_event\":{\"ops\":[\"list\",\"read\"],\"create_fields\":[\"category\",\"action\",\"actor_id\",\"actor_label\",\"entity_type\",\"entity_id\",\"entity_label\",\"occurred_at\",\"details\",\"ip\",\"user_agent\"],\"update_fields\":[],\"allowed_filters\":[\"data__category\",\"data__actor_id\",\"data__entity_type\",\"data__entity_id\",\"data__action\",\"created_at\",\"owned_by\",\"created_by\"],\"allowed_sorts\":[\"created_at\",\"data__occurred_at\",\"data__category\"],\"default_sort\":\"created_at\",\"max_limit\":200,\"fields\":[{\"name\":\"ip\",\"type\":\"string\",\"max_len\":64},{\"name\":\"action\",\"type\":\"string\",\"max_len\":64},{\"name\":\"details\",\"type\":\"dict\"},{\"name\":\"actor_id\",\"type\":\"string\",\"max_len\":64},{\"name\":\"category\",\"type\":\"enum\",\"values\":[\"mutation\",\"auth\",\"email\",\"ai\",\"system\"]},{\"name\":\"entity_id\",\"type\":\"string\",\"max_len\":64},{\"name\":\"user_agent\",\"type\":\"string\",\"max_len\":500},{\"name\":\"actor_label\",\"type\":\"string\",\"max_len\":200},{\"name\":\"entity_type\",\"type\":\"string\",\"max_len\":64},{\"name\":\"occurred_at\",\"type\":\"string\",\"max_len\":32},{\"name\":\"entity_label\",\"type\":\"string\",\"max_len\":200}]},\"candidate\":{\"ops\":[\"list\",\"read\",\"create\",\"update\",\"delete\"],\"create_fields\":[\"name\",\"first_name\",\"last_name\",\"salutation\",\"pronouns\",\"email\",\"phone\",\"street\",\"postal_code\",\"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\",\"street\",\"postal_code\",\"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\",\"max_count\":100,\"item_max\":160},{\"name\":\"street\",\"type\":\"string\",\"max_len\":200},{\"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\",\"max_count\":60,\"item_max\":80},{\"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\":true}},{\"name\":\"avatar_url\",\"type\":\"url\",\"max_len\":2048},{\"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\",\"anonymised\"]},{\"name\":\"postal_code\",\"type\":\"string\",\"max_len\":32},{\"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\":\"gdpr_anonymised_at\",\"type\":\"string\",\"max_len\":32},{\"name\":\"salary_expectation\",\"type\":\"number\"},{\"name\":\"cover_letter_blob_id\",\"type\":\"string\",\"max_len\":64},{\"name\":\"gdpr_retention_until\",\"type\":\"string\",\"max_len\":32}]},\"company_profile\":{\"ops\":[\"list\",\"read\",\"create\",\"update\",\"delete\"],\"create_fields\":[\"name\",\"display_name\",\"logo_url\",\"banner_url\",\"accent_color\",\"summary\",\"website\",\"size\",\"industry\",\"main_location\",\"privacy_url\",\"imprint_url\",\"retention_value\",\"retention_unit\",\"public_careers_enabled\",\"show_other_jobs\",\"show_about\"],\"update_fields\":[\"name\",\"display_name\",\"logo_url\",\"banner_url\",\"accent_color\",\"summary\",\"website\",\"size\",\"industry\",\"main_location\",\"privacy_url\",\"imprint_url\",\"retention_value\",\"retention_unit\",\"public_careers_enabled\",\"show_other_jobs\",\"show_about\"],\"allowed_filters\":[\"status\",\"is_archived\",\"owned_by\",\"created_by\"],\"allowed_sorts\":[\"created_at\",\"updated_at\"],\"default_sort\":\"created_at\",\"max_limit\":5,\"fields\":[{\"name\":\"name\",\"type\":\"string\",\"max_len\":200},{\"name\":\"size\",\"type\":\"enum\",\"values\":[\"1-10\",\"11-50\",\"51-200\",\"201-500\",\"500+\"]},{\"name\":\"summary\",\"type\":\"string\",\"max_len\":4000},{\"name\":\"website\",\"type\":\"url\",\"max_len\":2048},{\"name\":\"industry\",\"type\":\"string\",\"max_len\":200},{\"name\":\"logo_url\",\"type\":\"string\",\"max_len\":1500000},{\"name\":\"banner_url\",\"type\":\"string\",\"max_len\":2500000},{\"name\":\"show_about\",\"type\":\"bool\"},{\"name\":\"imprint_url\",\"type\":\"url\",\"max_len\":2048},{\"name\":\"privacy_url\",\"type\":\"url\",\"max_len\":2048},{\"name\":\"accent_color\",\"type\":\"string\",\"max_len\":16},{\"name\":\"display_name\",\"type\":\"string\",\"max_len\":200},{\"name\":\"main_location\",\"type\":\"string\",\"max_len\":200},{\"name\":\"retention_unit\",\"type\":\"enum\",\"values\":[\"weeks\",\"months\",\"years\"]},{\"name\":\"retention_value\",\"type\":\"number\"},{\"name\":\"show_other_jobs\",\"type\":\"bool\"},{\"name\":\"public_careers_enabled\",\"type\":\"bool\"}]},\"email_import_log\":{\"ops\":[\"list\",\"read\",\"create\"],\"create_fields\":[\"email_inbox_id\",\"status\",\"sender\",\"sender_name\",\"sender_email\",\"subject\",\"received_at\",\"message_id\",\"source_folder\",\"source_uid\",\"preview\",\"attachments\",\"signals\",\"matched_job_id\",\"matched_job_title\",\"matched_application_id\",\"matched_candidate_id\",\"score\",\"match_score\",\"reason\",\"error_message\"],\"update_fields\":[],\"allowed_filters\":[\"data__status\",\"data__email_inbox_id\",\"data__matched_job_id\",\"data__matched_candidate_id\",\"data__matched_application_id\",\"data__message_id\",\"status\",\"owned_by\",\"created_by\"],\"allowed_sorts\":[\"created_at\",\"data__received_at\"],\"default_sort\":\"created_at\",\"max_limit\":200,\"fields\":[{\"name\":\"score\",\"type\":\"number\"},{\"name\":\"reason\",\"type\":\"string\",\"max_len\":1000},{\"name\":\"sender\",\"type\":\"string\",\"max_len\":320},{\"name\":\"status\",\"type\":\"enum\",\"values\":[\"imported\",\"duplicate\",\"ignored\",\"error\"]},{\"name\":\"preview\",\"type\":\"string\",\"max_len\":2000},{\"name\":\"signals\",\"type\":\"list\"},{\"name\":\"subject\",\"type\":\"string\",\"max_len\":500},{\"name\":\"message_id\",\"type\":\"string\",\"max_len\":400},{\"name\":\"source_uid\",\"type\":\"number\"},{\"name\":\"attachments\",\"type\":\"list\"},{\"name\":\"match_score\",\"type\":\"number\"},{\"name\":\"received_at\",\"type\":\"string\",\"max_len\":32},{\"name\":\"sender_name\",\"type\":\"string\",\"max_len\":200},{\"name\":\"sender_email\",\"type\":\"string\",\"max_len\":320},{\"name\":\"error_message\",\"type\":\"string\",\"max_len\":1000},{\"name\":\"source_folder\",\"type\":\"string\",\"max_len\":200},{\"name\":\"email_inbox_id\",\"type\":\"string\",\"max_len\":64},{\"name\":\"matched_job_id\",\"type\":\"string\",\"max_len\":64},{\"name\":\"matched_job_title\",\"type\":\"string\",\"max_len\":200},{\"name\":\"matched_candidate_id\",\"type\":\"string\",\"max_len\":64},{\"name\":\"matched_application_id\",\"type\":\"string\",\"max_len\":64}]},\"email_inbox\":{\"ops\":[\"list\",\"read\",\"create\",\"update\",\"delete\"],\"create_fields\":[\"mail_account_id\",\"provider\",\"connected_at\",\"active\",\"email_address\",\"host\",\"port\",\"username\",\"scan_enabled\",\"scan_folder\",\"filter_min_score\",\"filter_sender_allowlist\",\"filter_sender_blocklist\",\"auto_match_jobs\",\"auto_create_candidates\",\"last_scan_at\",\"last_scan_uid\",\"last_scan_error\",\"total_scanned\",\"total_imported\",\"total_duplicates\",\"total_ignored\",\"total_errors\"],\"update_fields\":[\"provider\",\"connected_at\",\"active\",\"email_address\",\"host\",\"port\",\"username\",\"scan_enabled\",\"scan_folder\",\"filter_min_score\",\"filter_sender_allowlist\",\"filter_sender_blocklist\",\"auto_match_jobs\",\"auto_create_candidates\",\"last_scan_at\",\"last_scan_uid\",\"last_scan_error\",\"total_scanned\",\"total_imported\",\"total_duplicates\",\"total_ignored\",\"total_errors\"],\"allowed_filters\":[\"data__mail_account_id\",\"data__active\",\"data__scan_enabled\",\"data__provider\",\"status\",\"is_archived\",\"owned_by\",\"created_by\"],\"allowed_sorts\":[\"created_at\",\"updated_at\",\"data__connected_at\",\"data__last_scan_at\"],\"default_sort\":\"created_at\",\"max_limit\":10,\"fields\":[{\"name\":\"host\",\"type\":\"string\",\"max_len\":200},{\"name\":\"port\",\"type\":\"number\"},{\"name\":\"active\",\"type\":\"bool\"},{\"name\":\"provider\",\"type\":\"string\",\"max_len\":32},{\"name\":\"username\",\"type\":\"string\",\"max_len\":200},{\"name\":\"scan_folder\",\"type\":\"string\",\"max_len\":200},{\"name\":\"connected_at\",\"type\":\"string\",\"max_len\":32},{\"name\":\"last_scan_at\",\"type\":\"string\",\"max_len\":32},{\"name\":\"scan_enabled\",\"type\":\"bool\"},{\"name\":\"total_errors\",\"type\":\"number\"},{\"name\":\"email_address\",\"type\":\"string\",\"max_len\":320},{\"name\":\"last_scan_uid\",\"type\":\"number\"},{\"name\":\"total_ignored\",\"type\":\"number\"},{\"name\":\"total_scanned\",\"type\":\"number\"},{\"name\":\"total_imported\",\"type\":\"number\"},{\"name\":\"auto_match_jobs\",\"type\":\"bool\"},{\"name\":\"last_scan_error\",\"type\":\"string\",\"max_len\":400},{\"name\":\"mail_account_id\",\"type\":\"string\",\"max_len\":64,\"ref\":{\"type\":\"mail_account\",\"owned\":false,\"optional\":false}},{\"name\":\"filter_min_score\",\"type\":\"number\"},{\"name\":\"total_duplicates\",\"type\":\"number\"},{\"name\":\"auto_create_candidates\",\"type\":\"bool\"},{\"name\":\"filter_sender_allowlist\",\"type\":\"list\"},{\"name\":\"filter_sender_blocklist\",\"type\":\"list\"}]},\"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\",\"data__auto_send\",\"data__language\",\"status\",\"is_archived\",\"owned_by\",\"created_by\"],\"allowed_sorts\":[\"created_at\",\"updated_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\",\"interview_notes\",\"skills_score\",\"culture_score\",\"communication_score\",\"potential_score\",\"problem_solving\",\"analytical_thinking\",\"learning_agility\",\"attention_to_detail\",\"adaptability\",\"teamwork\",\"overall_score\",\"recommendation\",\"highlights\",\"concerns\",\"summary\"],\"update_fields\":[\"interview_notes\",\"skills_score\",\"culture_score\",\"communication_score\",\"potential_score\",\"problem_solving\",\"analytical_thinking\",\"learning_agility\",\"attention_to_detail\",\"adaptability\",\"teamwork\",\"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\":\"teamwork\",\"type\":\"number\"},{\"name\":\"highlights\",\"type\":\"string\",\"max_len\":4000},{\"name\":\"adaptability\",\"type\":\"number\"},{\"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\":\"interview_notes\",\"type\":\"string\",\"max_len\":16000},{\"name\":\"potential_score\",\"type\":\"number\"},{\"name\":\"problem_solving\",\"type\":\"number\"},{\"name\":\"learning_agility\",\"type\":\"number\"},{\"name\":\"analytical_thinking\",\"type\":\"number\"},{\"name\":\"attention_to_detail\",\"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\",\"skills_interview\",\"cultural_interview\"]},{\"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\",\"work_arrangement\",\"work_locations\",\"employment_type\",\"seniority\",\"career_level\",\"headcount\",\"salary_min\",\"salary_max\",\"salary_period\",\"currency\",\"salary_visibility\",\"summary\",\"intro\",\"description\",\"responsibilities\",\"requirements\",\"nice_to_have\",\"benefits\",\"closing_words\",\"language\",\"application_language\",\"job_category\",\"job_subcategory\",\"skills\",\"status\",\"public\",\"ai_screen_enabled\",\"ai_screen_prompt\",\"ai_criteria\",\"ai_thresholds\",\"knockout_questions\",\"screening_questions\",\"application_documents\",\"auto_send_confirmation\",\"pipeline_id\",\"tags\",\"hiring_manager_id\",\"hiring_team\",\"team_ids\",\"opened_at\",\"target_close_date\",\"closed_at\",\"external_apply_url\",\"color\"],\"update_fields\":[\"title\",\"slug\",\"department\",\"location\",\"country\",\"remote\",\"work_arrangement\",\"work_locations\",\"employment_type\",\"seniority\",\"career_level\",\"headcount\",\"salary_min\",\"salary_max\",\"salary_period\",\"currency\",\"salary_visibility\",\"summary\",\"intro\",\"description\",\"responsibilities\",\"requirements\",\"nice_to_have\",\"benefits\",\"closing_words\",\"language\",\"application_language\",\"job_category\",\"job_subcategory\",\"skills\",\"status\",\"public\",\"ai_screen_enabled\",\"ai_screen_prompt\",\"ai_criteria\",\"ai_thresholds\",\"knockout_questions\",\"screening_questions\",\"application_documents\",\"auto_send_confirmation\",\"pipeline_id\",\"tags\",\"hiring_manager_id\",\"hiring_team\",\"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\":\"intro\",\"type\":\"string\",\"max_len\":8000},{\"name\":\"title\",\"type\":\"string\",\"max_len\":200},{\"name\":\"public\",\"type\":\"bool\"},{\"name\":\"remote\",\"type\":\"enum\",\"values\":[\"onsite\",\"hybrid\",\"remote\"]},{\"name\":\"skills\",\"type\":\"tags\"},{\"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\":\"promotions\",\"type\":\"list\"},{\"name\":\"salary_max\",\"type\":\"number\"},{\"name\":\"salary_min\",\"type\":\"number\"},{\"name\":\"ai_criteria\",\"type\":\"list\"},{\"name\":\"description\",\"type\":\"string\",\"max_len\":16000},{\"name\":\"hiring_team\",\"type\":\"list\"},{\"name\":\"pipeline_id\",\"type\":\"string\",\"max_len\":64},{\"name\":\"address_form\",\"type\":\"enum\",\"values\":[\"du\",\"sie\"]},{\"name\":\"career_level\",\"type\":\"enum\",\"values\":[\"helper\",\"entry\",\"experienced\",\"team_lead\",\"executive\"]},{\"name\":\"job_category\",\"type\":\"string\",\"max_len\":64},{\"name\":\"nice_to_have\",\"type\":\"string\",\"max_len\":4000},{\"name\":\"requirements\",\"type\":\"string\",\"max_len\":8000},{\"name\":\"video_prompt\",\"type\":\"string\",\"max_len\":2000},{\"name\":\"closing_words\",\"type\":\"string\",\"max_len\":4000},{\"name\":\"salary_period\",\"type\":\"enum\",\"values\":[\"year\",\"month\",\"week\",\"day\",\"hour\"]},{\"name\":\"boards_enabled\",\"type\":\"list\"},{\"name\":\"work_locations\",\"type\":\"list\"},{\"name\":\"auto_promote_at\",\"type\":\"number\"},{\"name\":\"employment_type\",\"type\":\"enum\",\"values\":[\"full_time\",\"part_time\",\"internship\",\"working_student\",\"freelance\",\"contract\",\"dual_study\",\"trainee\",\"side_job\"]},{\"name\":\"job_subcategory\",\"type\":\"string\",\"max_len\":64},{\"name\":\"ai_screen_prompt\",\"type\":\"string\",\"max_len\":4000},{\"name\":\"responsibilities\",\"type\":\"string\",\"max_len\":8000},{\"name\":\"scoring_criteria\",\"type\":\"list\"},{\"name\":\"work_arrangement\",\"type\":\"enum\",\"values\":[\"onsite\",\"hybrid\",\"remote\",\"office\"]},{\"name\":\"ai_screen_enabled\",\"type\":\"bool\"},{\"name\":\"auto_reject_below\",\"type\":\"number\"},{\"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\":\"video_max_minutes\",\"type\":\"number\"},{\"name\":\"external_apply_url\",\"type\":\"url\",\"max_len\":2048},{\"name\":\"knockout_questions\",\"type\":\"list\"},{\"name\":\"screening_questions\",\"type\":\"list\"},{\"name\":\"application_language\",\"type\":\"string\",\"max_len\":16},{\"name\":\"application_documents\",\"type\":\"dict\"},{\"name\":\"auto_send_confirmation\",\"type\":\"bool\"},{\"name\":\"auto_thresholds_enabled\",\"type\":\"bool\"}]},\"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\":true}},{\"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 audit-event-list "List `audit_event` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (audit-event-list client {})) ([client opts] (request-list client "/xapi2/data/audit_event" opts))) (defn audit-event-get "Fetch one `audit_event` row by id." [client id] (request-json client "GET" (str "/xapi2/data/audit_event/" id) nil)) (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 company-profile-list "List `company_profile` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (company-profile-list client {})) ([client opts] (request-list client "/xapi2/data/company_profile" opts))) (defn company-profile-get "Fetch one `company_profile` row by id." [client id] (request-json client "GET" (str "/xapi2/data/company_profile/" id) nil)) (defn company-profile-create "Create a new `company_profile` row." [client data] (request-json client "POST" "/xapi2/data/company_profile" data)) (defn company-profile-update "Patch a `company_profile` row." [client id data] (request-json client "PATCH" (str "/xapi2/data/company_profile/" id) data)) (defn company-profile-delete "Delete a `company_profile` row." [client id] (request-json client "DELETE" (str "/xapi2/data/company_profile/" id) nil) true) (defn email-import-log-list "List `email_import_log` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (email-import-log-list client {})) ([client opts] (request-list client "/xapi2/data/email_import_log" opts))) (defn email-import-log-get "Fetch one `email_import_log` row by id." [client id] (request-json client "GET" (str "/xapi2/data/email_import_log/" id) nil)) (defn email-import-log-create "Create a new `email_import_log` row." [client data] (request-json client "POST" "/xapi2/data/email_import_log" data)) (defn email-inbox-list "List `email_inbox` rows. Pass an opts map: {:limit, :offset, :sort, :q, :filters}." ([client] (email-inbox-list client {})) ([client opts] (request-list client "/xapi2/data/email_inbox" opts))) (defn email-inbox-get "Fetch one `email_inbox` row by id." [client id] (request-json client "GET" (str "/xapi2/data/email_inbox/" id) nil)) (defn email-inbox-create "Create a new `email_inbox` row." [client data] (request-json client "POST" "/xapi2/data/email_inbox" data)) (defn email-inbox-update "Patch a `email_inbox` row." [client id data] (request-json client "PATCH" (str "/xapi2/data/email_inbox/" id) data)) (defn email-inbox-delete "Delete a `email_inbox` row." [client id] (request-json client "DELETE" (str "/xapi2/data/email_inbox/" 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)