import {z} from 'zod' export type ErpPostingDateFilter = {from?: string | undefined, to?: string | undefined}; export type UpdatedAtFilter = {gt?: string | undefined, gte?: string | undefined, lt?: string | undefined, lte?: string | undefined}; export type GetApiV3AccountingRecordsArgs = {cursor?: string | undefined, erp_posting_date?: ErpPostingDateFilter | undefined, ids?: Array | undefined, limit?: number | undefined, review_status?: ('PREPARE' | 'REVIEW' | 'EXPORTED' | 'READY_FOR_EXPORT') | undefined, single_entry?: boolean | undefined, source_type?: ('CARD' | 'REIMBURSEMENT' | 'BILL') | undefined, updated_at?: UpdatedAtFilter | undefined}; export type GetApiV3AccountingRecordsOptions = {baseUrl?: string | undefined, bearer?: string | undefined, fetch?: typeof fetch | undefined}; export const amount = z.object({amount: z.number(), currency: z.string()}); export const accountingFieldValueCustomRuleSetByDetails = z.object({rule_id: z.string(), type: z.literal("CUSTOM_RULE")}); export const accountingFieldValueMappingRuleSetByDetails = z.object({mapping_id: z.string().nullable(), mapping_rule_id: z.string(), type: z.literal("MAPPING_RULE")}); export const accountingFieldValueIntegrationSettingsSetByDetails = z.object({type: z.literal("INTEGRATION_SETTINGS")}); export const accountingFieldValue = z.object({brex_field_id: z.string(), brex_field_value_id: z.string().nullable(), field_name: z.string().nullable(), field_value_name: z.string().nullable(), remote_field_id: z.string().nullable(), remote_field_value_id: z.string().nullable(), set_by: z.enum(["MANUAL", "AUTOMATED", "AGENT", "OCR"]).nullable().optional(), set_by_details: z.union([accountingFieldValueCustomRuleSetByDetails, accountingFieldValueMappingRuleSetByDetails, accountingFieldValueIntegrationSettingsSetByDetails, z.object({}).nullable()]).optional(), type: z.enum(["IDENTIFIER", "STRING", "BOOL", "DATE"]).nullable().optional(), value: z.union([z.string().nullable(), z.boolean().nullable()]).optional()}); export const amortizationTemplateRef = z.object({display_name: z.string(), id: z.string(), remote_id: z.string().nullable().optional()}); export const amortizationInfo = z.object({amortization_end_date: z.string().nullable().optional(), amortization_start_date: z.string().nullable().optional(), amortization_template: amortizationTemplateRef.nullable().optional()}); export const accountingRecordLineItem = z.object({accounting_field_values: z.array(accountingFieldValue), amortization: amortizationInfo.nullable().optional(), amount: amount, billing_amount: amount.nullable().optional(), id: z.string(), memo: z.string().nullable().optional(), original_amount: amount.nullable().optional(), type: z.enum(["CREDIT", "DEBIT"])}); export const accountingReceipt = z.object({download_uris: z.array(z.string()).nullable().optional(), id: z.string()}); export const brexAssistant = z.object({disabled_reason: z.enum(["ACCOUNT_DISABLED", "INHERITED_FROM_ACCOUNT_DEFAULT", "USER_DISABLED"]).nullable(), experience: z.enum(["IAF", "LEGACY"]).nullable(), is_enabled: z.boolean().nullable()}); export const cardTransactionsCompletedNotificationPreference = z.object({brex_assistant: z.boolean()}); export const preferredChannel = z.object({channel: z.enum(["SLACK", "SMS", "WHATSAPP"]).nullable(), reply_mode: z.enum(["ONE_WAY", "TWO_WAY"]).nullable()}); export const notificationPreferences = z.object({card_transactions_completed: cardTransactionsCompletedNotificationPreference.nullable(), preferred_channels: z.array(preferredChannel).nullable(), text: z.enum(["OFF", "SMS", "WHATSAPP"]).nullable()}); export const user = z.object({account_id: z.string().optional(), brex_assistant: brexAssistant.nullable(), department_id: z.string().optional(), department_name: z.string().optional(), email: z.string().optional(), first_name: z.string(), id: z.string(), last_name: z.string(), location_id: z.string().optional(), location_name: z.string().optional(), manager_first_name: z.string().optional(), manager_id: z.string().optional(), manager_last_name: z.string().optional(), manager_title_id: z.string().optional(), manager_title_name: z.string().optional(), metadata: z.record(z.string(), z.string()).optional(), notification_preferences: notificationPreferences.nullable(), phone_number: z.string().nullable().optional(), role: z.enum(["ACCOUNT_ADMIN", "AP_CLERK", "BOOKKEEPER", "CARD_ADMIN", "EMPLOYEE", "EMPLOYEE_WITHOUT_CORP_CARDS", "EXPENSE_ADMIN", "SUPER_ADMIN", "USER_MANAGEMENT_ADMIN"]), status: z.enum(["ACTIVE", "ARCHIVED", "DELETED", "DISABLED", "INACTIVE", "INVITED", "PENDING_ACTIVATION"]), title_id: z.string().optional(), title_name: z.string().optional()}); export const vendorTaxFormW8 = z.object({country_of_citizenship: z.string().optional(), date_of_birth: z.string().optional(), domestic_tax_identification_number: z.string().optional(), foreign_tax_identification_number: z.string().optional(), legal_name: z.string().optional()}); export const vendorTaxFormW9 = z.object({business_name: z.string().optional(), is_signed: z.boolean().optional(), legal_name: z.string().optional(), signed_date: z.string().optional(), tax_classification: z.enum(["C_CORP", "INDIVIDUAL", "LLC", "OTHER", "PARTNERSHIP", "S_CORP"]).optional(), tax_identification_number: z.string().optional()}); export const vendorOwners = z.object({id: z.string()}); export const vendorPaymentConfig = z.object({default_payment_method_string: z.string().optional(), id: z.string(), irs_form1099_eligibility_status: z.enum(["ELIGIBLE", "INELIGIBLE"]).optional(), linked_erp_vendor_url: z.string().optional(), linked_vendor_erp_name: z.string().optional(), payment_terms: z.string().optional(), self_serve_request_payment_instrument: z.enum(["COMPLETE", "EXPIRED", "NOT_ALLOWED", "REQUESTED"]).optional(), self_serve_request_tax_info: z.enum(["COMPLETE", "EXPIRED", "NOT_ALLOWED", "REQUESTED"]).optional(), tax_form_tin_validation_status: z.enum(["INVALID", "NOT_REQUESTED", "REQUESTED", "VALID"]).optional(), tax_form_w8: vendorTaxFormW8.optional(), tax_form_w9: vendorTaxFormW9.optional(), vendor_owners: vendorOwners.optional()}); export const address = z.object({address1: z.string().optional(), address2: z.string().optional(), city: z.string().optional(), country: z.string().optional(), state: z.string().optional(), zip: z.string().optional()}); export const railInfo = z.object({type: z.enum(["AU_BECS", "CARD", "CA_EFT", "CRYPTO_BASE", "CRYPTO_ETH", "EU_SEPA", "GLOBAL_BANK_TRANSFER", "HK_FPS", "INTL_SWIFT_WIRE", "SG_EGIRO", "UK_BACS", "US_ACH", "US_CHEQUE", "US_WIRE"])}); export const railAccountDetailsCurrency = z.object({code: z.string()}); export const railAccountDetails = z.object({account_holder_address: address.optional(), bank_account_class: z.enum(["BUSINESS", "PERSONAL"]).optional(), bank_name: z.string().optional(), beneficiary_bank_address: address.optional(), beneficiary_name_info: z.record(z.string(), z.string()).optional(), beneficiary_type: z.enum(["BUSINESS", "INDIVIDUAL"]).optional(), display_name: z.string(), id: z.string(), inserted_at: z.string(), properties: z.record(z.string(), z.string()), rail: railInfo, status: z.enum(["ACCOUNT_CLOSED", "ACTIVE", "AUTOMATICALLY_INVALIDATED", "DELETED", "DISABLED", "FAILED_VERIFICATIONS", "PENDING_APPROVAL", "REJECTED"]), target_country: z.string().optional(), target_currency: railAccountDetailsCurrency.optional(), updated_at: z.string()}); export const vendor = z.object({contact_name: z.string().optional(), customer_account_id: z.string(), customer_dba_name: z.string().optional(), email: z.string().optional(), id: z.string(), name: z.string(), payment_config: vendorPaymentConfig.optional(), phone: z.string().optional(), rail_account_details: z.array(railAccountDetails).optional(), website: z.string().optional()}); export const accountingRecord = z.object({amount: amount, billing_amount: amount.nullable().optional(), due_at: z.string().nullable().optional(), erp_posting_date: z.string(), id: z.string(), invoice_number: z.string().nullable().optional(), line_items: z.array(accountingRecordLineItem), memo: z.string().nullable().optional(), original_amount: amount.nullable().optional(), posted_at: z.string(), receipts: z.array(accountingReceipt).nullable().optional(), review_status: z.enum(["READY_FOR_EXPORT", "PREPARE", "REVIEW", "EXPORTED"]), source_id: z.string().nullable().optional(), source_type: z.enum(["CARD", "REIMBURSEMENT", "BILL"]), status: z.enum(["APPROVED", "CANCELED", "DRAFT", "OUT_OF_POLICY", "SETTLED", "SPLIT", "SUBMITTED", "VOID"]).nullable().optional(), type: z.enum(["CARD_EXPENSE_POST", "CARD_EXPENSE_REPAYMENT", "REIMBURSEMENT_EXPENSE_REPAYMENT", "REIMBURSEMENT_EXPENSE_APPROVAL", "REIMBURSEMENT_EXPENSE_MONEY_MOVEMENT_PAYOUT", "REIMBURSEMENT_EXPENSE_MONEY_MOVEMENT_PAYIN", "REIMBURSEMENT_EXPENSE_POST", "CARD_PAYMENT", "REWARD_REDEMPTION", "REIMBURSEMENT_EXPENSE_MONEY_MOVEMENT_REFUND", "CARD_EXPENSE_REFUND", "CARD_PAYMENT_REFUND", "REIMBURSEMENT_EXPENSE_MONEY_MOVEMENT_PAYOUT_REVERSAL", "INVOICE", "INVOICE_PAYMENT", "BILL_EXPENSE", "BILL_EXPENSE_PAYMENT", "BILL_EXPENSE_CARD_PAYMENT", "BILL_MONEY_MOVEMENT_PAYIN"]), updated_at: z.string(), user: user.nullable().optional(), vendor: vendor.nullable().optional(), version: z.number()}); export const accountingRecordPaginatedResponse = z.object({has_next_page: z.boolean(), items: z.array(accountingRecord), next_cursor: z.string().nullable(), total_count: z.number().nullable().optional()}); 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 }; /** * Query accounting records * * GET /v3/accounting/records * * Also accepts auth: oauth2 */ export const getApiV3AccountingRecords = async (args: GetApiV3AccountingRecordsArgs, options?: GetApiV3AccountingRecordsOptions) => { const {baseUrl = "https://api.brex.com", fetch: fetchFn = fetch, bearer} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.cursor != null) query.append("cursor", String(args.cursor)) if (args.erp_posting_date != null) query.append("erp_posting_date", JSON.stringify(args.erp_posting_date)) for (const item of args.ids ?? []) query.append("ids", String(item)) if (args.limit != null) query.append("limit", String(args.limit)) if (args.review_status != null) query.append("review_status", String(args.review_status)) if (args.single_entry != null) query.append("single_entry", String(args.single_entry)) if (args.source_type != null) query.append("source_type", String(args.source_type)) if (args.updated_at != null) query.append("updated_at", JSON.stringify(args.updated_at)) if (bearer !== undefined) headers.set('authorization', `Bearer ${bearer}`) const url = `${baseUrl.replace(/\/+$/, '')}/v3/accounting/records${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(accountingRecordPaginatedResponse, await res.json()) };