import {z} from 'zod' export type GetApiContractsCampaignsCampaignIdArgs = {campaign_id: number, cursor?: (string | null) | undefined, limit?: number | undefined}; export type GetApiContractsCampaignsCampaignIdOptions = {baseUrl?: string | undefined, ApiKeyAuth?: string | undefined, ShopId?: string | undefined, fetch?: typeof fetch | undefined}; export const contractProduct = z.object({product_id: z.string().min(1), product_name: z.string().nullable().optional()}); export const retainerTier = z.object({name: z.string(), retainer: z.union([z.number(), z.string()]), gmv_threshold: z.number().gte(0)}); export const gmvBonusTier = z.object({gmv_threshold: z.number().gte(0), bonus_amount: z.union([z.number(), z.string()])}); export const retainerReward = z.object({kind: z.literal("retainer"), mode: z.enum(["fixed", "tiered"]), payment_per_creator: z.union([z.number(), z.string()]).nullable().optional(), payment_per_post: z.union([z.number(), z.string()]).nullable().optional(), tiers: z.array(retainerTier).nullable().optional(), gmv_bonus_tiers: z.array(gmvBonusTier).nullable().optional(), recurring: z.boolean().optional(), payment_schedule: z.enum(["one_time", "monthly"]).optional(), payment_due_days: z.number().int().gte(1).lte(120).optional(), minimum_gmv_floor: z.number().gte(0).nullable().optional(), live_sessions: z.number().int().gte(1).nullable().optional(), live_min_minutes: z.number().int().gte(1).lte(1440).nullable().optional(), hourly_rate: z.union([z.number(), z.string()]).nullable().optional(), max_paid_hours: z.number().int().gte(1).lte(744).nullable().optional(), minimum_live_gmv_floor: z.number().gte(0).nullable().optional(), gmv_floor_combined: z.boolean().nullable().optional()}); export const challengePaymentTier = z.object({threshold: z.number().gt(0), compensation: z.union([z.number(), z.string()]), videos_required: z.number().int().gte(0).nullable().optional(), sessions_required: z.number().int().gte(0).nullable().optional()}); export const challengeGate = z.object({metric: z.enum(["video_count", "gmv"]), threshold: z.number().gt(0)}); export const challengeReward = z.object({kind: z.literal("challenge"), metric_type: z.enum(["gmv", "views", "video_count", "gmv_videos", "live_gmv", "gmv_and_live_gmv", "videos_gmv_and_live_gmv", "sessions", "sessions_and_live_gmv"]), payment_tiers: z.array(challengePaymentTier), minimum_gate: challengeGate.nullable().optional(), gmv_floor: z.number().gte(0).nullable().optional(), videos_floor: z.number().int().gte(0).nullable().optional(), views_floor: z.number().int().gte(0).nullable().optional(), live_gmv_floor: z.number().gte(0).nullable().optional(), sessions_floor: z.number().int().gte(0).nullable().optional(), lives_count_as_posts: z.boolean().nullable().optional(), live_min_minutes: z.number().int().gte(1).lte(1440).nullable().optional(), ranking_priority: z.enum(["gmv", "videos", "live_gmv", "sessions"]).nullable().optional(), payment_due_days: z.number().int().gte(1).lte(120).optional()}); export const contestRank = z.object({rank: z.number().int().gte(1), reward: z.union([z.number(), z.string()])}); export const leaderboardTier = z.object({name: z.string(), bonus: z.union([z.number(), z.string()]), threshold: z.number().gte(0), videos_required: z.number().int().nullable().optional(), num_winners: z.number().int().nullable().optional()}); export const contractWeightedMetric = z.object({metric_type: z.enum(["gmv", "avg_views_per_post", "views", "videos_posted", "units_sold", "live_gmv", "combined_gmv", "sessions"]), weight: z.number().gte(0).lte(100)}); export const leaderboardReward = z.object({kind: z.literal("leaderboard"), mode: z.enum(["rank", "tier"]), contest_ranks: z.array(contestRank).nullable().optional(), tiers: z.array(leaderboardTier).nullable().optional(), random_winner_wheel: z.boolean().optional(), scoring_mode: z.enum(["single", "weighted"]).nullable().optional(), weighted_metrics: z.array(contractWeightedMetric).nullable().optional(), metric_type: z.enum(["gmv", "views", "videos_posted", "gmv_videos", "live_gmv", "gmv_and_live_gmv", "videos_gmv_and_live_gmv"]).nullable().optional(), gmv_floor: z.number().gte(0).nullable().optional(), videos_floor: z.number().int().gte(0).nullable().optional(), live_gmv_floor: z.number().gte(0).nullable().optional(), sessions_floor: z.number().int().gte(0).nullable().optional(), payment_due_days: z.number().int().gte(1).lte(120).optional()}); export const reimbursementStep = z.object({id: z.string(), label: z.string(), description: z.string()}); export const reimbursementReward = z.object({kind: z.literal("reimbursement"), reimbursement_amount: z.number().gt(0), reimbursement_bonus: z.number().gte(0).nullable().optional(), minimum_star_rating: z.number().int().gte(1).lte(5).nullable().optional(), steps: z.array(reimbursementStep), payment_due_days: z.number().int().gte(1).lte(120).optional()}); export const contractTerms = z.object({campaign_type: z.enum(["retainer", "challenge", "leaderboard", "reimbursement"]), brand_name: z.string().min(1), brand_representative_name: z.string().min(1), deliverables: z.number().int().gte(1), posting_platform: z.enum(["TikTok", "Instagram", "YouTube", "TikTok+Instagram"]).optional(), posting_requirements: z.string().optional(), posting_start_date: z.string(), effective_date: z.string(), expiry_date: z.string(), products: z.array(contractProduct).optional(), all_products: z.boolean().optional(), require_video_link_email: z.boolean().optional(), video_link_email: z.string().nullable().optional(), commission_pct: z.number().gte(0).lte(100).nullable().optional(), expenses_reimbursable: z.boolean().optional(), revision_rounds: z.union([z.literal(0), z.literal(1), z.literal(2)]).optional(), include_usage_rights: z.boolean().optional(), usage_rights_months: z.number().int().gte(0).lte(120).optional(), usage_rights_duration_days: z.number().int().optional(), usage_rights_scope: z.enum(["organic_only", "organic_and_paid", "paid_only"]).optional(), usage_exclusivity_enabled: z.boolean().optional(), usage_exclusivity_months: z.number().int().gte(1).lte(120).optional(), agreement_clause: z.string().nullable().optional(), termination_notice_days: z.number().int().gte(1).optional(), governing_law_jurisdiction: z.string().optional(), reward: z.discriminatedUnion("kind", [retainerReward, challengeReward, leaderboardReward, reimbursementReward])}); export const contractEnvelopeOut = z.object({id: z.string(), batch_id: z.string(), campaign_id: z.number().int(), campaign_name: z.string(), creator_id: z.number().int(), creator_name: z.string(), creator_handle: z.string(), contract_title: z.string(), template_source: z.literal("standard"), terms: contractTerms, status: z.enum(["draft", "sent", "awaiting_brand", "active", "expired", "voided", "delivery_failed"]), signing_order: z.enum(["brand_first", "creator_first"]).optional(), brand_signed_at: z.string().nullable().optional(), creator_signed_at: z.string().nullable().optional(), effective_date: z.string(), expiry_date: z.string(), last_error: z.string().nullable().optional()}); export const paginatedEnvelopes = z.object({data: z.array(contractEnvelopeOut), next_cursor: z.string().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 }; /** * List one campaign's contracts * * GET /contracts/campaigns/{campaign_id} */ export const getApiContractsCampaignsCampaignId = async (args: GetApiContractsCampaignsCampaignIdArgs, options?: GetApiContractsCampaignsCampaignIdOptions) => { const {baseUrl = "https://api.reacherapp.com/public/v1", fetch: fetchFn = fetch, ApiKeyAuth, ShopId} = 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.limit != null) query.append("limit", String(args.limit)) if (ApiKeyAuth !== undefined) headers.set("x-api-key", ApiKeyAuth) if (ShopId !== undefined) headers.set("x-shop-id", ShopId) const url = `${baseUrl.replace(/\/+$/, '')}/contracts/campaigns/${encodeURIComponent(String(args.campaign_id))}${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(paginatedEnvelopes, await res.json()) };