import type { ApiSettings, CampaignListItem, DeltaDeletedItem } from "../types"; import { apiDownload, apiFetch } from "./client"; import { apiReferenceOptionProvider, type ReferenceOptionProvider } from "@govoplan/core-webui"; import { campaignJobsQueryParams, type CampaignJobsQueryParameters } from "../features/campaigns/utils/jobListQuery"; import { campaignVersionUpdateForRequest } from "../features/campaigns/utils/editorState"; export { fetchResourceAccessExplanation, fetchResourceAccessExplanationSubjects } from "@govoplan/core-webui"; export type { AccessDecisionProvenanceItem, ResourceAccessExplanationUser as AccessExplanationUser, ResourceAccessExplanationResponse, ResourceAccessExplanationSubjectsResponse } from "@govoplan/core-webui"; export type CampaignListResponse = CampaignListItem[] | { campaigns?: CampaignListItem[]; items?: CampaignListItem[]; results?: CampaignListItem[]; }; export type CampaignShare = { id: string; campaign_id: string; target_type: "user" | "group"; target_id: string; permission: "read" | "write"; revoked_at?: string | null; }; export type CampaignShareTarget = {id: string;name: string;secondary?: string | null;}; export type CampaignShareTargets = {users: CampaignShareTarget[];groups: CampaignShareTarget[];}; export type CampaignCollaborationReferenceKind = | "campaign_version" | "recipient_import_batch" | "attachment_rule" | "delivery_job" | "report"; export type CampaignCollaborationReference = { kind: CampaignCollaborationReferenceKind; id: string; label?: string | null; }; export type CampaignCollaborationEntry = { id: string; campaign_id: string; actor_user_id?: string | null; actor_label: string; visibility: "collaborators" | "moderators"; content?: string | null; content_sha256: string; mention_user_ids: string[]; reference?: CampaignCollaborationReference | null; tombstone?: "withdrawn" | "redacted" | null; tombstone_reason?: string | null; withdrawn_at?: string | null; redacted_at?: string | null; created_at: string; }; export type CampaignCollaborationListResponse = { items: CampaignCollaborationEntry[]; next_cursor?: string | null; has_more: boolean; }; export type CampaignCollaborationCreate = { content: string; visibility?: CampaignCollaborationEntry["visibility"]; reference?: Pick | null; mention_user_ids?: string[]; }; export type CampaignWorkAssigneeType = "account" | "group" | "organization_function"; export type CampaignWorkAssignmentStatus = "open" | "in_progress" | "completed" | "rejected" | "cancelled"; export type CampaignWorkAssignmentResolutionState = "resolved" | "unavailable" | "provider_unavailable"; export type CampaignWorkAssignment = { id: string; campaign_id: string; purpose: string; status: CampaignWorkAssignmentStatus; due_at?: string | null; assignee_type: CampaignWorkAssigneeType; assignee_id: string; assignee_label_snapshot: string; assignee_current_label?: string | null; assignee_resolution_state: CampaignWorkAssignmentResolutionState; resolution_provenance: Record; resolution_checked_at: string; assigned_by_user_id?: string | null; assigned_by_label: string; reference?: CampaignCollaborationReference | null; completed_at?: string | null; cancelled_at?: string | null; task_mirror_id?: string | null; task_mirror_status: "not_configured" | "mirrored" | "failed" | "skipped"; task_mirror_error?: string | null; resource_revision: number; created_at: string; updated_at: string; }; export type CampaignWorkAssignmentListResponse = { items: CampaignWorkAssignment[]; next_cursor?: string | null; has_more: boolean; }; export type CampaignWorkAssignmentEvent = { id: string; assignment_id: string; event_kind: string; actor_user_id?: string | null; actor_label: string; status: CampaignWorkAssignmentStatus; assignee_type: CampaignWorkAssigneeType; assignee_id: string; assignee_label: string; resolution_state: CampaignWorkAssignmentResolutionState; details: Record; created_at: string; }; export type CampaignWorkAssignmentHistoryResponse = { items: CampaignWorkAssignmentEvent[]; next_cursor?: string | null; has_more: boolean; }; export type CampaignArchiveEncryptionPolicy = { available: boolean; allowed_password_encryption_methods: Array<"aes" | "zip_standard">; allowed_password_delivery_channels: Array<"separate_mail" | "sms" | "letter" | "phone" | "in_person">; policy_hash: string; source_path: Array<{ scope_type: string; scope_id?: string | null; path: string; label: string; applied_fields: string[]; policy: Record; }>; reason: string; diagnostics: Array>; legacy_label: string; }; export type CampaignUpdatePayload = { external_id?: string | null; name?: string | null; status?: string | null; description?: string | null; }; export type CampaignCreateMinimalPayload = { external_id?: string; name?: string; description?: string | null; current_flow?: string; current_step?: string; }; export type CampaignCreateResponse = { campaign: CampaignListItem & { current_version_id?: string | null; }; version: CampaignVersionListItem; }; export type CampaignVersionListItem = { id: string; campaign_id: string; version_number: number; edit_revision: number; strong_etag: string; schema_version?: string; source_filename?: string | null; source_base_path?: string | null; workflow_state?: string; current_flow?: string; current_step?: string | null; is_complete?: boolean; editor_state?: Record; autosaved_at?: string | null; published_at?: string | null; locked_at?: string | null; locked_by_user_id?: string | null; user_lock_state?: "temporary" | "permanent" | null; user_locked_at?: string | null; user_locked_by_user_id?: string | null; created_at?: string; updated_at?: string; validation_summary?: Record | null; build_summary?: Record | null; execution_snapshot_hash?: string | null; execution_snapshot_at?: string | null; delivery_mode?: "synchronous" | "worker_queue" | "database_queue" | null; delivery_mode_selected_at?: string | null; archived_at?: string | null; archived_by_user_id?: string | null; }; export type CampaignLifecycleAction = { allowed: boolean; reason?: string | null; }; export type CampaignLifecyclePolicy = { policy_id: string; policy_version: string; state_token: string; actions: Record; provenance: Record; }; export type CampaignCopyOptions = { name?: string; external_id?: string; include_recipients: boolean; include_files: boolean; include_shares: boolean; include_policies: boolean; include_mail_profile: boolean; }; export type CampaignTransferScope = | "metadata" | "template_config" | "recipients" | "attachments" | "review_state" | "delivery_history"; export type CampaignPortablePackage = { format: "govoplan.campaign-portable"; format_version: string; package_id: string; exported_at: string; source: Record; scopes: CampaignTransferScope[]; manifest: Record; payload: Record; integrity: { algorithm: string; package_sha256: string }; }; export type CampaignTransferPlanItem = { scope: CampaignTransferScope; code: string; summary: string; item_count?: number | null; }; export type CampaignImportPreview = { compatible: boolean; package_id?: string | null; package_sha256?: string | null; format_version?: string | null; source: Record; available_scopes: CampaignTransferScope[]; selected_scopes: CampaignTransferScope[]; destination: { external_id?: string; name?: string; status?: string }; will_create: CampaignTransferPlanItem[]; will_skip: CampaignTransferPlanItem[]; warnings: string[]; errors: string[]; }; export type CampaignImportApplyResponse = CampaignCreateResponse & { receipt: Record; }; export type CampaignScheduleOccurrence = { id: string; schedule_id: string; scheduled_for: string; status: "prepared" | "accepted" | "uncertain" | "failed" | "skipped" | "superseded" | string; idempotency_key?: string | null; generated_campaign_id?: string | null; generated_version_id?: string | null; error?: string | null; delivery_command_ids: string[]; recovery_state: string; evidence: Record; last_checked_at?: string | null; created_at: string; }; export type CampaignSchedule = { id: string; campaign_id: string; source_version_id: string; name: string; delivery_mode: "manual" | "autonomous"; recurrence_kind: "once" | "daily" | "weekly" | "monthly"; interval_count: number; timezone: string; starts_at: string; next_fire_at?: string | null; ends_at?: string | null; max_occurrences: number; occurrence_count: number; active: boolean; resource_revision: number; last_fired_at?: string | null; last_campaign_id?: string | null; last_error?: string | null; last_outcome?: string | null; last_recovery_state?: string | null; created_at: string; updated_at: string; occurrences: CampaignScheduleOccurrence[]; }; export type CampaignScheduleCreate = { source_version_id: string; name: string; delivery_mode: CampaignSchedule["delivery_mode"]; recurrence_kind: CampaignSchedule["recurrence_kind"]; interval_count: number; timezone: string; starts_at: string; ends_at?: string | null; max_occurrences: number; include_recipients: boolean; include_files: boolean; include_shares: boolean; include_policies: boolean; include_mail_profile: boolean; }; export type CampaignVersionDetail = CampaignVersionListItem & { review_build_token?: string | null; raw_json: Record; campaign_json?: Record; mail_profile_migration_required?: boolean; }; export type CampaignWorkspaceResponse = { campaign: CampaignListItem | null; versions: CampaignVersionListItem[]; current_version: CampaignVersionDetail | null; summary: CampaignSummary | null; selected_version_id?: string | null; }; export type CampaignDeltaResponse = { campaigns: CampaignListItem[]; deleted: DeltaDeletedItem[]; watermark?: string | null; has_more: boolean; full: boolean; total: number; page: number; page_size: number; pages: number; }; export type CampaignWorkspaceDeltaResponse = CampaignWorkspaceResponse & { deleted: DeltaDeletedItem[]; watermark?: string | null; has_more: boolean; full: boolean; }; export type CampaignWorkspaceQuery = { versionId?: string | null; includeCurrentVersion?: boolean; includeSummary?: boolean; includeVersions?: boolean; since?: string | null; limit?: number; }; export type RecipientImportColumnKind = "ignore" | "id" | "active" | "name" | "from" | "to" | "cc" | "bcc" | "reply_to" | "field" | "new_field" | "attachment_pattern"; export type RecipientImportColumnMappingProfileItem = { columnIndex: number; kind: RecipientImportColumnKind; fieldName?: string | null; newFieldName?: string | null; }; export type RecipientImportMappingProfile = { id: string; name: string; createdAt: string; updatedAt: string; columnCount: number; headers: string[]; normalizedHeaders: string[]; orderedHeaderFingerprint: string; unorderedHeaderFingerprint: string; delimiter: "," | ";" | "\t"; headerRows: number; quoted: boolean; valueSeparators: string; mappings: RecipientImportColumnMappingProfileItem[]; }; export type RecipientImportMappingProfilePayload = Omit; export type RecipientImportMappingProfileListResponse = { profiles: RecipientImportMappingProfile[]; }; export type CampaignAddressLookupCandidate = { contact_id: string; address_book_id: string; display_name: string; email?: string | null; email_label?: string | null; organization?: string | null; role_title?: string | null; tags: string[]; source_kind: string; source_ref?: string | null; source_revision?: string | null; provenance: Record; }; export type CampaignAddressLookupResponse = { available: boolean; candidates: CampaignAddressLookupCandidate[]; }; export type CampaignRecipientAddressSource = { source_id: string; source_label: string; source_kind: string; source_revision: string; recipient_count: number; provenance: Record; }; export type CampaignRecipientAddressSourcesResponse = { available: boolean; sources: CampaignRecipientAddressSource[]; }; export type CampaignDistributionListParameter = { key: string; value_type: "string" | "integer" | "number" | "boolean" | "date" | "datetime" | "string_list"; label?: string | null; required: boolean; default?: unknown; allowed_values: unknown[]; minimum?: number | null; maximum?: number | null; pattern?: string | null; description?: string | null; }; export type CampaignDistributionListSource = { id: string; tenant_id: string; name: string; revision_id: string; revision: number; definition_hash: string; definition_kind: string; description?: string | null; status: string; entry_count: number; read_only: boolean; stale: boolean; parameters: CampaignDistributionListParameter[]; updated_at?: string | null; provenance: Record; metadata: Record; }; export type CampaignDistributionListSourcesResponse = { available: boolean; expand_available: boolean; sources: CampaignDistributionListSource[]; }; export type CampaignDistributionSourceReference = { provider: string; resource_type: string; resource_id: string; revision?: string | null; fingerprint?: string | null; label?: string | null; metadata: Record; }; export type CampaignDistributionExplanation = { code: string; message: string; severity: "info" | "warning" | "error" | string; provider?: string | null; source?: CampaignDistributionSourceReference | null; provenance: Record; }; export type CampaignDistributionChannelCandidate = { channel: "email" | "postal" | "internal_mail" | "portal" | string; target: string; target_key: string; status: string; contact_point_id?: string | null; locale?: string | null; preferred: boolean; reason_code?: string | null; explanation?: string | null; source?: CampaignDistributionSourceReference | null; decision_provenance: Record; }; export type CampaignDistributionRecipient = { recipient_key: string; display_name: string; status: string; channels: CampaignDistributionChannelCandidate[]; identity_id?: string | null; account_id?: string | null; contact_id?: string | null; organization_unit_id?: string | null; function_id?: string | null; source_entry_ids: string[]; explanations: CampaignDistributionExplanation[]; attributes: Record; provenance: Record; }; export type CampaignDistributionProviderEvidence = { provider: string; source: CampaignDistributionSourceReference; actual_revision?: string | null; actual_fingerprint?: string | null; stale: boolean; generated_at?: string | null; details: Record; }; export type CampaignDistributionListExpansion = { source: CampaignDistributionListSource; request: Record; recipients: CampaignDistributionRecipient[]; excluded: CampaignDistributionRecipient[]; diagnostics: CampaignDistributionExplanation[]; provider_evidence: CampaignDistributionProviderEvidence[]; expansion_hash: string; generated_at?: string | null; snapshot_id?: string | null; stale: boolean; truncated: boolean; }; export type CampaignDistributionListExpansionInput = { list_id: string; revision?: number | null; effective_at?: string | null; purpose?: string; requested_channels: Array<"email" | "postal" | "internal_mail" | "portal">; parameters: Record; idempotency_key?: string | null; }; export type CampaignPostboxDirectoryEntry = { id: string; address: string; address_key: string; name: string; status: string; classification: string; organization_unit_id?: string | null; organization_unit_name?: string | null; function_id?: string | null; function_name?: string | null; context_key?: string | null; holder_count: number; vacant: boolean; }; export type CampaignPostboxTemplate = { id: string; slug: string; name: string; description?: string | null; published_revision_id: string; function_type_id?: string | null; scope_kind: string; scope_id?: string | null; classification: string; allow_vacant_delivery: boolean; }; export type CampaignPostboxFunction = { id: string; slug: string; name: string; function_type_id?: string | null; }; export type CampaignPostboxOrganizationUnit = { id: string; slug: string; name: string; unit_type_id?: string | null; parent_id?: string | null; functions: CampaignPostboxFunction[]; }; export type CampaignPostboxCatalog = { available: boolean; postboxes: CampaignPostboxDirectoryEntry[]; templates: CampaignPostboxTemplate[]; organization_units: CampaignPostboxOrganizationUnit[]; }; export type CampaignCalendarCatalogEntry = { id: string; name: string; color?: string | null; timezone: string; source_kind: string; writable: boolean; }; export type CampaignCalendarCatalog = { available: boolean; reason?: string | null; calendars: CampaignCalendarCatalogEntry[]; }; export type CampaignPrintTemplateOutputProfile = { id: string; label: string; output_format: "html" | "text"; media_type: string; channel: string; capabilities: string[]; page: Record; }; export type CampaignPrintTemplate = { id: string; name: string; slug: string; template_type: string; status: string; current_revision: number; current_revision_id: string; published_revision_id?: string | null; description?: string | null; read_only: boolean; revision?: { revision: number; output_profiles: CampaignPrintTemplateOutputProfile[]; required_fields: Array<{path: string;label?: string | null;required: boolean;}>; } | null; }; export type CampaignPrintTemplatesResponse = { available: boolean; templates: CampaignPrintTemplate[]; }; export type CampaignContentLibraryTarget = "subject" | "text" | "html"; export type CampaignContentLibraryItem = { id: string; name: string; description?: string | null; template_type: string; kind: "fragment" | "campaign_part"; status: string; scope_type: "tenant" | "group" | "user"; scope_id?: string | null; read_only: boolean; current_revision: number; revision: number; revision_id: string; locale?: string | null; published: boolean; targets: CampaignContentLibraryTarget[]; subject?: string | null; text?: string | null; html?: string | null; body_mode: "text" | "html" | "both"; required_fields: Array<{ path: string; value_type: "string" | "integer" | "number" | "boolean" | "date" | "datetime" | "object" | "array"; label?: string | null; required: boolean; description?: string | null; }>; }; export type CampaignContentLibraryResponse = { available: boolean; writable: boolean; reason?: string | null; items: CampaignContentLibraryItem[]; }; export type CampaignContentLibrarySaveInput = { name: string; description?: string | null; kind: "fragment" | "campaign_part"; target?: CampaignContentLibraryTarget | null; subject?: string | null; text?: string | null; html?: string | null; body_mode: "text" | "html" | "both"; locale: string; visibility: "personal" | "tenant"; }; export type CampaignRecipientSnapshotItem = { contact_id: string; display_name: string; email: string; email_label?: string | null; fields: Record; provenance: Record; }; export type CampaignRecipientSnapshotExcludedItem = { contact_id: string; display_name: string; channel: string; target: string; contact_point_id?: string | null; status: string; reason_code?: string | null; explanation?: string | null; provenance: Record; }; export type CampaignRecipientAddressSourceSnapshot = { source_id: string; source_label: string; source_kind: string; source_revision: string; generated_at: string; recipients: CampaignRecipientSnapshotItem[]; excluded: CampaignRecipientSnapshotExcludedItem[]; included_count: number; excluded_count: number; purpose: string; provenance: Record; }; export type CampaignVersionUpdatePayload = { campaign_json?: Record | null; current_flow?: string | null; current_step?: string | null; workflow_state?: string | null; is_complete?: boolean | null; editor_state?: Record | null; source_filename?: string | null; source_base_path?: string | null; migrate_legacy_mail_settings?: boolean; base_revision?: number; reconciliation_kind?: "none" | "auto_merge" | "manual"; resolved_conflict_paths?: string[]; }; export type CampaignPartialValidationPayload = { campaign_json?: Record | null; section?: string | null; }; export type CampaignPartialValidationResponse = { ok: boolean; section?: string | null; error_count: number; warning_count: number; info_count: number; issues: Record[]; }; export type CampaignRetentionReport = { policy_status: "configured" | "defaults" | "unavailable" | string; policy_reason?: string | null; effective_policy: { store_raw_campaign_json?: boolean; raw_campaign_json_retention_days?: number | null; generated_eml_retention_days?: number | null; stored_report_detail_retention_days?: number | null; mock_mailbox_retention_days?: number | null; audit_detail_retention_days?: number | null; audit_detail_level?: string; }; sources: Array<{ scope_type?: string | null; path?: string | null; label?: string | null; applied_fields?: string[]; }>; evidence: Record; privacy_impact: { state: string; summary: string; retained_categories: string[]; minimized_categories: string[]; }; }; export type CampaignSummary = { generated_at?: string; selected_version_id?: string | null; campaign?: CampaignListItem; current_version?: { id: string; version_number?: number; schema_version?: string; source_filename?: string | null; created_at?: string | null; validation_summary?: Record | null; build_summary?: Record | null; delivery_mode?: "synchronous" | "worker_queue" | "database_queue" | null; delivery_mode_selected_at?: string | null; } | null; cards?: { jobs_total?: number; inactive?: number; queueable?: number; queueable_unattempted?: number; retryable?: number; cancellable?: number; needs_attention?: number; sent?: number; smtp_accepted?: number; postbox_accepted?: number; print_accepted?: number; failed?: number; outcome_unknown?: number; not_attempted?: number; skipped?: number; queued_or_active?: number; cancelled?: number; partially_completed?: boolean; imap_appended?: number; imap_failed?: number; imap_skipped?: number; }; status_counts?: Record>; issues?: Record; attachments?: Record; attempts?: Record; postbox_receipts?: Record; retention?: CampaignRetentionReport; delivery?: Record; recent_failures?: Record[]; }; export type CampaignQueuePayload = { version_id?: string | null; include_warnings?: boolean; enqueue_celery?: boolean; dry_run?: boolean; }; export type CampaignDeliveryOptions = { campaign_id: string; version_id: string; worker_queue_available: boolean; postbox_available: boolean; approval_gate: { configured?: boolean; available?: boolean; approved?: boolean; state?: string; explanation?: string | null; request_id?: string; request_revision?: number; subject_digest?: string; }; synchronous_send: { allowed?: boolean; reason?: string | null; message?: string | null; eligible_recipient_job_count?: number; policy?: { max_recipient_jobs?: number; source?: string; deployment_max_recipient_jobs?: number; tenant_max_recipient_jobs?: number | null; }; }; }; export type CampaignApprovalRequestPayload = { title: string; description?: string | null; steps: Array<{ key: string; label: string; selectors: Array<{ kind: "account" | "group" | "role" | "function_assignment" | "any_account"; value: string; }>; required_approvals: number; rejection_policy: "fail_fast" | "collect"; signature_required: boolean; forbidden_evidence_roles: Array<"author" | "owner" | "validator" | "builder" | "reviewer">; }>; unique_actors_across_steps: boolean; policy_refs: string[]; idempotency_key: string; }; export type CampaignSendNowPayload = { version_id?: string | null; include_warnings?: boolean; check_files?: boolean; validate_before_send?: boolean; build_before_send?: boolean; dry_run?: boolean; use_rate_limit?: boolean; enqueue_imap_task?: boolean; }; export type CampaignAppendSentPayload = { version_id?: string; dry_run?: boolean; enqueue_celery?: boolean; run_inline?: boolean; }; export type CampaignAttachmentPreviewFile = { id: string; version_id?: string; blob_id?: string; display_path: string; filename: string; owner_type: string; owner_id: string; checksum_sha256?: string; size_bytes?: number; content_type?: string | null; linked_to_campaign?: boolean; share?: Record | null; }; export type CampaignAttachmentPreviewRule = { source: "global" | "entry"; entry_index: number; entry_id?: string | null; index: number; attachment_id?: string | null; label?: string | null; required: boolean; pattern: string; base_path_name?: string | null; base_path?: string | null; status: "ok" | "missing" | "ambiguous"; behavior?: string | null; zip_included?: boolean; zip_mode?: "inherit" | "include" | "exclude"; zip_archive_id?: string | null; zip_filename?: string | null; matches: CampaignAttachmentPreviewFile[]; match_count: number; linked_match_count?: number; unlinked_match_count?: number; issues: Record[]; }; export type CampaignAttachmentPreviewResponse = { campaign_id: string; version_id: string; shared_file_count: number; candidate_file_count?: number; matched_file_count?: number; linked_file_count?: number; unlinked_file_count?: number; rules: CampaignAttachmentPreviewRule[]; linkable_files?: CampaignAttachmentPreviewFile[]; unused_shared_files: CampaignAttachmentPreviewFile[]; }; export type CampaignAttachmentPreviewPayload = { include_unmatched?: boolean; include_unlinked_candidates?: boolean; campaign_json?: Record; }; export type CampaignAttachmentLinkMatchesPayload = { campaign_json?: Record | null; dry_run?: boolean; }; export type CampaignAttachmentLinkMatchesResponse = { campaign_id: string; version_id: string; matched_file_count: number; already_linked_file_count: number; linked_file_count: number; dry_run?: boolean; linked_files: CampaignAttachmentPreviewFile[]; linkable_files: CampaignAttachmentPreviewFile[]; }; export type CampaignMockSendPayload = { use_reviewed_build?: boolean; version_id?: string | null; send?: boolean; include_warnings?: boolean; include_needs_review?: boolean; append_sent?: boolean; clear_mailbox?: boolean; check_files?: boolean; }; export type CampaignReviewStatePayload = { merge_progress?: boolean; build_token?: string | null; base_revision?: number | null; decision_category_key?: string | null; inspection_complete: boolean; reviewed_message_keys: string[]; issue_decisions: Array<{ job_id: string; decision: "accept"; reason?: string | null; }>; }; export type CampaignSendJobPayload = { kind: "test" | "single_send" | "single_resend"; idempotency_key: string; reason?: string; context?: Record; include_warnings?: boolean; use_rate_limit?: boolean; enqueue_imap_task?: boolean; }; export type CampaignJobsQuery = Omit; export type CampaignJobsResponse = { jobs: Record[]; page: number; page_size: number; total: number; total_unfiltered: number; pages: number; cursor?: string | null; next_cursor?: string | null; counts: Record>; filtered_counts: Record>; review: { inspection_complete?: boolean; blocking_count?: number; required_count?: number; reviewed_required_count?: number; bulk_acceptable_count?: number; }; }; export type CampaignJobsDeltaResponse = CampaignJobsResponse & { deleted: DeltaDeletedItem[]; watermark?: string | null; has_more: boolean; full: boolean; }; export type CampaignJobDetailResponse = { job: Record; attempts: { smtp?: Record[]; imap?: Record[]; postbox?: Record[]; print?: Record[]; }; }; export type CampaignReportEmailPayload = { to: string[]; version_id?: string; include_jobs?: boolean; attach_jobs_csv?: boolean; attach_report_json?: boolean; dry_run?: boolean; idempotency_key?: string; }; export type AggregateReportCount = { value: number | null; suppressed: boolean; }; export type AggregateReportCampaign = { id: string; name: string; status: string; }; export type AggregateReportCampaignListItem = AggregateReportCampaign & { updated_at: string; }; export type AggregateCampaignReport = { generated_at: string; campaign: AggregateReportCampaign; version_number: number | null; completion_state: "not_started" | "in_progress" | "completed" | "partially_completed" | "incomplete" | "outcome_unknown" | "suppressed"; population: { denominator: AggregateReportCount; denominator_definition: string; inactive_source_entries: AggregateReportCount; excluded_or_blocked_jobs: AggregateReportCount; }; outcomes: { smtp_accepted: AggregateReportCount; postbox_accepted: AggregateReportCount; print_accepted: AggregateReportCount; delivered: AggregateReportCount; partially_accepted: AggregateReportCount; failed: AggregateReportCount; outcome_unknown: AggregateReportCount; queued_or_active: AggregateReportCount; cancelled: AggregateReportCount; excluded: AggregateReportCount; not_attempted: AggregateReportCount; }; time_range: { first_activity_at: string | null; last_activity_at: string | null; suppressed: boolean; }; privacy: { small_cell_threshold: number; suppression_applied: boolean; rule: string; }; }; export async function listCampaigns(settings: ApiSettings): Promise { const response = await apiFetch(settings, "/api/v1/campaigns"); if (Array.isArray(response)) { return response; } return response.campaigns ?? response.items ?? response.results ?? []; } export async function listAggregateReportCampaigns( settings: ApiSettings) : Promise { const response = await apiFetch<{campaigns: AggregateReportCampaignListItem[]}>(settings, "/api/v1/campaigns/aggregate-reports"); return response.campaigns; } export async function listCampaignsDelta( settings: ApiSettings, options: {since?: string | null;limit?: number;} = {}) : Promise { const params = new URLSearchParams(); if (options.since) params.set("since", options.since); if (options.limit) params.set("limit", String(options.limit)); const suffix = params.size > 0 ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/campaigns/delta${suffix}`); } export async function listRecipientImportMappingProfiles(settings: ApiSettings): Promise { const response = await apiFetch(settings, "/api/v1/campaigns/recipient-import/mapping-profiles"); return response.profiles ?? []; } export async function createRecipientImportMappingProfile( settings: ApiSettings, payload: RecipientImportMappingProfilePayload) : Promise { return apiFetch(settings, "/api/v1/campaigns/recipient-import/mapping-profiles", { method: "POST", body: JSON.stringify(payload) }); } export async function updateRecipientImportMappingProfile( settings: ApiSettings, profileId: string, payload: RecipientImportMappingProfilePayload) : Promise { return apiFetch(settings, `/api/v1/campaigns/recipient-import/mapping-profiles/${encodeURIComponent(profileId)}`, { method: "PUT", body: JSON.stringify(payload) }); } export async function deleteRecipientImportMappingProfile(settings: ApiSettings, profileId: string): Promise { await apiFetch(settings, `/api/v1/campaigns/recipient-import/mapping-profiles/${encodeURIComponent(profileId)}`, { method: "DELETE" }); } export async function lookupCampaignAddresses( settings: ApiSettings, campaignId: string, query: string, limit = 25) : Promise { const params = new URLSearchParams({ query, limit: String(limit) }); return apiFetch(settings, `/api/v1/campaigns/${campaignId}/address-lookup?${params.toString()}`); } export async function listCampaignRecipientAddressSources( settings: ApiSettings, campaignId: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/recipient-address-sources`); } export async function listCampaignRecipientDistributionLists( settings: ApiSettings, campaignId: string, query = "") : Promise { const params = new URLSearchParams(); params.set("limit", "250"); if (query.trim()) params.set("query", query.trim()); const suffix = params.size ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/recipient-distribution-lists${suffix}`); } export async function previewCampaignRecipientDistributionList( settings: ApiSettings, campaignId: string, payload: CampaignDistributionListExpansionInput) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/recipient-distribution-lists/preview`, { method: "POST", body: JSON.stringify(payload) }); } export async function snapshotCampaignRecipientDistributionList( settings: ApiSettings, campaignId: string, payload: CampaignDistributionListExpansionInput) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/recipient-distribution-lists/snapshot`, { method: "POST", body: JSON.stringify(payload) }); } export async function getCampaignPostboxCatalog( settings: ApiSettings, campaignId: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/postbox-catalog`); } export async function getCampaignCalendarInvitationCatalog( settings: ApiSettings, campaignId: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/calendar-invitation-catalog`); } export async function listCampaignPrintTemplates( settings: ApiSettings, campaignId: string, query = "") : Promise { const suffix = query.trim() ? `?query=${encodeURIComponent(query.trim())}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/print-templates${suffix}`); } export async function listCampaignContentLibrary( settings: ApiSettings, campaignId: string, query = "") : Promise { const suffix = query.trim() ? `?query=${encodeURIComponent(query.trim())}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/content-library${suffix}`); } export async function saveCampaignContentLibraryItem( settings: ApiSettings, campaignId: string, payload: CampaignContentLibrarySaveInput) : Promise<{template: CampaignContentLibraryItem;}> { return apiFetch<{template: CampaignContentLibraryItem;}>(settings, `/api/v1/campaigns/${campaignId}/content-library`, { method: "POST", body: JSON.stringify(payload) }); } export async function snapshotCampaignRecipientAddressSource( settings: ApiSettings, campaignId: string, sourceId: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/recipient-address-sources/snapshot`, { method: "POST", body: JSON.stringify({ source_id: sourceId, purpose: "campaign_delivery" }) }); } export async function getCampaign(settings: ApiSettings, campaignId: string): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}`); } export async function getCampaignArchiveEncryptionPolicy( settings: ApiSettings, campaignId: string ): Promise { return apiFetch( settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/archive-encryption-policy` ); } export async function updateCampaignMetadata( settings: ApiSettings, campaignId: string, payload: CampaignUpdatePayload) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}`, { method: "PUT", body: JSON.stringify(payload) }); } export async function archiveCampaign( settings: ApiSettings, campaignId: string, expectedStateToken: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/archive`, { method: "POST", body: JSON.stringify({ expected_state_token: expectedStateToken }) }); } export async function getCampaignLifecyclePolicy( settings: ApiSettings, campaignId: string, versionId?: string | null) : Promise { const suffix = versionId ? `?version_id=${encodeURIComponent(versionId)}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/lifecycle-policy${suffix}`); } export async function deleteCampaign( settings: ApiSettings, campaignId: string, expectedStateToken: string) : Promise { await apiFetch(settings, `/api/v1/campaigns/${campaignId}`, { method: "DELETE", body: JSON.stringify({ expected_state_token: expectedStateToken }) }); } export async function copyCampaign( settings: ApiSettings, campaignId: string, sourceVersionId: string, expectedStateToken: string, options: CampaignCopyOptions) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/copies`, { method: "POST", body: JSON.stringify({ source_version_id: sourceVersionId, expected_state_token: expectedStateToken, ...options, name: options.name?.trim() || undefined, external_id: options.external_id?.trim() || undefined }) }); } export async function exportCampaignPackage( settings: ApiSettings, campaignId: string, versionId: string, scopes: CampaignTransferScope[]) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/exports`, { method: "POST", body: JSON.stringify({ scopes }) }); } export async function previewCampaignImport( settings: ApiSettings, payload: { package: Record; selected_scopes?: CampaignTransferScope[] | null; external_id?: string; name?: string; }) : Promise { return apiFetch(settings, "/api/v1/campaign-transfers/imports/preview", { method: "POST", body: JSON.stringify(payload) }); } export async function importCampaignPackage( settings: ApiSettings, payload: { package: Record; selected_scopes: CampaignTransferScope[]; external_id?: string; name?: string; expected_package_sha256: string; }) : Promise { return apiFetch(settings, "/api/v1/campaign-transfers/imports", { method: "POST", body: JSON.stringify(payload) }); } export async function listCampaignSchedules( settings: ApiSettings, campaignId: string) : Promise { const response = await apiFetch<{items: CampaignSchedule[]}>(settings, `/api/v1/campaigns/${campaignId}/schedules`); return response.items; } export async function createCampaignSchedule( settings: ApiSettings, campaignId: string, payload: CampaignScheduleCreate) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/schedules`, { method: "POST", body: JSON.stringify(payload) }); } export async function setCampaignScheduleState( settings: ApiSettings, campaignId: string, scheduleId: string, active: boolean, baseRevision: number) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/schedules/${scheduleId}`, { method: "PATCH", body: JSON.stringify({ active, base_revision: baseRevision }) }); } export async function archiveCampaignVersion( settings: ApiSettings, campaignId: string, versionId: string, expectedStateToken: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/archive`, { method: "POST", body: JSON.stringify({ expected_state_token: expectedStateToken }) }); } export async function createNewCampaign( settings: ApiSettings, overrides: CampaignCreateMinimalPayload = {}) : Promise { const now = new Date(); const stamp = now.toISOString().slice(0, 19).replace(/[-:T]/g, ""); const payload = { external_id: overrides.external_id ?? `new-campaign-${stamp}`, name: overrides.name ?? "i18n:govoplan-campaign.new_campaign.1f0d021c", description: overrides.description ?? "", current_flow: overrides.current_flow ?? "create", current_step: overrides.current_step ?? "basics" }; return apiFetch(settings, "/api/v1/campaigns/new", { method: "POST", body: JSON.stringify(payload) }); } export async function getCampaignSchema(settings: ApiSettings): Promise { return apiFetch(settings, "/api/v1/schemas/campaign"); } export async function getCampaignSchemaUi(settings: ApiSettings): Promise { return apiFetch(settings, "/api/v1/schemas/campaign/ui"); } export async function getCampaignWorkspace( settings: ApiSettings, campaignId: string, options: CampaignWorkspaceQuery = {}) : Promise { const params = new URLSearchParams(); if (options.versionId) params.set("version_id", options.versionId); if (options.includeCurrentVersion !== undefined) params.set("include_current_version", String(options.includeCurrentVersion)); if (options.includeSummary !== undefined) params.set("include_summary", String(options.includeSummary)); if (options.includeVersions !== undefined) params.set("include_versions", String(options.includeVersions)); const suffix = params.size > 0 ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/workspace${suffix}`); } export async function getCampaignWorkspaceDelta( settings: ApiSettings, campaignId: string, options: CampaignWorkspaceQuery = {}, requestOptions?: Pick) : Promise { const params = new URLSearchParams(); if (options.versionId) params.set("version_id", options.versionId); if (options.includeCurrentVersion !== undefined) params.set("include_current_version", String(options.includeCurrentVersion)); if (options.includeSummary !== undefined) params.set("include_summary", String(options.includeSummary)); if (options.includeVersions !== undefined) params.set("include_versions", String(options.includeVersions)); if (options.since) params.set("since", options.since); if (options.limit) params.set("limit", String(options.limit)); const suffix = params.size > 0 ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/workspace/delta${suffix}`, requestOptions); } export async function listCampaignVersions( settings: ApiSettings, campaignId: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions`); } export async function getCampaignVersion( settings: ApiSettings, campaignId: string, versionId: string) : Promise { // Conflict reconciliation and discard/reload require the current revision, // never an older coalesced or short-lived cached read. return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}`, { cache: "no-store" }); } export async function unlockCampaignVersionValidation( settings: ApiSettings, campaignId: string, versionId: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/unlock-validation`, { method: "POST" }); } export async function lockCampaignVersionTemporarily( settings: ApiSettings, campaignId: string, versionId: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/lock-temporarily`, { method: "POST" }); } export async function unlockCampaignVersionUserLock( settings: ApiSettings, campaignId: string, versionId: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/unlock-user-lock`, { method: "POST" }); } export async function lockCampaignVersionPermanently( settings: ApiSettings, campaignId: string, versionId: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/lock-permanently`, { method: "POST" }); } export async function updateCampaignVersion( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignVersionUpdatePayload, ifMatch: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}`, { method: "PUT", headers: { "If-Match": ifMatch }, body: JSON.stringify(campaignVersionUpdateForRequest(payload)) }); } export async function forkCampaignVersion( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignVersionUpdatePayload = {}) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/fork`, { method: "POST", body: JSON.stringify(campaignVersionUpdateForRequest(payload)) }); } export async function autosaveCampaignVersion( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignVersionUpdatePayload, ifMatch: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/autosave`, { method: "POST", headers: { "If-Match": ifMatch }, body: JSON.stringify(campaignVersionUpdateForRequest(payload)) }); } export async function setCampaignVersionStep( settings: ApiSettings, campaignId: string, versionId: string, currentStep: string, currentFlow?: string | null) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/set-step`, { method: "POST", body: JSON.stringify({ current_flow: currentFlow, current_step: currentStep }) }); } export async function validatePartial( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignPartialValidationPayload = {}) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/validate-partial`, { method: "POST", body: JSON.stringify(payload) }); } export async function publishCampaignVersion( settings: ApiSettings, campaignId: string, versionId: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/publish`, { method: "POST" }); } export async function validateVersion( settings: ApiSettings, versionId: string, checkFiles = false, linkUnsharedMatches = false) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/versions/${versionId}/validate`, { method: "POST", body: JSON.stringify({ check_files: checkFiles, link_unshared_matches: linkUnsharedMatches }) }); } export async function buildVersion( settings: ApiSettings, versionId: string, writeEml = true) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/versions/${versionId}/build`, { method: "POST", body: JSON.stringify({ write_eml: writeEml, idempotency_key: crypto.randomUUID() }) }); } export function previewCampaignAttachments( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignAttachmentPreviewPayload = {}) : Promise { return apiFetch( settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/attachments/preview`, { method: "POST", body: JSON.stringify(payload) } ); } export function linkCampaignAttachmentMatches( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignAttachmentLinkMatchesPayload = {}) : Promise { return apiFetch( settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/attachments/link-matches`, { method: "POST", body: JSON.stringify(payload) } ); } export async function getCampaignSummary( settings: ApiSettings, campaignId: string, versionId?: string) : Promise { const suffix = versionId ? `?version_id=${encodeURIComponent(versionId)}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/summary${suffix}`); } export type CampaignDeliveryProgress = { delivery_mode?: string | null; workflow_state?: string; campaign_id: string; version_id: string; generated_at: string; total_jobs: number; smtp: { total: number; processed: number; accepted: number; active: number; pending: number; failed: number; outcome_unknown: number; excluded: number; paused: number; cancelled: number }; imap: { total: number; processed: number; appended: number; active: number; pending: number; failed: number; outcome_unknown: number; excluded: number }; status_counts: { send: Record; queue: Record; imap: Record }; }; export async function getCampaignDeliveryProgress( settings: ApiSettings, campaignId: string, versionId: string, signal?: AbortSignal ): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/delivery-progress?version_id=${encodeURIComponent(versionId)}`, { cache: "no-store", signal }); } export async function getCampaignJobs( settings: ApiSettings, campaignId: string, options: CampaignJobsQuery = {}, init?: Pick) : Promise { const params = campaignJobsQueryParams(options); const suffix = params.size > 0 ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/jobs${suffix}`, init); } export async function getCampaignJobsDelta( settings: ApiSettings, campaignId: string, options: CampaignJobsQuery & {since?: string | null;limit?: number;} = {}, init?: Pick) : Promise { const params = campaignJobsQueryParams(options); const suffix = params.size > 0 ? `?${params.toString()}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/jobs/delta${suffix}`, init); } export async function getCampaignJobDetail( settings: ApiSettings, campaignId: string, jobId: string) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/jobs/${jobId}`); } export async function getCampaignReport( settings: ApiSettings, campaignId: string, versionId?: string) : Promise { const params = new URLSearchParams({ include_jobs: "false" }); if (versionId) params.set("version_id", versionId); return apiFetch(settings, `/api/v1/campaigns/${campaignId}/report?${params.toString()}`); } export async function getAggregateCampaignReport( settings: ApiSettings, campaignId: string) : Promise { return apiFetch( settings, `/api/v1/campaigns/aggregate-reports/${encodeURIComponent(campaignId)}` ); } export async function downloadCampaignJobsCsv( settings: ApiSettings, campaignId: string, versionId?: string) : Promise { const suffix = versionId ? `?version_id=${encodeURIComponent(versionId)}` : ""; await apiDownload( settings, `/api/v1/campaigns/${campaignId}/report/jobs.csv${suffix}`, `campaign-${campaignId}${versionId ? `-${versionId}` : ""}-jobs.csv` ); } export async function downloadCampaignPrintArtifact( settings: ApiSettings, downloadPath: string, filename: string) : Promise { if (![ "/api/v1/campaigns/", "/api/v1/files/" ].some((prefix) => downloadPath.startsWith(prefix))) { throw new Error("The printable output download path is invalid."); } await apiDownload(settings, downloadPath, filename || "campaign-print-output.html"); } export async function emailCampaignReport( settings: ApiSettings, campaignId: string, payload: CampaignReportEmailPayload) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/report/email`, { method: "POST", body: JSON.stringify(payload) }); } export async function retryCampaignJobs( settings: ApiSettings, campaignId: string, payload: Record = {}) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/jobs/retry`, { method: "POST", body: JSON.stringify(payload) }); } export async function sendUnattemptedCampaignJobs( settings: ApiSettings, campaignId: string, payload: Record = {}) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/jobs/send-unattempted`, { method: "POST", body: JSON.stringify(payload) }); } export async function sendCampaignJob( settings: ApiSettings, campaignId: string, jobId: string, payload: CampaignSendJobPayload) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/jobs/${jobId}/send`, { method: "POST", body: JSON.stringify(payload) }); } export async function resolveCampaignJobOutcome( settings: ApiSettings, campaignId: string, jobId: string, decision: "smtp_accepted" | "not_sent" | "imap_appended" | "imap_not_appended" | "postbox_accepted" | "postbox_not_accepted", note?: string, attemptId?: string) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/jobs/${jobId}/resolve-outcome`, { method: "POST", body: JSON.stringify({ decision, note: note || null, attempt_id: attemptId || null }) }); } export async function updateCampaignReviewState( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignReviewStatePayload) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/review-state`, { method: "POST", body: JSON.stringify(payload) }); } export async function queueCampaign( settings: ApiSettings, campaignId: string, payload: CampaignQueuePayload = {}) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/queue`, { method: "POST", body: JSON.stringify(payload) }); } export async function getCampaignDeliveryOptions( settings: ApiSettings, campaignId: string, versionId?: string | null) : Promise { const query = versionId ? `?version_id=${encodeURIComponent(versionId)}` : ""; return apiFetch(settings, `/api/v1/campaigns/${campaignId}/delivery-options${query}`); } export async function requestCampaignApproval( settings: ApiSettings, campaignId: string, versionId: string, payload: CampaignApprovalRequestPayload) : Promise> { return apiFetch>( settings, `/api/v1/campaigns/${campaignId}/versions/${versionId}/approval-request`, { method: "POST", body: JSON.stringify(payload) } ); } export async function sendCampaignNow( settings: ApiSettings, campaignId: string, payload: CampaignSendNowPayload = {}) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/send-now`, { method: "POST", body: JSON.stringify(payload) }); } export async function mockSendCampaign( settings: ApiSettings, campaignId: string, payload: CampaignMockSendPayload = {}) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/mock-send`, { method: "POST", body: JSON.stringify(payload) }); } export async function pauseCampaign(settings: ApiSettings, campaignId: string): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/pause`, { method: "POST" }); } export async function resumeCampaign(settings: ApiSettings, campaignId: string): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/resume`, { method: "POST" }); } export async function cancelCampaign(settings: ApiSettings, campaignId: string): Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/cancel`, { method: "POST" }); } export async function appendSent( settings: ApiSettings, campaignId: string, payload: CampaignAppendSentPayload = {}) : Promise> { return apiFetch>(settings, `/api/v1/campaigns/${campaignId}/append-sent`, { method: "POST", body: JSON.stringify({ version_id: payload.version_id, dry_run: payload.dry_run ?? false, enqueue_celery: payload.enqueue_celery ?? true, run_inline: payload.run_inline ?? false }) }); } export async function getCampaignShareTargets(settings: ApiSettings, campaignId: string): Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/share-targets`); } export async function listCampaignCollaboration( settings: ApiSettings, campaignId: string, options: {cursor?: string | null;limit?: number;} = {} ): Promise { const params = new URLSearchParams({ limit: String(options.limit ?? 25) }); if (options.cursor) params.set("cursor", options.cursor); return apiFetch( settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/collaboration?${params.toString()}` ); } export async function createCampaignCollaborationEntry( settings: ApiSettings, campaignId: string, payload: CampaignCollaborationCreate ): Promise { return apiFetch( settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/collaboration`, { method: "POST", body: JSON.stringify(payload) } ); } export async function withdrawCampaignCollaborationEntry( settings: ApiSettings, campaignId: string, entryId: string ): Promise { return apiFetch( settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/collaboration/${encodeURIComponent(entryId)}/withdraw`, { method: "POST", body: JSON.stringify({}) } ); } export async function redactCampaignCollaborationEntry( settings: ApiSettings, campaignId: string, entryId: string ): Promise { return apiFetch( settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/collaboration/${encodeURIComponent(entryId)}/redact`, { method: "POST", body: JSON.stringify({}) } ); } export function campaignCollaborationMentionProvider( settings: ApiSettings, campaignId: string ): ReferenceOptionProvider { return apiReferenceOptionProvider( settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/collaboration/mention-options` ); } export async function listCampaignWorkAssignments( settings: ApiSettings, campaignId: string, options: { cursor?: string | null; limit?: number; statuses?: CampaignWorkAssignmentStatus[] } = {} ): Promise { const params = new URLSearchParams({ limit: String(options.limit ?? 50) }); if (options.cursor) params.set("cursor", options.cursor); for (const status of options.statuses ?? []) params.append("assignment_status", status); return apiFetch( settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments?${params.toString()}` ); } export async function createCampaignWorkAssignment( settings: ApiSettings, campaignId: string, payload: { purpose: string; assignee: { type: CampaignWorkAssigneeType; id: string }; due_at?: string | null; reference?: Pick | null; mirror_to_tasks?: boolean; } ): Promise { return apiFetch( settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments`, { method: "POST", body: JSON.stringify(payload) } ); } export async function reassignCampaignWorkAssignment( settings: ApiSettings, campaignId: string, assignmentId: string, payload: { expected_revision: number; assignee: { type: CampaignWorkAssigneeType; id: string }; reason?: string | null; mirror_to_tasks?: boolean; } ): Promise { return apiFetch( settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments/${encodeURIComponent(assignmentId)}/reassign`, { method: "POST", body: JSON.stringify(payload) } ); } export async function transitionCampaignWorkAssignment( settings: ApiSettings, campaignId: string, assignment: Pick, action: "accept" | "start" | "complete" | "reject" | "cancel" ): Promise { return apiFetch( settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments/${encodeURIComponent(assignment.id)}/transition`, { method: "POST", body: JSON.stringify({ expected_revision: assignment.resource_revision, action }) } ); } export async function listCampaignWorkAssignmentHistory( settings: ApiSettings, campaignId: string, assignmentId: string, cursor?: string | null ): Promise { const params = new URLSearchParams({ limit: "50" }); if (cursor) params.set("cursor", cursor); return apiFetch( settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments/${encodeURIComponent(assignmentId)}/history?${params.toString()}` ); } export async function reconcileCampaignWorkAssignments( settings: ApiSettings, campaignId: string ): Promise<{ checked: number; changed: number; assignments: CampaignWorkAssignment[] }> { return apiFetch( settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments/reconcile`, { method: "POST" } ); } export function campaignWorkAssignmentTargetProvider( settings: ApiSettings, campaignId: string, assigneeType: CampaignWorkAssigneeType ): ReferenceOptionProvider { return apiReferenceOptionProvider( settings, `/api/v1/campaigns/${encodeURIComponent(campaignId)}/assignments/options`, { assignee_type: assigneeType } ); } export function campaignShareTargetProvider( settings: ApiSettings, campaignId: string, targetType: "user" | "group" ): ReferenceOptionProvider { return apiReferenceOptionProvider( settings, `/api/v1/campaigns/${campaignId}/share-target-options`, { target_type: targetType } ); } export async function getCampaignShares(settings: ApiSettings, campaignId: string): Promise { const pageSize = 500; const shares: CampaignShare[] = []; for (let page = 1; ; page += 1) { const response = await apiFetch<{shares: CampaignShare[];pages?: number;}>(settings, `/api/v1/campaigns/${campaignId}/shares?page=${page}&page_size=${pageSize}`); shares.push(...response.shares); if (page >= (response.pages ?? 1)) { return shares; } } } export async function updateCampaignOwner( settings: ApiSettings, campaignId: string, payload: {owner_user_id?: string | null;owner_group_id?: string | null;}) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/owner`, { method: "PUT", body: JSON.stringify(payload) }); } export async function upsertCampaignShare( settings: ApiSettings, campaignId: string, payload: {target_type: "user" | "group";target_id: string;permission: "read" | "write";}) : Promise { return apiFetch(settings, `/api/v1/campaigns/${campaignId}/shares`, { method: "POST", body: JSON.stringify(payload) }); } export async function revokeCampaignShare(settings: ApiSettings, campaignId: string, shareId: string): Promise { await apiFetch(settings, `/api/v1/campaigns/${campaignId}/shares/${shareId}`, { method: "DELETE" }); }