import {z} from 'zod' export type GetApiJobsArgs = {job_types?: (Array<'income' | 'identity' | 'bill_cancellation' | 'bill_switch' | 'direct_deposit_switch' | 'tax_forms' | 'paystubs' | 'direct_deposit_payment' | 'direct_deposit_allocations' | 'shifts' | 'employment'>) | undefined, link_token_id?: string | undefined, account_id?: string | undefined, outcome?: ('pending' | 'error' | 'success') | undefined, partner_customer_id?: string | undefined, limit?: number | undefined, cursor?: string | undefined, from_timestamp?: string | undefined, to_timestamp?: string | undefined, 'Pinwheel-Version': '2025-07-08' | '2023-11-22' | '2023-07-18' | '2023-04-18' | '2022-09-09' | '2022-06-22' | '2022-03-02'}; export type GetApiJobsOptions = {baseUrl?: string | undefined, apiSecret?: string | undefined, fetch?: typeof fetch | undefined}; export const paginationMeta = z.object({count: z.number().int().optional(), next_cursor: z.string().optional()}); export const merchantPayment = z.object({card_name: z.string().optional(), last_four_card_number: z.string().optional(), account_name: z.string().optional(), account_type: z.enum(["checking", "savings"]).optional(), last_four_account_number: z.string().optional()}); export const targetAccount = z.object({account_name: z.string().optional(), account_type: z.enum(["checking", "savings"]).optional(), last_four_account_number: z.string().optional()}); export const directDepositAllocationDetail = z.object({type: z.enum(["amount", "percentage", "remainder"]).optional(), value: z.number().int().optional(), target: targetAccount.optional()}); export const paramsPayloadV20231122 = z.object({document_type: z.enum(["paystub", "W-2", "1099", "direct_deposit_form", "verification_report", "1040", "bank_statement", "ssi_award_letter", "w2", "verification_photo"]).optional(), pay_date: z.string().optional(), year: z.number().int().optional(), count: z.number().int().optional(), from_pay_date: z.string().optional(), to_pay_date: z.string().optional(), has_potential_paystubs_documents: z.boolean().optional(), sync_status: z.enum(["in_progress", "complete"]).optional(), bill_id: z.string().optional(), type: z.enum(["ach", "card"]).optional(), payment: merchantPayment.optional(), frequency: z.enum(["weekly", "bi-weekly", "monthly", "bi-monthly", "quarterly", "semi-yearly", "yearly", "other"]).optional(), next_payment_date: z.string().optional(), next_payment_amount_cents: z.number().int().optional(), is_integrated_switch: z.boolean().optional(), is_integrated_cancellation: z.boolean().optional(), detection_method: z.enum(["manual", "external_plaid", "first_party"]).optional(), reported_platform_name: z.string().optional(), reserved_platform_id: z.string().optional(), card_name: z.string().optional(), action: z.enum(["full_switch", "partial_switch"]).optional(), allocation: directDepositAllocationDetail.optional()}); export const jobObjResponseV20231122 = z.object({id: z.string(), name: z.enum(["income", "identity", "bill_cancellation", "bill_switch", "direct_deposit_switch", "tax_forms", "paystubs", "direct_deposit_payment", "direct_deposit_allocations", "shifts", "employment"]), timestamp: z.string(), outcome: z.enum(["pending", "error", "success"]), error_code: z.string().optional(), error_type: z.string().optional(), link_token_id: z.string(), params: paramsPayloadV20231122.optional(), account_id: z.string().optional()}); export const getApiJobsResponse = z.object({meta: paginationMeta, data: z.array(jobObjResponseV20231122)}); 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 Completed Jobs * * GET /jobs */ export const getApiJobs = async (args: GetApiJobsArgs, options?: GetApiJobsOptions) => { const {baseUrl = "https://api.getpinwheel.com/v1", fetch: fetchFn = fetch, apiSecret} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") for (const item of args.job_types ?? []) query.append("job_types", String(item)) if (args.link_token_id != null) query.append("link_token_id", String(args.link_token_id)) if (args.account_id != null) query.append("account_id", String(args.account_id)) if (args.outcome != null) query.append("outcome", String(args.outcome)) if (args.partner_customer_id != null) query.append("partner_customer_id", String(args.partner_customer_id)) if (args.limit != null) query.append("limit", String(args.limit)) if (args.cursor != null) query.append("cursor", String(args.cursor)) if (args.from_timestamp != null) query.append("from_timestamp", String(args.from_timestamp)) if (args.to_timestamp != null) query.append("to_timestamp", String(args.to_timestamp)) if (args['Pinwheel-Version'] != null) headers.set("Pinwheel-Version", String(args['Pinwheel-Version'])) if (apiSecret !== undefined) headers.set("X-API-SECRET", apiSecret) const url = `${baseUrl.replace(/\/+$/, '')}/jobs${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(getApiJobsResponse, await res.json()) };