import {z} from 'zod' export type DateISO = string; export type GetApiCandidatesArgs = {limit?: number | undefined, cursor?: string | undefined, modifiedSince?: DateISO | undefined, modifiedUntil?: DateISO | undefined, listIds?: Array | undefined, include: Array}; export type GetApiCandidatesOptions = {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 candidateListItemDto = 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(), hasClientContacts: z.boolean()}); export const cursorPageInfoDto = z.object({nextCursor: z.string().nullable(), hasNextPage: z.boolean()}); export const getCandidatesResponseDto = z.object({items: z.array(candidateListItemDto), pageInfo: cursorPageInfoDto}); 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 }; /** * List candidates * * GET /candidates */ export const getApiCandidates = async (args: GetApiCandidatesArgs, options: GetApiCandidatesOptions) => { const {baseUrl, fetch: fetchFn = fetch, xApiKey} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.limit != null) query.append("limit", String(args.limit)) if (args.cursor != null) query.append("cursor", String(args.cursor)) if (args.modifiedSince != null) query.append("modifiedSince", String(args.modifiedSince)) if (args.modifiedUntil != null) query.append("modifiedUntil", String(args.modifiedUntil)) for (const item of args.listIds ?? []) query.append("listIds", String(item)) for (const item of args.include ?? []) query.append("include", String(item)) if (xApiKey !== undefined) headers.set("x-api-key", xApiKey) const url = `${baseUrl.replace(/\/+$/, '')}/candidates${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(getCandidatesResponseDto, await res.json()) };