import {z} from 'zod' export type DateISO = string; export type CandidateCustomAttributePathDto = string; export type DmCurrency = 'AED' | 'AFN' | 'ALL' | 'AMD' | 'ANG' | 'AOA' | 'ARS' | 'AUD' | 'AWG' | 'AZN' | 'BAM' | 'BBD' | 'BDT' | 'BGN' | 'BHD' | 'BIF' | 'BMD' | 'BND' | 'BOB' | 'BRL' | 'BSD' | 'BTC' | 'BTN' | 'BWP' | 'BYN' | 'BZD' | 'CAD' | 'CDF' | 'CHF' | 'CLF' | 'CLP' | 'CNH' | 'CNY' | 'COP' | 'CRC' | 'CUC' | 'CUP' | 'CVE' | 'CZK' | 'DJF' | 'DKK' | 'DOP' | 'DZD' | 'EGP' | 'ERN' | 'ETB' | 'EUR' | 'FJD' | 'FKP' | 'GBP' | 'GEL' | 'GGP' | 'GHS' | 'GIP' | 'GMD' | 'GNF' | 'GTQ' | 'GYD' | 'HKD' | 'HNL' | 'HRK' | 'HTG' | 'HUF' | 'IDR' | 'ILS' | 'IMP' | 'INR' | 'IQD' | 'IRR' | 'ISK' | 'JEP' | 'JMD' | 'JOD' | 'JPY' | 'KES' | 'KGS' | 'KHR' | 'KMF' | 'KPW' | 'KRW' | 'KWD' | 'KYD' | 'KZT' | 'LAK' | 'LBP' | 'LKR' | 'LRD' | 'LSL' | 'LYD' | 'MAD' | 'MDL' | 'MGA' | 'MKD' | 'MMK' | 'MNT' | 'MOP' | 'MRU' | 'MUR' | 'MVR' | 'MWK' | 'MXN' | 'MYR' | 'MZN' | 'NAD' | 'NGN' | 'NIO' | 'NOK' | 'NPR' | 'NZD' | 'OMR' | 'PAB' | 'PEN' | 'PGK' | 'PHP' | 'PKR' | 'PLN' | 'PYG' | 'QAR' | 'RON' | 'RSD' | 'RUB' | 'RWF' | 'SAR' | 'SBD' | 'SCR' | 'SDG' | 'SEK' | 'SGD' | 'SHP' | 'SLE' | 'SLL' | 'SOS' | 'SRD' | 'SSP' | 'STD' | 'STN' | 'SVC' | 'SYP' | 'SZL' | 'THB' | 'TJS' | 'TMT' | 'TND' | 'TOP' | 'TRY' | 'TTD' | 'TWD' | 'TZS' | 'UAH' | 'UGX' | 'USD' | 'UYU' | 'UZS' | 'VEF' | 'VES' | 'VND' | 'VUV' | 'WST' | 'XAF' | 'XAG' | 'XAU' | 'XCD' | 'XCG' | 'XDR' | 'XOF' | 'XPD' | 'XPF' | 'XPT' | 'YER' | 'ZAR' | 'ZMW' | 'ZWG' | 'ZWL'; export type AppliedCandidateFilterDto = {type: 'text', operator: 'contains' | 'equals' | 'startsWith' | 'notEquals', path: 'candidate.firstName' | 'candidate.lastName', value: string} | {type: 'text', operator: 'isEmpty' | 'isNotEmpty', path: 'candidate.emailAddresses' | 'candidate.phoneNumbers' | 'candidate.linkedInUrl'} | {type: 'entitySelect', operator: 'in' | 'notIn', path: 'candidate.mainContact', value: string | null} | {type: 'date', operator: 'equals' | 'notEquals' | 'lessThanOrEqual' | 'greaterThanOrEqual', path: 'candidate.createdAt', value: DateISO} | {type: 'dateRange', operator: 'between', path: 'candidate.createdAt', value: {from: DateISO, to: DateISO}} | {type: 'text', operator: 'contains' | 'equals' | 'startsWith' | 'notEquals', path: CandidateCustomAttributePathDto, value: string} | {type: 'date', operator: 'lessThanOrEqual' | 'greaterThanOrEqual' | 'equals' | 'notEquals', path: CandidateCustomAttributePathDto, value: DateISO} | {type: 'date', operator: 'isEmpty' | 'isNotEmpty', path: CandidateCustomAttributePathDto} | {type: 'date', operator: 'between', path: CandidateCustomAttributePathDto, value: {from: DateISO, to: DateISO}} | {type: 'number', operator: 'greaterThanOrEqual' | 'lessThanOrEqual', path: CandidateCustomAttributePathDto, value: number} | {type: 'number', operator: 'between', path: CandidateCustomAttributePathDto, value: {from: number, to: number}} | {type: 'entitySelect', operator: 'in' | 'notIn', path: CandidateCustomAttributePathDto, value: string | null} | {type: 'entitySelect', operator: 'in' | 'notIn' | 'isAllOf', path: CandidateCustomAttributePathDto, value: Array} | {type: 'boolean', operator: 'equals' | 'notEquals', path: CandidateCustomAttributePathDto, value: boolean} | {type: 'enumSelect', operator: 'in' | 'notIn' | 'isAllOf', path: CandidateCustomAttributePathDto, value: Array} | {type: 'enumListSelect', operator: 'contains' | 'doesNotContain', path: CandidateCustomAttributePathDto, value: string | null} | {type: 'currency', operator: 'greaterThanOrEqual' | 'lessThanOrEqual', path: CandidateCustomAttributePathDto, value: {amount: number, currency: DmCurrency}} | {type: 'duration', operator: 'greaterThanOrEqual' | 'lessThanOrEqual', path: CandidateCustomAttributePathDto, value: number} | {type: 'url', operator: 'equals' | 'notEquals' | 'contains' | 'startsWith', path: CandidateCustomAttributePathDto, value: string} | {type: 'timestamp', operator: 'greaterThanOrEqual' | 'lessThanOrEqual', path: CandidateCustomAttributePathDto, value: DateISO} | {type: 'rating', operator: 'greaterThanOrEqual' | 'lessThanOrEqual' | 'equals', path: CandidateCustomAttributePathDto, value: number} | {type: 'rating', operator: 'between', path: CandidateCustomAttributePathDto, value: {from: number, to: number}}; export type SearchCandidatesRequestDto = {filters?: Array | undefined, page?: number | undefined, pageSize?: number | undefined}; export type CreateApiCandidatesSearchArgs = {body: SearchCandidatesRequestDto}; export type CreateApiCandidatesSearchOptions = {baseUrl: string, xApiKey?: string | undefined, fetch?: typeof fetch | undefined}; export const locationType = z.enum(["primary_home", "secondary_home", "primary_work", "headquarters", "regional_office"]); export const locationDto = z.object({street1: z.string().nullable(), street2: z.string().nullable(), postalCode: z.string().nullable(), city: z.string().nullable(), region: z.string().nullable(), state: z.string().nullable(), country: z.string().nullable(), rawCityCountry: z.string().nullable(), latitude: z.number().nullable(), longitude: z.number().nullable(), formattedAddress: z.string().nullable(), type: locationType.nullable()}); export const dateISO = z.string(); export const userDto = z.object({userId: z.string(), name: z.string(), email: z.string(), avatarUrl: z.string().nullable(), deactivatedAt: dateISO.nullable()}); export const candidateLanguage = z.enum(["Afrikaans", "Albanian", "Amharic", "Arabic", "Armenian", "Assamese", "Aymara", "Azerbaijani", "Bangla", "Basque", "Belarusian", "Bemba", "Berber", "Bislama", "Bosnian", "Bulgarian", "Burman", "Catalan", "Chechen", "Chewa", "Chinese (Mandarin)", "Chinese (Cantonese)", "Chittagonian", "Croatian", "Czech", "Danish", "Dari", "Dhivehi", "Dutch", "Dzongkha", "English", "Estonian", "Ewe", "Fijian", "Filipino", "Finnish", "French", "Galician", "Georgian", "German", "Greek", "Guarani", "Gujarati", "Haitian Creole", "Hausa", "Hebrew", "Hindi", "Hungarian", "Icelandic", "Igbo", "Indonesian", "Irish", "Italian", "Japanese", "Kannada", "Kazakh", "Khmer", "Kinyarwanda", "Kirundi", "Korean", "Kurdish", "Kyrgyz", "Lao", "Latvian", "Lingala", "Lithuanian", "Luganda", "Luxembourgish", "Macedonian", "Malagasy", "Malay", "Malayalam", "Maltese", "Maori", "Marathi", "Moldovan", "Mongolian", "Montenegrin", "Ndebele", "Nepali", "Norwegian", "Odia", "Oromo", "Pashto", "Persian", "Polish", "Portuguese", "Punjabi", "Quechua", "Romanian", "Russian", "Samoan", "Sango", "Serbian", "Sesotho", "Setswana", "Shona", "Sindhi", "Sinhala", "Slovak", "Slovenian", "Somali", "Spanish", "Swahili", "Swedish", "Tajik", "Tamil", "Telugu", "Tetum", "Thai", "Tigrinya", "Tok Pisin", "Tongan", "Tsonga", "Tswana", "Turkish", "Turkmen", "Ukrainian", "Urdu", "Uzbek", "Venda", "Vietnamese", "Welsh", "Xhosa", "Yoruba", "Zulu"]); export const candidateFluency = z.enum(["Elementary proficiency", "Limited working proficiency", "Professional working proficiency", "Full professional proficiency", "Native or bilingual proficiency"]).nullable(); export const languageDto = z.object({id: z.string().min(1).optional(), language: candidateLanguage, fluency: candidateFluency.optional()}); export const candidateCurrency = z.enum(["AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BRL", "BSD", "BTC", "BTN", "BWP", "BYN", "BZD", "CAD", "CDF", "CHF", "CLF", "CLP", "CNH", "CNY", "COP", "CRC", "CUC", "CUP", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GGP", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "IMP", "INR", "IQD", "IRR", "ISK", "JEP", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRU", "MUR", "MVR", "MWK", "MXN", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLE", "SLL", "SOS", "SRD", "SSP", "STD", "STN", "SVC", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VEF", "VES", "VND", "VUV", "WST", "XAF", "XAG", "XAU", "XCD", "XCG", "XDR", "XOF", "XPD", "XPF", "XPT", "YER", "ZAR", "ZMW", "ZWG", "ZWL"]); export const compensationDto = z.object({id: z.string().min(1).optional(), currency: candidateCurrency, totalCompensation: z.number().nullable().optional(), baseCompensation: z.number().nullable().optional()}); export const nextTaskDueDto = z.object({id: z.string().min(1), content: z.string(), dueDate: dateISO.nullable()}); export const companyBadgeDto = z.object({id: z.string(), name: z.string(), logoUrl: z.string().nullable()}); export const workExperienceBadgeDto = z.object({title: z.string().nullable(), company: companyBadgeDto.nullable()}); export const dmCurrency = z.enum(["AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BRL", "BSD", "BTC", "BTN", "BWP", "BYN", "BZD", "CAD", "CDF", "CHF", "CLF", "CLP", "CNH", "CNY", "COP", "CRC", "CUC", "CUP", "CVE", "CZK", "DJF", "DKK", "DOP", "DZD", "EGP", "ERN", "ETB", "EUR", "FJD", "FKP", "GBP", "GEL", "GGP", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", "HKD", "HNL", "HRK", "HTG", "HUF", "IDR", "ILS", "IMP", "INR", "IQD", "IRR", "ISK", "JEP", "JMD", "JOD", "JPY", "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", "LAK", "LBP", "LKR", "LRD", "LSL", "LYD", "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRU", "MUR", "MVR", "MWK", "MXN", "MYR", "MZN", "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", "OMR", "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", "QAR", "RON", "RSD", "RUB", "RWF", "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLE", "SLL", "SOS", "SRD", "SSP", "STD", "STN", "SVC", "SYP", "SZL", "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", "UAH", "UGX", "USD", "UYU", "UZS", "VEF", "VES", "VND", "VUV", "WST", "XAF", "XAG", "XAU", "XCD", "XCG", "XDR", "XOF", "XPD", "XPF", "XPT", "YER", "ZAR", "ZMW", "ZWG", "ZWL"]); export const monetaryAmountDto = z.object({currency: dmCurrency, amount: z.number()}); export const dateRangeDto = z.object({startDate: dateISO, endDate: dateISO.nullable()}); export const userBadgeDto = z.object({id: z.string(), name: z.string(), avatarUrl: z.string().nullable(), deactivatedAt: dateISO.nullable()}); export const sharedCustomAttributeTypeDto = z.union([z.object({viewType: z.literal("customText"), text: z.string().nullable()}), z.object({viewType: z.literal("customNumber"), number: z.number().nullable()}), z.object({viewType: z.literal("customCheckbox"), checked: z.boolean().nullable()}), z.object({viewType: z.literal("customDate"), date: dateISO.nullable()}), z.object({viewType: z.literal("customTimestamp"), timestamp: dateISO.nullable()}), z.object({viewType: z.literal("customRating"), rating: z.number().nullable()}), z.object({viewType: z.literal("customSelect"), select: z.string().nullable()}), z.object({viewType: z.literal("customMultiSelect"), multiSelect: z.array(z.string())}), z.object({viewType: z.literal("customCurrency"), monetaryAmount: monetaryAmountDto.nullable()}), z.object({viewType: z.literal("customDateRange"), dateRange: dateRangeDto.nullable()}), z.object({viewType: z.literal("customDuration"), duration: z.number().nullable()}), z.object({viewType: z.literal("customUrl"), url: z.string().nullable()}), z.object({viewType: z.literal("customEntity"), type: z.literal("user"), id: z.string().nullable(), entity: userBadgeDto.nullable().optional()}), z.object({viewType: z.literal("customMultiEntity"), type: z.literal("user"), ids: z.array(z.string()), entities: z.array(userBadgeDto).optional()})]); export const customAttributeItemDto = z.object({attributeDefinitionId: z.string().min(1), label: z.string(), aiFillOngoing: z.boolean(), aiOverridden: z.boolean(), isFilledByAI: z.boolean(), value: sharedCustomAttributeTypeDto}); export const skillBadgeDto = z.object({id: z.string(), name: z.string()}); export const candidateBaseDto = z.object({id: z.string().min(1), name: z.object({viewType: z.literal("candidateName"), avatarUrl: z.string().nullable(), firstName: z.string(), lastName: z.string()}), gender: z.object({viewType: z.literal("candidateGender"), gender: z.enum(["male", "female", "x", "null"]).nullable()}), nationality: z.object({viewType: z.literal("candidateNationality"), nationality: z.array(z.enum(["Afghan", "Albanian", "Algerian", "American", "Andorran", "Angolan", "Antiguan", "Argentinean", "Armenian", "Australian", "Austrian", "Azerbaijani", "Bahamian", "Bahraini", "Bangladeshi", "Barbadian", "Barbudan", "Batswana", "Belarusian", "Belgian", "Belizean", "Beninese", "Bhutanese", "Bolivian", "Bosnian", "Brazilian", "British", "Bruneian", "Bulgarian", "Burkinabe", "Burmese", "Burundian", "Cambodian", "Cameroonian", "Canadian", "Cape Verdean", "Central African", "Chadian", "Chilean", "Chinese", "Colombian", "Comoran", "Congolese", "Costa Rican", "Croatian", "Cuban", "Cypriot", "Czech", "Danish", "Djibouti", "Dominican", "Dutch", "East Timorese", "Ecuadorean", "Egyptian", "Emirian", "Equatorial Guinean", "Eritrean", "Estonian", "Ethiopian", "Fijian", "Filipino", "Finnish", "French", "Gabonese", "Gambian", "Georgian", "German", "Ghanaian", "Greek", "Grenadian", "Guatemalan", "Guinea-Bissauan", "Guinean", "Guyanese", "Haitian", "Herzegovinian", "Honduran", "Hungarian", "I-Kiribati", "Icelander", "Indian", "Indonesian", "Iranian", "Iraqi", "Irish", "Israeli", "Italian", "Ivorian", "Jamaican", "Japanese", "Jordanian", "Kazakhstani", "Kenyan", "Kittian and Nevisian", "Kuwaiti", "Kyrgyz", "Laotian", "Latvian", "Lebanese", "Liberian", "Libyan", "Liechtensteiner", "Lithuanian", "Luxembourger", "Macedonian", "Malagasy", "Malawian", "Malaysian", "Maldivian", "Malian", "Maltese", "Marshallese", "Mauritanian", "Mauritian", "Mexican", "Micronesian", "Moldovan", "Monacan", "Mongolian", "Moroccan", "Mosotho", "Motswana", "Mozambican", "Namibian", "Nauruan", "Nepalese", "New Zealander", "Ni-Vanuatu", "Nicaraguan", "Nigerian", "Nigerien", "North Korean", "Northern Irish", "Norwegian", "Omani", "Pakistani", "Palauan", "Panamanian", "Papua New Guinean", "Paraguayan", "Peruvian", "Polish", "Portuguese", "Qatari", "Romanian", "Russian", "Rwandan", "Saint Lucian", "Salvadoran", "Samoan", "San Marinese", "Sao Tomean", "Saudi", "Scottish", "Senegalese", "Serbian", "Seychellois", "Sierra Leonean", "Singaporean", "Slovakian", "Slovenian", "Solomon Islander", "Somali", "South African", "South Korean", "Spanish", "Sri Lankan", "Sudanese", "Surinamer", "Swazi", "Swedish", "Swiss", "Syrian", "Taiwanese", "Tajik", "Tanzanian", "Thai", "Togolese", "Tongan", "Trinidadian or Tobagonian", "Tunisian", "Turkish", "Tuvaluan", "Ugandan", "Ukrainian", "Uruguayan", "Uzbekistani", "Venezuelan", "Vietnamese", "Welsh", "Yemenite", "Zambian", "Zimbabwean"]))}), status: z.object({viewType: z.literal("candidateStatus"), status: z.array(z.enum(["actively_looking", "approachable_but_not_actively_looking", "do_not_contact", "do_not_poach", "not_actively_looking"]))}), emails: z.object({viewType: z.literal("candidateEmails"), emails: z.array(z.object({email: z.string(), purpose: z.enum(["personal", "work", "null"]).nullable(), isPrimary: z.boolean()}))}), phoneNumbers: z.object({viewType: z.literal("candidatePhoneNumbers"), phoneNumbers: z.array(z.object({phoneNumber: z.string(), purpose: z.enum(["personal", "work", "null"]).nullable(), isPrimary: z.boolean()}))}), locations: z.object({viewType: z.literal("candidateLocation"), locations: z.array(locationDto)}), dateOfBirth: z.object({viewType: z.literal("candidateDateOfBirth"), dateOfBirth: dateISO.nullable()}), socialMedia: z.object({viewType: z.literal("candidateSocialMedia"), socialMedia: z.array(z.object({url: z.string(), type: z.enum(["LINKEDIN", "TWITTER", "FACEBOOK", "INSTAGRAM"]), isPrimary: z.boolean()}))}), lastInteractionDate: z.object({viewType: z.literal("candidateLastInteractionDate"), lastInteractionDate: dateISO.nullable()}), lastInteractionWith: z.object({viewType: z.literal("candidateLastInteractionWith"), user: userDto.nullable()}), nextInteractionDate: z.object({viewType: z.literal("candidateNextInteractionDate"), nextInteractionDate: dateISO.nullable()}), nextInteractionWith: z.object({viewType: z.literal("candidateNextInteractionWith"), user: userDto.nullable()}), mainContact: z.object({viewType: z.literal("candidateMainContact"), user: userDto.nullable()}), languages: z.object({viewType: z.literal("candidateLanguages"), languages: z.array(languageDto)}), compensation: z.object({viewType: z.literal("candidateCompensation"), compensation: compensationDto.nullable()}), currentPlacements: z.object({viewType: z.literal("candidateCurrentPlacements"), hasCurrentPlacement: z.boolean(), placements: z.array(z.object({id: z.string(), company: z.object({id: z.string(), name: z.string(), logoUrl: z.string().nullable()}), startDate: dateISO.nullable(), endDate: dateISO.nullable()}))}), nextTaskDue: z.object({viewType: z.literal("candidateNextTaskDue"), nextTaskDue: nextTaskDueDto.nullable()}), createdAt: dateISO, modifiedAt: dateISO, lastActivityDate: dateISO, acquisitionSource: z.object({viewType: z.literal("candidateAcquisitionSource"), acquisitionSource: z.enum(["manual", "cv_parse", "inbound_vacancy_portal", "inbound_spontaneous_application", "chrome_extension_linkedin", "inbound_from_linkedin_jobboard_email", "inbound_from_external_jobboard", "juicebox", "pin", "maildrop", "csv_import", "vdab", "advertsdata", "null"]).nullable()}).nullable(), latestWorkExperience: workExperienceBadgeDto.nullable(), customAttributes: z.array(customAttributeItemDto), aiSummary: z.string().nullable(), isVisibleIfClientContact: z.boolean().nullable(), skills: z.array(skillBadgeDto).optional()}); export const searchCandidatesPaginationDto = z.object({hasNextPage: z.boolean()}); export const searchCandidatesResponseDto = z.object({items: z.array(candidateBaseDto), pagination: searchCandidatesPaginationDto}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Search candidates * * POST /candidates/_search */ export const createApiCandidatesSearch = async (args: CreateApiCandidatesSearchArgs, options: CreateApiCandidatesSearchOptions) => { const {baseUrl, fetch: fetchFn = fetch, xApiKey} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('content-type', "application/json") headers.set('accept', "application/json") if (xApiKey !== undefined) headers.set("x-api-key", xApiKey) const url = `${baseUrl.replace(/\/+$/, '')}/candidates/_search${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "POST", headers, body: JSON.stringify(args.body)}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(searchCandidatesResponseDto, await res.json()) };