d25d482dc2
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
914 lines
34 KiB
TypeScript
914 lines
34 KiB
TypeScript
import type {
|
|
AshbyApplication,
|
|
AshbyCandidate,
|
|
AshbyContactInfo,
|
|
AshbyCustomField,
|
|
AshbyFileHandle,
|
|
AshbyHiringTeamMember,
|
|
AshbyJob,
|
|
AshbyOffer,
|
|
AshbyOfferVersion,
|
|
AshbyOpening,
|
|
AshbyOpeningLatestVersion,
|
|
AshbySourceSummary,
|
|
AshbyUserSummary,
|
|
} from '@/tools/ashby/types'
|
|
import type { OutputProperty } from '@/tools/types'
|
|
|
|
type Unknown = Record<string, unknown>
|
|
|
|
/**
|
|
* Build the standard Ashby Authorization header. Ashby uses HTTP Basic auth
|
|
* with the API key as the username and an empty password.
|
|
*/
|
|
export function ashbyAuthHeaders(apiKey: string): Record<string, string> {
|
|
return {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json; version=1',
|
|
Authorization: `Basic ${btoa(`${apiKey}:`)}`,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extract a human-readable error message from an Ashby error response. Ashby
|
|
* returns errors as either `errorInfo.message` or an `errors` string array.
|
|
*/
|
|
export function ashbyErrorMessage(data: unknown, fallback: string): string {
|
|
if (!data || typeof data !== 'object') return fallback
|
|
const d = data as Unknown
|
|
const info = d.errorInfo as Unknown | undefined
|
|
if (info && typeof info.message === 'string' && info.message) return info.message
|
|
if (Array.isArray(d.errors) && d.errors.length > 0) {
|
|
return d.errors.map((e) => String(e)).join('; ')
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
function mapContact(raw: unknown): AshbyContactInfo | null {
|
|
if (!raw || typeof raw !== 'object') return null
|
|
const c = raw as Unknown
|
|
return {
|
|
value: (c.value as string) ?? '',
|
|
type: (c.type as string) ?? 'Other',
|
|
isPrimary: (c.isPrimary as boolean) ?? true,
|
|
}
|
|
}
|
|
|
|
function mapContactArray(raw: unknown): AshbyContactInfo[] {
|
|
if (!Array.isArray(raw)) return []
|
|
return raw.map((c) => mapContact(c)).filter((c): c is AshbyContactInfo => c !== null)
|
|
}
|
|
|
|
function mapCustomFields(raw: unknown): AshbyCustomField[] {
|
|
if (!Array.isArray(raw)) return []
|
|
return raw.map((f) => {
|
|
const cf = f as Unknown
|
|
return {
|
|
id: (cf.id as string) ?? null,
|
|
title: (cf.title as string) ?? '',
|
|
isPrivate: (cf.isPrivate as boolean) ?? false,
|
|
valueLabel: (cf.valueLabel as string) ?? null,
|
|
value: cf.value ?? null,
|
|
}
|
|
})
|
|
}
|
|
|
|
function mapFileHandle(raw: unknown): AshbyFileHandle | null {
|
|
if (!raw || typeof raw !== 'object') return null
|
|
const f = raw as Unknown
|
|
return {
|
|
id: (f.id as string) ?? '',
|
|
name: (f.name as string) ?? '',
|
|
handle: (f.handle as string) ?? '',
|
|
}
|
|
}
|
|
|
|
function mapFileHandles(raw: unknown): AshbyFileHandle[] {
|
|
if (!Array.isArray(raw)) return []
|
|
return raw.map((f) => mapFileHandle(f)).filter((f): f is AshbyFileHandle => f !== null)
|
|
}
|
|
|
|
export function mapUserSummary(raw: unknown): AshbyUserSummary | null {
|
|
if (!raw || typeof raw !== 'object') return null
|
|
const u = raw as Unknown
|
|
return {
|
|
id: (u.id as string) ?? '',
|
|
firstName: (u.firstName as string) ?? null,
|
|
lastName: (u.lastName as string) ?? null,
|
|
email: (u.email as string) ?? null,
|
|
globalRole: (u.globalRole as string) ?? null,
|
|
isEnabled: (u.isEnabled as boolean) ?? false,
|
|
updatedAt: (u.updatedAt as string) ?? null,
|
|
managerId: (u.managerId as string) ?? null,
|
|
}
|
|
}
|
|
|
|
function mapSource(raw: unknown): AshbySourceSummary | null {
|
|
if (!raw || typeof raw !== 'object') return null
|
|
const s = raw as Unknown
|
|
const sourceType = s.sourceType as Unknown | undefined
|
|
return {
|
|
id: (s.id as string) ?? '',
|
|
title: (s.title as string) ?? '',
|
|
isArchived: (s.isArchived as boolean) ?? false,
|
|
sourceType: sourceType
|
|
? {
|
|
id: (sourceType.id as string) ?? '',
|
|
title: (sourceType.title as string) ?? '',
|
|
isArchived: (sourceType.isArchived as boolean) ?? false,
|
|
}
|
|
: null,
|
|
}
|
|
}
|
|
|
|
export function mapCandidate(raw: unknown): AshbyCandidate {
|
|
const c = (raw ?? {}) as Unknown
|
|
const socialLinks = Array.isArray(c.socialLinks)
|
|
? (c.socialLinks as Array<{ type?: string; url?: string }>)
|
|
: []
|
|
const location = c.location as Unknown | undefined
|
|
const locationComponents = Array.isArray(location?.locationComponents)
|
|
? (location?.locationComponents as Array<{ type?: string; name?: string }>)
|
|
: []
|
|
return {
|
|
id: (c.id as string) ?? '',
|
|
name: (c.name as string) ?? '',
|
|
primaryEmailAddress: mapContact(c.primaryEmailAddress),
|
|
primaryPhoneNumber: mapContact(c.primaryPhoneNumber),
|
|
emailAddresses: mapContactArray(c.emailAddresses),
|
|
phoneNumbers: mapContactArray(c.phoneNumbers),
|
|
socialLinks: socialLinks.map((l) => ({
|
|
type: l.type ?? '',
|
|
url: l.url ?? '',
|
|
})),
|
|
linkedInUrl: socialLinks.find((l) => l.type === 'LinkedIn')?.url ?? null,
|
|
githubUrl: socialLinks.find((l) => l.type === 'GitHub')?.url ?? null,
|
|
profileUrl: (c.profileUrl as string) ?? null,
|
|
position: (c.position as string) ?? null,
|
|
company: (c.company as string) ?? null,
|
|
school: (c.school as string) ?? null,
|
|
timezone: (c.timezone as string) ?? null,
|
|
location: location
|
|
? {
|
|
id: (location.id as string) ?? null,
|
|
locationSummary: (location.locationSummary as string) ?? null,
|
|
locationComponents: locationComponents.map((lc) => ({
|
|
type: lc.type ?? '',
|
|
name: lc.name ?? '',
|
|
})),
|
|
}
|
|
: null,
|
|
tags: Array.isArray(c.tags)
|
|
? (c.tags as Array<{ id?: string; title?: string; isArchived?: boolean }>).map((t) => ({
|
|
id: t.id ?? '',
|
|
title: t.title ?? '',
|
|
isArchived: t.isArchived ?? false,
|
|
}))
|
|
: [],
|
|
applicationIds: Array.isArray(c.applicationIds) ? (c.applicationIds as string[]) : [],
|
|
customFields: mapCustomFields(c.customFields),
|
|
resumeFileHandle: mapFileHandle(c.resumeFileHandle),
|
|
fileHandles: mapFileHandles(c.fileHandles),
|
|
source: mapSource(c.source),
|
|
creditedToUser: mapUserSummary(c.creditedToUser),
|
|
fraudStatus: (c.fraudStatus as string) ?? null,
|
|
createdAt: (c.createdAt as string) ?? null,
|
|
updatedAt: (c.updatedAt as string) ?? null,
|
|
}
|
|
}
|
|
|
|
function mapHiringTeam(raw: unknown): AshbyHiringTeamMember[] {
|
|
if (!Array.isArray(raw)) return []
|
|
return raw.map((m) => {
|
|
const mem = m as Unknown
|
|
return {
|
|
email: (mem.email as string) ?? null,
|
|
firstName: (mem.firstName as string) ?? null,
|
|
lastName: (mem.lastName as string) ?? null,
|
|
role: (mem.role as string) ?? null,
|
|
userId: (mem.userId as string) ?? null,
|
|
}
|
|
})
|
|
}
|
|
|
|
function mapOpeningLatestVersion(raw: unknown): AshbyOpeningLatestVersion | null {
|
|
if (!raw || typeof raw !== 'object') return null
|
|
const v = raw as Unknown
|
|
return {
|
|
id: (v.id as string) ?? null,
|
|
identifier: (v.identifier as string) ?? null,
|
|
description: (v.description as string) ?? null,
|
|
authorId: (v.authorId as string) ?? null,
|
|
createdAt: (v.createdAt as string) ?? null,
|
|
teamId: (v.teamId as string) ?? null,
|
|
jobIds: Array.isArray(v.jobIds) ? (v.jobIds as string[]) : [],
|
|
targetHireDate: (v.targetHireDate as string) ?? null,
|
|
targetStartDate: (v.targetStartDate as string) ?? null,
|
|
isBackfill: (v.isBackfill as boolean) ?? false,
|
|
employmentType: (v.employmentType as string) ?? null,
|
|
locationIds: Array.isArray(v.locationIds) ? (v.locationIds as string[]) : [],
|
|
hiringTeam: mapHiringTeam(v.hiringTeam),
|
|
customFields: mapCustomFields(v.customFields),
|
|
}
|
|
}
|
|
|
|
export function mapOpenings(raw: unknown): AshbyOpening[] {
|
|
if (!Array.isArray(raw)) return []
|
|
return raw.map((o) => {
|
|
const op = o as Unknown
|
|
return {
|
|
id: (op.id as string) ?? '',
|
|
openedAt: (op.openedAt as string) ?? null,
|
|
closedAt: (op.closedAt as string) ?? null,
|
|
isArchived: (op.isArchived as boolean) ?? false,
|
|
archivedAt: (op.archivedAt as string) ?? null,
|
|
closeReasonId: (op.closeReasonId as string) ?? null,
|
|
openingState: (op.openingState as string) ?? null,
|
|
latestVersion: mapOpeningLatestVersion(op.latestVersion),
|
|
}
|
|
})
|
|
}
|
|
|
|
export function mapJob(raw: unknown): AshbyJob {
|
|
const j = (raw ?? {}) as Unknown
|
|
const location = j.location as Unknown | undefined
|
|
const address = location?.address as Unknown | undefined
|
|
const postalAddress = address?.postalAddress as Unknown | undefined
|
|
const compensation = j.compensation as Unknown | undefined
|
|
return {
|
|
id: (j.id as string) ?? '',
|
|
title: (j.title as string) ?? '',
|
|
confidential: (j.confidential as boolean) ?? false,
|
|
status: (j.status as string) ?? null,
|
|
employmentType: (j.employmentType as string) ?? null,
|
|
locationId: (j.locationId as string) ?? null,
|
|
departmentId: (j.departmentId as string) ?? null,
|
|
defaultInterviewPlanId: (j.defaultInterviewPlanId as string) ?? null,
|
|
interviewPlanIds: Array.isArray(j.interviewPlanIds) ? (j.interviewPlanIds as string[]) : [],
|
|
customFields: mapCustomFields(j.customFields),
|
|
jobPostingIds: Array.isArray(j.jobPostingIds) ? (j.jobPostingIds as string[]) : [],
|
|
customRequisitionId: (j.customRequisitionId as string) ?? null,
|
|
brandId: (j.brandId as string) ?? null,
|
|
hiringTeam: mapHiringTeam(j.hiringTeam),
|
|
author: mapUserSummary(j.author),
|
|
createdAt: (j.createdAt as string) ?? null,
|
|
updatedAt: (j.updatedAt as string) ?? null,
|
|
openedAt: (j.openedAt as string) ?? null,
|
|
closedAt: (j.closedAt as string) ?? null,
|
|
location: location
|
|
? {
|
|
id: (location.id as string) ?? null,
|
|
name: (location.name as string) ?? null,
|
|
externalName: (location.externalName as string) ?? null,
|
|
isArchived: (location.isArchived as boolean) ?? false,
|
|
isRemote: (location.isRemote as boolean) ?? false,
|
|
workplaceType: (location.workplaceType as string) ?? null,
|
|
parentLocationId: (location.parentLocationId as string) ?? null,
|
|
type: (location.type as string) ?? null,
|
|
address: postalAddress
|
|
? {
|
|
addressCountry: (postalAddress.addressCountry as string) ?? null,
|
|
addressRegion: (postalAddress.addressRegion as string) ?? null,
|
|
addressLocality: (postalAddress.addressLocality as string) ?? null,
|
|
postalCode: (postalAddress.postalCode as string) ?? null,
|
|
streetAddress: (postalAddress.streetAddress as string) ?? null,
|
|
}
|
|
: null,
|
|
}
|
|
: null,
|
|
openings: mapOpenings(j.openings),
|
|
compensation: compensation
|
|
? {
|
|
compensationTiers: Array.isArray(compensation.compensationTiers)
|
|
? (
|
|
compensation.compensationTiers as Array<{
|
|
id?: string
|
|
title?: string
|
|
additionalInformation?: string
|
|
tierSummary?: string
|
|
}>
|
|
).map((t) => ({
|
|
id: t.id ?? null,
|
|
title: t.title ?? null,
|
|
additionalInformation: t.additionalInformation ?? null,
|
|
tierSummary: t.tierSummary ?? null,
|
|
}))
|
|
: [],
|
|
}
|
|
: null,
|
|
}
|
|
}
|
|
|
|
export function mapApplication(raw: unknown): AshbyApplication {
|
|
const a = (raw ?? {}) as Unknown
|
|
const candidate = a.candidate as Unknown | undefined
|
|
const job = a.job as Unknown | undefined
|
|
const stage = a.currentInterviewStage as Unknown | undefined
|
|
const archiveReason = a.archiveReason as Unknown | undefined
|
|
return {
|
|
id: (a.id as string) ?? '',
|
|
createdAt: (a.createdAt as string) ?? null,
|
|
updatedAt: (a.updatedAt as string) ?? null,
|
|
status: (a.status as string) ?? '',
|
|
customFields: mapCustomFields(a.customFields),
|
|
candidate: {
|
|
id: (candidate?.id as string) ?? '',
|
|
name: (candidate?.name as string) ?? null,
|
|
primaryEmailAddress: mapContact(candidate?.primaryEmailAddress),
|
|
primaryPhoneNumber: mapContact(candidate?.primaryPhoneNumber),
|
|
},
|
|
currentInterviewStage: stage
|
|
? {
|
|
id: (stage.id as string) ?? '',
|
|
title: (stage.title as string) ?? null,
|
|
type: (stage.type as string) ?? null,
|
|
orderInInterviewPlan: (stage.orderInInterviewPlan as number) ?? null,
|
|
interviewStageGroupId: (stage.interviewStageGroupId as string) ?? null,
|
|
interviewPlanId: (stage.interviewPlanId as string) ?? null,
|
|
}
|
|
: null,
|
|
source: mapSource(a.source),
|
|
archiveReason: archiveReason
|
|
? {
|
|
id: (archiveReason.id as string) ?? '',
|
|
text: (archiveReason.text as string) ?? null,
|
|
reasonType: (archiveReason.reasonType as string) ?? null,
|
|
isArchived: (archiveReason.isArchived as boolean) ?? false,
|
|
customFields: mapCustomFields(archiveReason.customFields),
|
|
}
|
|
: null,
|
|
archivedAt: (a.archivedAt as string) ?? null,
|
|
job: {
|
|
id: (job?.id as string) ?? '',
|
|
title: (job?.title as string) ?? null,
|
|
locationId: (job?.locationId as string) ?? null,
|
|
departmentId: (job?.departmentId as string) ?? null,
|
|
},
|
|
creditedToUser: mapUserSummary(a.creditedToUser),
|
|
hiringTeam: mapHiringTeam(a.hiringTeam),
|
|
appliedViaJobPostingId: (a.appliedViaJobPostingId as string) ?? null,
|
|
submitterClientIp: (a.submitterClientIp as string) ?? null,
|
|
submitterUserAgent: (a.submitterUserAgent as string) ?? null,
|
|
applicationHistory: Array.isArray(a.applicationHistory)
|
|
? (a.applicationHistory as Unknown[]).map((h) => ({
|
|
id: (h.id as string) ?? '',
|
|
stageId: (h.stageId as string) ?? null,
|
|
stageNumber: (h.stageNumber as number) ?? null,
|
|
title: (h.title as string) ?? null,
|
|
enteredStageAt: (h.enteredStageAt as string) ?? null,
|
|
actorId: (h.actorId as string) ?? null,
|
|
}))
|
|
: [],
|
|
}
|
|
}
|
|
|
|
export const CONTACT_INFO_OUTPUT = {
|
|
type: 'object',
|
|
description: 'Contact info',
|
|
optional: true,
|
|
properties: {
|
|
value: { type: 'string', description: 'Value (email or phone number)' },
|
|
type: { type: 'string', description: 'Contact type (Personal, Work, Other)' },
|
|
isPrimary: { type: 'boolean', description: 'Whether this is the primary contact' },
|
|
},
|
|
} as const satisfies OutputProperty
|
|
|
|
export const CUSTOM_FIELDS_OUTPUT = {
|
|
type: 'array',
|
|
description: 'Custom field values',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', description: 'Custom field UUID' },
|
|
title: { type: 'string', description: 'Field title' },
|
|
isPrivate: { type: 'boolean', description: 'Whether the field is private' },
|
|
valueLabel: { type: 'string', description: 'Human-readable value label', optional: true },
|
|
value: { type: 'string', description: 'Raw field value (type depends on fieldType)' },
|
|
},
|
|
},
|
|
} as const satisfies OutputProperty
|
|
|
|
export const FILE_HANDLE_OUTPUT = {
|
|
type: 'object',
|
|
description: 'File reference',
|
|
optional: true,
|
|
properties: {
|
|
id: { type: 'string', description: 'File UUID' },
|
|
name: { type: 'string', description: 'File name' },
|
|
handle: { type: 'string', description: 'File handle used with file.info' },
|
|
},
|
|
} as const satisfies OutputProperty
|
|
|
|
export const FILE_HANDLES_OUTPUT = {
|
|
type: 'array',
|
|
description: 'File references',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', description: 'File UUID' },
|
|
name: { type: 'string', description: 'File name' },
|
|
handle: { type: 'string', description: 'File handle used with file.info' },
|
|
},
|
|
},
|
|
} as const satisfies OutputProperty
|
|
|
|
export const USER_SUMMARY_OUTPUT = {
|
|
type: 'object',
|
|
description: 'User summary',
|
|
optional: true,
|
|
properties: {
|
|
id: { type: 'string', description: 'User UUID' },
|
|
firstName: { type: 'string', description: 'First name', optional: true },
|
|
lastName: { type: 'string', description: 'Last name', optional: true },
|
|
email: { type: 'string', description: 'Email', optional: true },
|
|
globalRole: { type: 'string', description: 'Role', optional: true },
|
|
isEnabled: { type: 'boolean', description: 'Whether enabled' },
|
|
updatedAt: { type: 'string', description: 'Last update timestamp', optional: true },
|
|
managerId: { type: 'string', description: "User ID of the user's manager", optional: true },
|
|
},
|
|
} as const satisfies OutputProperty
|
|
|
|
export const SOURCE_SUMMARY_OUTPUT = {
|
|
type: 'object',
|
|
description: 'Attribution source',
|
|
optional: true,
|
|
properties: {
|
|
id: { type: 'string', description: 'Source UUID' },
|
|
title: { type: 'string', description: 'Source title' },
|
|
isArchived: { type: 'boolean', description: 'Whether archived' },
|
|
sourceType: {
|
|
type: 'object',
|
|
description: 'Source type grouping',
|
|
optional: true,
|
|
properties: {
|
|
id: { type: 'string', description: 'Source type UUID' },
|
|
title: { type: 'string', description: 'Source type title' },
|
|
isArchived: { type: 'boolean', description: 'Whether archived' },
|
|
},
|
|
},
|
|
},
|
|
} as const satisfies OutputProperty
|
|
|
|
export const HIRING_TEAM_OUTPUT = {
|
|
type: 'array',
|
|
description: 'Hiring team members',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
userId: { type: 'string', description: 'User UUID' },
|
|
firstName: { type: 'string', description: 'First name' },
|
|
lastName: { type: 'string', description: 'Last name' },
|
|
email: { type: 'string', description: 'Email' },
|
|
role: { type: 'string', description: 'Hiring team role' },
|
|
},
|
|
},
|
|
} as const satisfies OutputProperty
|
|
|
|
export const CANDIDATE_OUTPUTS = {
|
|
id: { type: 'string', description: 'Candidate UUID' },
|
|
name: { type: 'string', description: 'Full name' },
|
|
primaryEmailAddress: { ...CONTACT_INFO_OUTPUT, description: 'Primary email contact info' },
|
|
primaryPhoneNumber: { ...CONTACT_INFO_OUTPUT, description: 'Primary phone contact info' },
|
|
emailAddresses: {
|
|
type: 'array',
|
|
description: 'All email addresses',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
value: { type: 'string', description: 'Email address' },
|
|
type: { type: 'string', description: 'Contact type' },
|
|
isPrimary: { type: 'boolean', description: 'Whether primary' },
|
|
},
|
|
},
|
|
},
|
|
phoneNumbers: {
|
|
type: 'array',
|
|
description: 'All phone numbers',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
value: { type: 'string', description: 'Phone number' },
|
|
type: { type: 'string', description: 'Contact type' },
|
|
isPrimary: { type: 'boolean', description: 'Whether primary' },
|
|
},
|
|
},
|
|
},
|
|
socialLinks: {
|
|
type: 'array',
|
|
description: 'Social network links',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
type: { type: 'string', description: 'Link type (LinkedIn, GitHub, Twitter, etc.)' },
|
|
url: { type: 'string', description: 'Profile URL' },
|
|
},
|
|
},
|
|
},
|
|
linkedInUrl: { type: 'string', description: 'LinkedIn profile URL', optional: true },
|
|
githubUrl: { type: 'string', description: 'GitHub profile URL', optional: true },
|
|
profileUrl: { type: 'string', description: 'URL to the candidate Ashby profile', optional: true },
|
|
position: { type: 'string', description: 'Current position or title', optional: true },
|
|
company: { type: 'string', description: 'Current company', optional: true },
|
|
school: { type: 'string', description: 'Most recent school', optional: true },
|
|
timezone: { type: 'string', description: 'Candidate timezone', optional: true },
|
|
location: {
|
|
type: 'object',
|
|
description: 'Candidate location',
|
|
optional: true,
|
|
properties: {
|
|
id: { type: 'string', description: 'Location UUID', optional: true },
|
|
locationSummary: { type: 'string', description: 'Human-readable location summary' },
|
|
locationComponents: {
|
|
type: 'array',
|
|
description: 'Structured location parts (city, region, country, etc.)',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
type: { type: 'string', description: 'Component type' },
|
|
name: { type: 'string', description: 'Component value' },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
tags: {
|
|
type: 'array',
|
|
description: 'Tags applied to the candidate',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', description: 'Tag UUID' },
|
|
title: { type: 'string', description: 'Tag title' },
|
|
isArchived: { type: 'boolean', description: 'Whether archived' },
|
|
},
|
|
},
|
|
},
|
|
applicationIds: {
|
|
type: 'array',
|
|
description: 'IDs of associated applications',
|
|
items: { type: 'string', description: 'Application UUID' },
|
|
},
|
|
customFields: CUSTOM_FIELDS_OUTPUT,
|
|
resumeFileHandle: { ...FILE_HANDLE_OUTPUT, description: 'Resume file reference' },
|
|
fileHandles: { ...FILE_HANDLES_OUTPUT, description: 'All uploaded file references' },
|
|
source: SOURCE_SUMMARY_OUTPUT,
|
|
creditedToUser: { ...USER_SUMMARY_OUTPUT, description: 'User credited with sourcing' },
|
|
fraudStatus: { type: 'string', description: 'Fraud detection status', optional: true },
|
|
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
|
|
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
|
|
} as const satisfies Record<string, OutputProperty>
|
|
|
|
export const APPLICATION_OUTPUTS = {
|
|
id: { type: 'string', description: 'Application UUID' },
|
|
status: { type: 'string', description: 'Status (Active, Hired, Archived, Lead)' },
|
|
customFields: CUSTOM_FIELDS_OUTPUT,
|
|
candidate: {
|
|
type: 'object',
|
|
description: 'Associated candidate summary',
|
|
properties: {
|
|
id: { type: 'string', description: 'Candidate UUID' },
|
|
name: { type: 'string', description: 'Candidate name' },
|
|
primaryEmailAddress: { ...CONTACT_INFO_OUTPUT, description: 'Primary email' },
|
|
primaryPhoneNumber: { ...CONTACT_INFO_OUTPUT, description: 'Primary phone' },
|
|
},
|
|
},
|
|
currentInterviewStage: {
|
|
type: 'object',
|
|
description: 'Current interview stage',
|
|
optional: true,
|
|
properties: {
|
|
id: { type: 'string', description: 'Stage UUID' },
|
|
title: { type: 'string', description: 'Stage title' },
|
|
type: { type: 'string', description: 'Stage type' },
|
|
orderInInterviewPlan: {
|
|
type: 'number',
|
|
description: 'Position in plan',
|
|
optional: true,
|
|
},
|
|
interviewStageGroupId: { type: 'string', description: 'Stage group UUID', optional: true },
|
|
interviewPlanId: { type: 'string', description: 'Interview plan UUID', optional: true },
|
|
},
|
|
},
|
|
source: SOURCE_SUMMARY_OUTPUT,
|
|
archiveReason: {
|
|
type: 'object',
|
|
description: 'Reason for archival (when archived)',
|
|
optional: true,
|
|
properties: {
|
|
id: { type: 'string', description: 'Reason UUID' },
|
|
text: { type: 'string', description: 'Reason text' },
|
|
reasonType: { type: 'string', description: 'Reason category' },
|
|
isArchived: { type: 'boolean', description: 'Whether the reason is archived' },
|
|
customFields: CUSTOM_FIELDS_OUTPUT,
|
|
},
|
|
},
|
|
archivedAt: { type: 'string', description: 'ISO 8601 archive timestamp', optional: true },
|
|
job: {
|
|
type: 'object',
|
|
description: 'Associated job summary',
|
|
properties: {
|
|
id: { type: 'string', description: 'Job UUID' },
|
|
title: { type: 'string', description: 'Job title' },
|
|
locationId: { type: 'string', description: 'Location UUID', optional: true },
|
|
departmentId: { type: 'string', description: 'Department UUID', optional: true },
|
|
},
|
|
},
|
|
creditedToUser: { ...USER_SUMMARY_OUTPUT, description: 'User credited with the application' },
|
|
hiringTeam: HIRING_TEAM_OUTPUT,
|
|
appliedViaJobPostingId: {
|
|
type: 'string',
|
|
description: 'Job posting UUID the candidate applied through',
|
|
optional: true,
|
|
},
|
|
submitterClientIp: { type: 'string', description: 'Submitter IP address', optional: true },
|
|
submitterUserAgent: {
|
|
type: 'string',
|
|
description: 'Submitter browser user agent',
|
|
optional: true,
|
|
},
|
|
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp' },
|
|
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp' },
|
|
applicationHistory: {
|
|
type: 'array',
|
|
description: 'Stage history (only populated by application.info, empty for list endpoints)',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', description: 'History entry UUID' },
|
|
stageId: { type: 'string', description: 'Interview stage UUID', optional: true },
|
|
stageNumber: { type: 'number', description: 'Stage order number', optional: true },
|
|
title: { type: 'string', description: 'Stage title at the time', optional: true },
|
|
enteredStageAt: {
|
|
type: 'string',
|
|
description: 'ISO 8601 timestamp the stage was entered',
|
|
optional: true,
|
|
},
|
|
actorId: {
|
|
type: 'string',
|
|
description: 'User UUID who triggered the stage change',
|
|
optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as const satisfies Record<string, OutputProperty>
|
|
|
|
export const OPENINGS_OUTPUT = {
|
|
type: 'array',
|
|
description: 'Headcount openings associated with the job',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', description: 'Opening UUID' },
|
|
openedAt: { type: 'string', description: 'Opening open timestamp', optional: true },
|
|
closedAt: { type: 'string', description: 'Opening close timestamp', optional: true },
|
|
isArchived: { type: 'boolean', description: 'Whether archived' },
|
|
archivedAt: { type: 'string', description: 'Archive timestamp', optional: true },
|
|
closeReasonId: { type: 'string', description: 'Close reason UUID', optional: true },
|
|
openingState: {
|
|
type: 'string',
|
|
description: 'Opening state (Approved, Open, Filled, Closed, Draft)',
|
|
optional: true,
|
|
},
|
|
latestVersion: {
|
|
type: 'object',
|
|
description: 'Latest opening version',
|
|
optional: true,
|
|
properties: {
|
|
id: { type: 'string', description: 'Version UUID', optional: true },
|
|
identifier: { type: 'string', description: 'Human-readable identifier' },
|
|
description: { type: 'string', description: 'Opening description' },
|
|
authorId: { type: 'string', description: 'Author user UUID', optional: true },
|
|
createdAt: { type: 'string', description: 'Version creation timestamp', optional: true },
|
|
teamId: { type: 'string', description: 'Team UUID', optional: true },
|
|
jobIds: {
|
|
type: 'array',
|
|
description: 'Associated job UUIDs',
|
|
items: { type: 'string', description: 'Job UUID' },
|
|
},
|
|
targetHireDate: { type: 'string', description: 'Target hire date', optional: true },
|
|
targetStartDate: { type: 'string', description: 'Target start date', optional: true },
|
|
isBackfill: { type: 'boolean', description: 'Whether this is a backfill opening' },
|
|
employmentType: { type: 'string', description: 'Employment type', optional: true },
|
|
locationIds: {
|
|
type: 'array',
|
|
description: 'Location UUIDs',
|
|
items: { type: 'string', description: 'Location UUID' },
|
|
},
|
|
hiringTeam: HIRING_TEAM_OUTPUT,
|
|
customFields: CUSTOM_FIELDS_OUTPUT,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as const satisfies OutputProperty
|
|
|
|
function mapOfferVersion(raw: unknown): AshbyOfferVersion | null {
|
|
if (!raw || typeof raw !== 'object') return null
|
|
const v = raw as Unknown
|
|
const salary = v.salary as Unknown | undefined
|
|
return {
|
|
id: (v.id as string) ?? null,
|
|
startDate: (v.startDate as string) ?? null,
|
|
salary: salary
|
|
? {
|
|
currencyCode: (salary.currencyCode as string) ?? null,
|
|
value: (salary.value as number) ?? null,
|
|
}
|
|
: null,
|
|
createdAt: (v.createdAt as string) ?? null,
|
|
openingId: (v.openingId as string) ?? null,
|
|
customFields: mapCustomFields(v.customFields),
|
|
fileHandles: mapFileHandles(v.fileHandles),
|
|
author: mapUserSummary(v.author),
|
|
approvalStatus: (v.approvalStatus as string) ?? null,
|
|
}
|
|
}
|
|
|
|
export function mapOffer(raw: unknown): AshbyOffer {
|
|
const r = (raw ?? {}) as Unknown
|
|
return {
|
|
id: (r.id as string) ?? '',
|
|
decidedAt: (r.decidedAt as string) ?? null,
|
|
applicationId: (r.applicationId as string) ?? null,
|
|
acceptanceStatus: (r.acceptanceStatus as string) ?? null,
|
|
offerStatus: (r.offerStatus as string) ?? null,
|
|
latestVersion: mapOfferVersion(r.latestVersion),
|
|
}
|
|
}
|
|
|
|
export const OFFER_OUTPUTS = {
|
|
id: { type: 'string', description: 'Offer UUID' },
|
|
decidedAt: {
|
|
type: 'string',
|
|
description: 'Timestamp the offer was decided',
|
|
optional: true,
|
|
},
|
|
applicationId: {
|
|
type: 'string',
|
|
description: 'Associated application UUID',
|
|
optional: true,
|
|
},
|
|
acceptanceStatus: {
|
|
type: 'string',
|
|
description: 'Acceptance status (Accepted, Declined, Pending, etc.)',
|
|
optional: true,
|
|
},
|
|
offerStatus: {
|
|
type: 'string',
|
|
description: 'Offer status (e.g. WaitingOnCandidateResponse, CandidateAccepted)',
|
|
optional: true,
|
|
},
|
|
latestVersion: {
|
|
type: 'object',
|
|
description: 'Most recent version of the offer with pricing and metadata',
|
|
optional: true,
|
|
properties: {
|
|
id: { type: 'string', description: 'Version UUID', optional: true },
|
|
startDate: { type: 'string', description: 'Offer start date', optional: true },
|
|
salary: {
|
|
type: 'object',
|
|
description: 'Salary details',
|
|
optional: true,
|
|
properties: {
|
|
currencyCode: {
|
|
type: 'string',
|
|
description: 'ISO 4217 currency code',
|
|
optional: true,
|
|
},
|
|
value: { type: 'number', description: 'Salary amount', optional: true },
|
|
},
|
|
},
|
|
createdAt: {
|
|
type: 'string',
|
|
description: 'Version creation timestamp',
|
|
optional: true,
|
|
},
|
|
openingId: {
|
|
type: 'string',
|
|
description: 'Associated opening UUID',
|
|
optional: true,
|
|
},
|
|
customFields: CUSTOM_FIELDS_OUTPUT,
|
|
fileHandles: {
|
|
...FILE_HANDLES_OUTPUT,
|
|
description:
|
|
'Offer letter file handles (unsigned .pdf, .docx, and signed .pdf when generated)',
|
|
},
|
|
author: { ...USER_SUMMARY_OUTPUT, description: 'User who authored the version' },
|
|
approvalStatus: {
|
|
type: 'string',
|
|
description: 'Approval workflow status',
|
|
optional: true,
|
|
},
|
|
},
|
|
},
|
|
} as const satisfies Record<string, OutputProperty>
|
|
|
|
export const JOB_OUTPUTS = {
|
|
id: { type: 'string', description: 'Job UUID' },
|
|
title: { type: 'string', description: 'Job title' },
|
|
confidential: { type: 'boolean', description: 'Whether the job is confidential' },
|
|
status: { type: 'string', description: 'Status (Open, Closed, Draft, Archived)', optional: true },
|
|
employmentType: {
|
|
type: 'string',
|
|
description: 'Employment type (FullTime, PartTime, Intern, Contract, Temporary)',
|
|
optional: true,
|
|
},
|
|
locationId: { type: 'string', description: 'Primary location UUID', optional: true },
|
|
departmentId: { type: 'string', description: 'Department UUID', optional: true },
|
|
defaultInterviewPlanId: {
|
|
type: 'string',
|
|
description: 'Default interview plan UUID',
|
|
optional: true,
|
|
},
|
|
interviewPlanIds: {
|
|
type: 'array',
|
|
description: 'All interview plan UUIDs',
|
|
items: { type: 'string', description: 'Interview plan UUID' },
|
|
},
|
|
customFields: CUSTOM_FIELDS_OUTPUT,
|
|
jobPostingIds: {
|
|
type: 'array',
|
|
description: 'Associated job posting UUIDs',
|
|
items: { type: 'string', description: 'Job posting UUID' },
|
|
},
|
|
customRequisitionId: {
|
|
type: 'string',
|
|
description: 'Custom requisition identifier',
|
|
optional: true,
|
|
},
|
|
brandId: { type: 'string', description: 'Brand UUID', optional: true },
|
|
hiringTeam: HIRING_TEAM_OUTPUT,
|
|
author: { ...USER_SUMMARY_OUTPUT, description: 'Job author (creator)' },
|
|
createdAt: { type: 'string', description: 'ISO 8601 creation timestamp', optional: true },
|
|
updatedAt: { type: 'string', description: 'ISO 8601 last update timestamp', optional: true },
|
|
openedAt: { type: 'string', description: 'ISO 8601 opened timestamp', optional: true },
|
|
closedAt: { type: 'string', description: 'ISO 8601 closed timestamp', optional: true },
|
|
location: {
|
|
type: 'object',
|
|
description: 'Primary location details',
|
|
optional: true,
|
|
properties: {
|
|
id: { type: 'string', description: 'Location UUID', optional: true },
|
|
name: { type: 'string', description: 'Location name', optional: true },
|
|
externalName: { type: 'string', description: 'External display name', optional: true },
|
|
isArchived: { type: 'boolean', description: 'Whether archived' },
|
|
isRemote: { type: 'boolean', description: 'Whether remote' },
|
|
workplaceType: {
|
|
type: 'string',
|
|
description: 'Workplace type (OnSite, Remote, Hybrid)',
|
|
optional: true,
|
|
},
|
|
parentLocationId: {
|
|
type: 'string',
|
|
description: 'Parent location UUID',
|
|
optional: true,
|
|
},
|
|
type: { type: 'string', description: 'Location type', optional: true },
|
|
address: {
|
|
type: 'object',
|
|
description: 'Postal address',
|
|
optional: true,
|
|
properties: {
|
|
addressCountry: { type: 'string', description: 'Country', optional: true },
|
|
addressRegion: { type: 'string', description: 'State or region', optional: true },
|
|
addressLocality: { type: 'string', description: 'City or locality', optional: true },
|
|
postalCode: { type: 'string', description: 'Postal code', optional: true },
|
|
streetAddress: { type: 'string', description: 'Street address', optional: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
openings: OPENINGS_OUTPUT,
|
|
compensation: {
|
|
type: 'object',
|
|
description:
|
|
'Compensation tiers for the job. Only present when the request includes the `compensation` expand parameter.',
|
|
optional: true,
|
|
properties: {
|
|
compensationTiers: {
|
|
type: 'array',
|
|
description: 'List of compensation tiers',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'string', description: 'Tier ID', optional: true },
|
|
title: { type: 'string', description: 'Tier title', optional: true },
|
|
additionalInformation: {
|
|
type: 'string',
|
|
description: 'Additional information about the tier',
|
|
optional: true,
|
|
},
|
|
tierSummary: {
|
|
type: 'string',
|
|
description: 'Human-readable summary of the tier',
|
|
optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
} as const satisfies Record<string, OutputProperty>
|