@graph8/sdk 0.373.0 → 0.374.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/README.md +3 -3
  2. package/blocks/_foundation/README.md +47 -0
  3. package/blocks/_foundation/block.json +13 -0
  4. package/blocks/_foundation/components/auth-gate.tsx +363 -0
  5. package/blocks/_foundation/server/auth.ts +815 -0
  6. package/blocks/campaigns/README.md +1 -1
  7. package/blocks/campaigns/block.json +34 -1
  8. package/blocks/campaigns/components/campaign-links.tsx +180 -0
  9. package/blocks/company-intelligence/components/research-run.tsx +6 -2
  10. package/blocks/crm/README.md +19 -0
  11. package/blocks/crm/block.json +205 -6
  12. package/blocks/crm/components/bulk-update.tsx +119 -0
  13. package/blocks/crm/components/contact-documents-tab.tsx +144 -0
  14. package/blocks/crm/components/contact-insights-tab.tsx +118 -0
  15. package/blocks/crm/components/contact-lookup-link.tsx +47 -6
  16. package/blocks/crm/components/contact-row-actions.tsx +43 -0
  17. package/blocks/crm/components/contact-tag-chips.tsx +113 -0
  18. package/blocks/crm/components/contacts-screen.tsx +46 -5
  19. package/blocks/crm/components/owner-assign.tsx +132 -0
  20. package/blocks/crm/components/record-open-links.tsx +79 -0
  21. package/blocks/crm/components/tag-assign.tsx +112 -0
  22. package/blocks/crm/components/tag-manager.tsx +178 -0
  23. package/blocks/crm/components/unlock-info-button.tsx +82 -0
  24. package/blocks/crm/components/verify-email-button.tsx +93 -0
  25. package/blocks/crm/hooks/use-contact-tags.ts +29 -0
  26. package/blocks/crm/lib/record-actions.ts +102 -0
  27. package/blocks/deals/README.md +1 -0
  28. package/blocks/deals/block.json +17 -0
  29. package/blocks/deals/components/create-deal-button.tsx +109 -0
  30. package/blocks/deals/components/deal-detail.tsx +5 -3
  31. package/blocks/dialer/components/call-log-screen.tsx +6 -1
  32. package/blocks/documents/README.md +86 -0
  33. package/blocks/documents/block.json +378 -0
  34. package/blocks/documents/components/account-bar.tsx +48 -0
  35. package/blocks/documents/components/correspondence-form.tsx +95 -0
  36. package/blocks/documents/components/correspondence-import.tsx +106 -0
  37. package/blocks/documents/components/correspondence-panel.tsx +191 -0
  38. package/blocks/documents/components/documents-screen.tsx +76 -0
  39. package/blocks/documents/components/library-document-detail.tsx +175 -0
  40. package/blocks/documents/components/library-document-form.tsx +105 -0
  41. package/blocks/documents/components/library-folder-form.tsx +74 -0
  42. package/blocks/documents/components/library-panel.tsx +235 -0
  43. package/blocks/documents/components/library-upload.tsx +160 -0
  44. package/blocks/documents/components/question-row.tsx +130 -0
  45. package/blocks/documents/components/questionnaire-detail.tsx +197 -0
  46. package/blocks/documents/components/questionnaires-panel.tsx +173 -0
  47. package/blocks/documents/components/record-documents-tab.tsx +109 -0
  48. package/blocks/documents/lib/documents-types.ts +252 -0
  49. package/blocks/documents/lib/download.ts +64 -0
  50. package/blocks/documents/recipes/follow-a-questionnaire.ts +26 -0
  51. package/blocks/documents/recipes/log-account-correspondence.ts +27 -0
  52. package/blocks/documents/recipes/organize-the-library.ts +25 -0
  53. package/blocks/email-signatures/README.md +123 -0
  54. package/blocks/email-signatures/block.json +341 -0
  55. package/blocks/email-signatures/components/email-signatures-screen.tsx +166 -0
  56. package/blocks/email-signatures/components/signature-assign-template.tsx +119 -0
  57. package/blocks/email-signatures/components/signature-block-editor.tsx +87 -0
  58. package/blocks/email-signatures/components/signature-campaign-form.tsx +165 -0
  59. package/blocks/email-signatures/components/signature-campaigns.tsx +75 -0
  60. package/blocks/email-signatures/components/signature-identity-form.tsx +131 -0
  61. package/blocks/email-signatures/components/signature-mailboxes.tsx +195 -0
  62. package/blocks/email-signatures/components/signature-policy.tsx +130 -0
  63. package/blocks/email-signatures/components/signature-preview.tsx +119 -0
  64. package/blocks/email-signatures/components/signature-report.tsx +77 -0
  65. package/blocks/email-signatures/components/signature-template-details.tsx +112 -0
  66. package/blocks/email-signatures/components/signature-template-form.tsx +109 -0
  67. package/blocks/email-signatures/hooks/use-signatures-data.ts +93 -0
  68. package/blocks/email-signatures/lib/signatures-types.ts +392 -0
  69. package/blocks/email-signatures/recipes/assign-a-signature-to-a-new-hire.ts +32 -0
  70. package/blocks/email-signatures/recipes/create-a-template-and-make-it-default.ts +35 -0
  71. package/blocks/email-signatures/recipes/signature-engagement-report.ts +16 -0
  72. package/blocks/enrichment/README.md +22 -4
  73. package/blocks/enrichment/block.json +145 -5
  74. package/blocks/enrichment/components/auto-enrichment-configs.tsx +134 -0
  75. package/blocks/enrichment/components/auto-enrichment-settings.tsx +82 -0
  76. package/blocks/enrichment/components/bulk-enrich-panel.tsx +7 -7
  77. package/blocks/enrichment/components/enrichment-activity.tsx +93 -0
  78. package/blocks/enrichment/components/enrichment-job-detail.tsx +18 -0
  79. package/blocks/enrichment/components/enrichment-screen.tsx +34 -3
  80. package/blocks/enrichment/components/list-pipelines.tsx +97 -0
  81. package/blocks/enrichment/components/pipeline-detail.tsx +214 -0
  82. package/blocks/enrichment/components/pipeline-editor.tsx +144 -0
  83. package/blocks/enrichment/components/pipeline-templates.tsx +90 -0
  84. package/blocks/enrichment/lib/enrichment-types.ts +12 -0
  85. package/blocks/enrichment/lib/pipeline-types.ts +234 -0
  86. package/blocks/forms/README.md +2 -0
  87. package/blocks/forms/components/forms-screen.tsx +6 -3
  88. package/blocks/inbox/README.md +40 -4
  89. package/blocks/inbox/block.json +130 -8
  90. package/blocks/inbox/components/blocked-senders.tsx +103 -0
  91. package/blocks/inbox/components/compose-message.tsx +193 -0
  92. package/blocks/inbox/components/inbox-screen.tsx +54 -6
  93. package/blocks/inbox/components/inbox-search.tsx +106 -0
  94. package/blocks/inbox/components/tag-manager.tsx +136 -0
  95. package/blocks/inbox/components/thread-actions.tsx +133 -0
  96. package/blocks/inbox/components/thread-view.tsx +2 -0
  97. package/blocks/inbox/hooks/use-inbox-mailboxes.ts +31 -0
  98. package/blocks/inbox/lib/inbox-manage.ts +227 -0
  99. package/blocks/newsletter/components/newsletter-screen.tsx +3 -1
  100. package/blocks/pages/block.json +10 -1
  101. package/blocks/pages/hooks/use-landing-page-paths.ts +60 -0
  102. package/blocks/programmatic-pages/README.md +82 -0
  103. package/blocks/programmatic-pages/block.json +292 -0
  104. package/blocks/programmatic-pages/components/programmatic-campaign-form.tsx +136 -0
  105. package/blocks/programmatic-pages/components/programmatic-generate.tsx +112 -0
  106. package/blocks/programmatic-pages/components/programmatic-generated-pages.tsx +210 -0
  107. package/blocks/programmatic-pages/components/programmatic-overview.tsx +84 -0
  108. package/blocks/programmatic-pages/components/programmatic-pages-screen.tsx +221 -0
  109. package/blocks/programmatic-pages/components/programmatic-preview.tsx +66 -0
  110. package/blocks/programmatic-pages/components/programmatic-publish.tsx +102 -0
  111. package/blocks/programmatic-pages/components/programmatic-settings.tsx +96 -0
  112. package/blocks/programmatic-pages/components/programmatic-source-fields.tsx +75 -0
  113. package/blocks/programmatic-pages/components/programmatic-source.tsx +106 -0
  114. package/blocks/programmatic-pages/components/programmatic-variables.tsx +84 -0
  115. package/blocks/programmatic-pages/hooks/use-programmatic-workspace.ts +19 -0
  116. package/blocks/programmatic-pages/lib/programmatic-types.ts +330 -0
  117. package/blocks/programmatic-pages/recipes/create-a-programmatic-campaign.ts +23 -0
  118. package/blocks/programmatic-pages/recipes/render-and-follow.ts +21 -0
  119. package/blocks/programmatic-pages/recipes/review-then-publish.ts +17 -0
  120. package/blocks/quotes/README.md +25 -1
  121. package/blocks/quotes/block.json +123 -6
  122. package/blocks/quotes/components/manual-products.tsx +144 -0
  123. package/blocks/quotes/components/quote-batch-actions.tsx +86 -0
  124. package/blocks/quotes/components/quote-documents.tsx +94 -0
  125. package/blocks/quotes/components/quote-evidence.tsx +95 -0
  126. package/blocks/quotes/components/quote-list.tsx +23 -5
  127. package/blocks/quotes/components/quote-settings.tsx +184 -0
  128. package/blocks/quotes/components/quote-templates.tsx +189 -0
  129. package/blocks/quotes/components/quote-viewer.tsx +16 -3
  130. package/blocks/quotes/components/quotes-screen.tsx +15 -5
  131. package/blocks/quotes/lib/quote-admin.ts +264 -0
  132. package/blocks/sequencer/README.md +55 -0
  133. package/blocks/sequencer/block.json +172 -5
  134. package/blocks/sequencer/components/enrollment-withdraw.tsx +71 -0
  135. package/blocks/sequencer/components/sequence-builder.tsx +13 -3
  136. package/blocks/sequencer/components/sequence-campaign-link.tsx +78 -0
  137. package/blocks/sequencer/components/sequence-detail.tsx +6 -0
  138. package/blocks/sequencer/components/sequence-edit-form.tsx +111 -0
  139. package/blocks/sequencer/components/sequence-enrollments.tsx +2 -1
  140. package/blocks/sequencer/components/sequence-manage-actions.tsx +119 -0
  141. package/blocks/sequencer/components/sequence-senders-panel.tsx +84 -0
  142. package/blocks/sequencer/components/sequence-templates.tsx +160 -0
  143. package/blocks/sequencer/components/sequences-screen.tsx +36 -6
  144. package/blocks/sequencer/components/step-ai-assist.tsx +196 -0
  145. package/blocks/sequencer/components/step-editor.tsx +10 -1
  146. package/blocks/sequencer/components/step-templates.tsx +224 -0
  147. package/blocks/sequencer/lib/sequencer-manage.ts +235 -0
  148. package/blocks/sequencer/lib/sequencer-utils.ts +4 -0
  149. package/blocks/signals/README.md +39 -0
  150. package/blocks/signals/block.json +287 -0
  151. package/blocks/signals/components/auto-seed-panel.tsx +111 -0
  152. package/blocks/signals/components/listener-form.tsx +184 -0
  153. package/blocks/signals/components/matched-posts.tsx +195 -0
  154. package/blocks/signals/components/post-engagements.tsx +114 -0
  155. package/blocks/signals/components/record-social-tab.tsx +49 -0
  156. package/blocks/signals/components/social-listeners-screen.tsx +250 -0
  157. package/blocks/signals/components/social-settings.tsx +128 -0
  158. package/blocks/signals/components/source-lists.tsx +83 -0
  159. package/blocks/signals/components/top-engagers.tsx +68 -0
  160. package/blocks/signals/components/x-integration.tsx +129 -0
  161. package/blocks/signals/lib/signals-types.ts +228 -0
  162. package/blocks/signals/recipes/preview-and-create-a-listener.ts +16 -0
  163. package/blocks/signals/recipes/spend-against-caps.ts +14 -0
  164. package/blocks/signals/recipes/triage-matched-posts.ts +18 -0
  165. package/blocks/visitors/components/traffic-chart.tsx +3 -2
  166. package/blocks/web-analytics/README.md +102 -0
  167. package/blocks/web-analytics/block.json +292 -0
  168. package/blocks/web-analytics/components/acquisition-tab.tsx +154 -0
  169. package/blocks/web-analytics/components/analytics-filters.tsx +161 -0
  170. package/blocks/web-analytics/components/audience-tab.tsx +135 -0
  171. package/blocks/web-analytics/components/conversions-tab.tsx +128 -0
  172. package/blocks/web-analytics/components/engagement-tab.tsx +123 -0
  173. package/blocks/web-analytics/components/overview-tab.tsx +102 -0
  174. package/blocks/web-analytics/components/pages-tab.tsx +148 -0
  175. package/blocks/web-analytics/components/ranked-table.tsx +103 -0
  176. package/blocks/web-analytics/components/realtime-tab.tsx +103 -0
  177. package/blocks/web-analytics/components/report-card.tsx +71 -0
  178. package/blocks/web-analytics/components/web-analytics-screen.tsx +90 -0
  179. package/blocks/web-analytics/hooks/use-report.ts +48 -0
  180. package/blocks/web-analytics/lib/analytics-scope.ts +155 -0
  181. package/blocks/web-analytics/lib/page-path.ts +14 -0
  182. package/blocks/web-analytics/lib/web-analytics-types.ts +187 -0
  183. package/blocks/web-analytics/recipes/best-landing-pages.ts +26 -0
  184. package/blocks/web-analytics/recipes/form-conversion-check.ts +19 -0
  185. package/blocks/web-analytics/recipes/weekly-traffic-report.ts +37 -0
  186. package/blocks/workbench/README.md +54 -2
  187. package/blocks/workbench/block.json +234 -5
  188. package/blocks/workbench/components/workbench-auto-enrich.tsx +88 -0
  189. package/blocks/workbench/components/workbench-automation.tsx +36 -0
  190. package/blocks/workbench/components/workbench-connections.tsx +136 -0
  191. package/blocks/workbench/components/workbench-crm-import.tsx +91 -0
  192. package/blocks/workbench/components/workbench-datafile-import.tsx +154 -0
  193. package/blocks/workbench/components/workbench-grid.tsx +70 -7
  194. package/blocks/workbench/components/workbench-pipelines.tsx +173 -0
  195. package/blocks/workbench/components/workbench-row-actions.tsx +8 -1
  196. package/blocks/workbench/components/workbench-schedule.tsx +191 -0
  197. package/blocks/workbench/components/workbench-screen.tsx +50 -17
  198. package/blocks/workbench/components/workbench-send-to-sequence.tsx +116 -0
  199. package/blocks/workbench/components/workbench-table-settings.tsx +53 -0
  200. package/blocks/workbench/components/workbench-triggers.tsx +178 -0
  201. package/blocks/workbench/hooks/use-connected-crms.ts +29 -0
  202. package/blocks/workbench/lib/api-status.ts +8 -0
  203. package/blocks/workbench/lib/automation-types.ts +257 -0
  204. package/dist/cli.js +257 -51
  205. package/dist/index.browser.js +1 -1
  206. package/dist/index.browser.js.map +1 -1
  207. package/dist/index.browser.mjs +1 -1
  208. package/dist/index.browser.mjs.map +1 -1
  209. package/dist/index.js +1 -1
  210. package/dist/index.js.map +1 -1
  211. package/dist/index.mjs +1 -1
  212. package/dist/index.mjs.map +1 -1
  213. package/dist/react.browser.js +1 -1
  214. package/dist/react.browser.js.map +1 -1
  215. package/dist/react.browser.mjs +1 -1
  216. package/dist/react.browser.mjs.map +1 -1
  217. package/dist/react.js +1 -1
  218. package/dist/react.js.map +1 -1
  219. package/dist/react.mjs +1 -1
  220. package/dist/react.mjs.map +1 -1
  221. package/dist/types/blocks/auth.d.ts +45 -0
  222. package/dist/types/blocks/create.d.ts +5 -2
  223. package/dist/types/version.d.ts +2 -2
  224. package/docs/api/forms.md +1 -1
  225. package/package.json +1 -1
  226. package/stacks/6sense/pages/contacts.tsx.tmpl +2 -0
  227. package/stacks/6sense.json +20 -0
  228. package/stacks/apollo/pages/calls.tsx.tmpl +9 -9
  229. package/stacks/apollo/pages/campaigns.tsx.tmpl +27 -0
  230. package/stacks/apollo/pages/contacts.tsx.tmpl +6 -0
  231. package/stacks/apollo/pages/inbox-contact.tsx.tmpl +3 -3
  232. package/stacks/apollo/pages/lists.tsx.tmpl +14 -2
  233. package/stacks/apollo/pages/sequences.tsx.tmpl +8 -2
  234. package/stacks/apollo.json +33 -2
  235. package/stacks/clay/pages/contacts.tsx.tmpl +2 -0
  236. package/stacks/clay/pages/lists.tsx.tmpl +14 -2
  237. package/stacks/gong/pages/calls.tsx.tmpl +9 -9
  238. package/stacks/gong/pages/contacts.tsx.tmpl +2 -0
  239. package/stacks/hootsuite/pages/contacts.tsx.tmpl +2 -0
  240. package/stacks/hootsuite.json +10 -0
  241. package/stacks/hubspot/pages/campaigns.tsx.tmpl +30 -0
  242. package/stacks/hubspot/pages/companies.tsx.tmpl +2 -0
  243. package/stacks/hubspot/pages/contacts.tsx.tmpl +6 -0
  244. package/stacks/hubspot/pages/deals.tsx.tmpl +4 -0
  245. package/stacks/hubspot/pages/forms.tsx.tmpl +36 -0
  246. package/stacks/hubspot/pages/inbox.tsx.tmpl +2 -2
  247. package/stacks/hubspot/pages/sequences.tsx.tmpl +8 -2
  248. package/stacks/hubspot/pages/web-analytics.tsx.tmpl +14 -0
  249. package/stacks/hubspot.json +45 -2
  250. package/stacks/intercom/pages/inbox-contact.tsx.tmpl +3 -3
  251. package/stacks/intercom.json +10 -0
  252. package/stacks/mailchimp/pages/contacts.tsx.tmpl +2 -0
  253. package/stacks/mailchimp/pages/forms.tsx.tmpl +36 -0
  254. package/stacks/mailchimp.json +2 -1
  255. package/stacks/outreach/pages/calls.tsx.tmpl +9 -9
  256. package/stacks/outreach/pages/campaigns.tsx.tmpl +27 -0
  257. package/stacks/outreach/pages/contacts.tsx.tmpl +2 -0
  258. package/stacks/outreach/pages/inbox-contact.tsx.tmpl +3 -3
  259. package/stacks/outreach/pages/sequences.tsx.tmpl +8 -2
  260. package/stacks/outreach.json +13 -2
  261. package/stacks/pipedrive/pages/contacts.tsx.tmpl +2 -0
  262. package/stacks/pipedrive/pages/inbox.tsx.tmpl +2 -2
  263. package/stacks/pipedrive.json +10 -0
  264. package/stacks/salesforce/pages/companies.tsx.tmpl +2 -0
  265. package/stacks/salesforce/pages/contacts.tsx.tmpl +6 -0
  266. package/stacks/salesforce/pages/deals.tsx.tmpl +4 -0
  267. package/stacks/salesforce.json +10 -0
  268. package/stacks/zoominfo/pages/contacts.tsx.tmpl +4 -0
  269. package/stacks/zoominfo/pages/lists.tsx.tmpl +14 -2
  270. package/stacks/zoominfo.json +20 -0
  271. package/styles/styles.css +1 -1
  272. package/templates/auth/auth-route.ts.tmpl +16 -0
  273. package/templates/auth/g8.auth.anonymous.ts.tmpl +15 -0
  274. package/templates/auth/g8.auth.basic.ts.tmpl +17 -0
  275. package/templates/auth/g8.auth.jwt.ts.tmpl +23 -0
  276. package/templates/auth/g8.auth.password.ts.tmpl +18 -0
  277. package/templates/auth/g8.auth.session.ts.tmpl +23 -0
  278. package/templates/auth/middleware.ts.tmpl +14 -0
  279. package/templates/next-app/README.md.tmpl +5 -0
@@ -0,0 +1,392 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Types, vocabularies and pure helpers for the email-signatures block.
5
+ *
6
+ * The vocabularies (block kinds, layouts, fonts, brand sources, policy modes, campaign statuses, source choices) are the
7
+ * contract's exact enums (signatures/domain/models.py), so a body built from them is accepted live. Read payloads stay
8
+ * open-shaped: a stored template or policy can carry keys a current strict model would reject, so every field is optional
9
+ * and a missing value is shown as unknown, never guessed.
10
+ * SDK operations: none.
11
+ */
12
+
13
+ export const FONTS = ["Arial", "Verdana", "Georgia", "Tahoma"] as const;
14
+ export type SignatureFont = (typeof FONTS)[number];
15
+
16
+ export const LAYOUTS = [
17
+ { value: "classic-stacked", label: "Classic, stacked" },
18
+ { value: "photo-left", label: "Photo on the left" },
19
+ { value: "logo-left", label: "Logo on the left" },
20
+ { value: "two-column", label: "Two columns" },
21
+ { value: "compact-inline", label: "Compact, one line" },
22
+ { value: "banner-hero", label: "Banner on top" },
23
+ ] as const;
24
+ export type SignatureLayout = (typeof LAYOUTS)[number]["value"];
25
+
26
+ export const BRAND_SOURCES = [
27
+ { value: "brand_kit", label: "Use the brand kit colour and font when there is one" },
28
+ { value: "custom", label: "Always use the colour and font set here" },
29
+ ] as const;
30
+ export type BrandSource = (typeof BRAND_SOURCES)[number]["value"];
31
+
32
+ export const BLOCK_KINDS = [
33
+ { value: "identity", label: "Name and title", hint: "Who is signing. Every template needs one required, locked block of this kind." },
34
+ { value: "contact", label: "Contact details", hint: "Phone, email, address." },
35
+ { value: "photo", label: "Photo", hint: "A picture of the sender (an image address)." },
36
+ { value: "logo", label: "Logo", hint: "The company logo (an image address)." },
37
+ { value: "social", label: "Social links", hint: "LinkedIn, X or a personal site." },
38
+ { value: "cta", label: "Call to action", hint: "A link such as Book a call." },
39
+ { value: "banner", label: "Banner slot", hint: "An image that campaigns can replace. At most one optional banner per template." },
40
+ { value: "divider", label: "Divider", hint: "A thin line." },
41
+ { value: "disclaimer", label: "Disclaimer", hint: "Legal or confidentiality text." },
42
+ { value: "green_footer", label: "Green footer", hint: "A short environmental note." },
43
+ { value: "custom_html", label: "Custom HTML", hint: "Your own markup. The server removes anything unsafe." },
44
+ ] as const;
45
+ export type BlockKind = (typeof BLOCK_KINDS)[number]["value"];
46
+
47
+ export const POLICY_MODES = [
48
+ { value: "off", label: "Off", hint: "Nothing is managed. Mailboxes keep the signature they have." },
49
+ { value: "default", label: "Managed", hint: "Mailboxes get the assigned or default template; employees may change the parts you leave unlocked." },
50
+ { value: "enforced", label: "Enforced", hint: "Mailboxes get the template and employees cannot override it. Every active mailbox must already be assigned." },
51
+ ] as const;
52
+ export type PolicyMode = (typeof POLICY_MODES)[number]["value"];
53
+
54
+ export const COLD_VARIANTS = [
55
+ { value: "compact", label: "Compact (the essentials only)" },
56
+ { value: "full", label: "Full" },
57
+ ] as const;
58
+
59
+ export const CAMPAIGN_STATUSES = [
60
+ { value: "draft", label: "Draft" },
61
+ { value: "scheduled", label: "Scheduled" },
62
+ { value: "paused", label: "Paused" },
63
+ ] as const;
64
+
65
+ export const SOURCE_CHOICES = [
66
+ { value: "org", label: "The organisation or department default" },
67
+ { value: "template", label: "A named template" },
68
+ { value: "custom", label: "Its own signature (restores what it had before)" },
69
+ ] as const;
70
+ export type SourceChoice = (typeof SOURCE_CHOICES)[number]["value"];
71
+
72
+ /**
73
+ * The placeholders a block's text, link and image may use: `{{member.name}}`, with an optional fallback
74
+ * `{{member.title|Team}}`. There is no variables endpoint; this list mirrors what the signature renderer resolves
75
+ * (signatures/usecases/context.py). A required block with a placeholder that has no fallback fails to render for an employee
76
+ * whose directory entry lacks that value.
77
+ */
78
+ export const VARIABLES: { token: string; label: string }[] = [
79
+ { token: "member.name", label: "Employee name" },
80
+ { token: "member.first_name", label: "First name" },
81
+ { token: "member.last_name", label: "Last name" },
82
+ { token: "member.title", label: "Job title" },
83
+ { token: "member.department", label: "Department" },
84
+ { token: "member.phone", label: "Phone" },
85
+ { token: "member.email", label: "Email" },
86
+ { token: "member.pronouns", label: "Pronouns" },
87
+ { token: "member.avatar_url", label: "Photo address" },
88
+ { token: "member.linkedin_url", label: "LinkedIn address" },
89
+ { token: "member.twitter_url", label: "X address" },
90
+ { token: "member.personal_website", label: "Personal website" },
91
+ { token: "mailbox.email", label: "Mailbox address" },
92
+ { token: "mailbox.display_name", label: "Mailbox display name" },
93
+ { token: "company.name", label: "Company name" },
94
+ { token: "company.logo_url", label: "Company logo address" },
95
+ { token: "company.address", label: "Company address" },
96
+ { token: "company.phone", label: "Company phone" },
97
+ { token: "company.website", label: "Company website" },
98
+ { token: "brand.primary_color", label: "Brand primary colour" },
99
+ { token: "brand.secondary_color", label: "Brand secondary colour" },
100
+ { token: "brand.accent_color", label: "Brand accent colour" },
101
+ { token: "brand.font", label: "Brand font" },
102
+ ];
103
+
104
+ export const variableText = (token: string): string => `{{${token}}}`;
105
+
106
+ /** One block of a template, exactly the fields the API stores (it forbids any other). */
107
+ export interface TemplateBlock {
108
+ id: string;
109
+ kind: BlockKind;
110
+ text: string;
111
+ url: string;
112
+ image: string;
113
+ locked: boolean;
114
+ required: boolean;
115
+ }
116
+
117
+ export interface TemplateData {
118
+ name: string;
119
+ layout: SignatureLayout;
120
+ blocks: TemplateBlock[];
121
+ primary_color: string;
122
+ font: SignatureFont;
123
+ brand_source: BrandSource;
124
+ }
125
+
126
+ /** A stored template as the API returns it. `data` is open: a legacy row may lack fields. */
127
+ export interface SignatureTemplate {
128
+ id: string;
129
+ version: number;
130
+ data: Partial<Omit<TemplateData, "blocks">> & { blocks?: Partial<TemplateBlock>[] };
131
+ }
132
+
133
+ export interface PolicyData {
134
+ mode: PolicyMode;
135
+ default_template_id: string | null;
136
+ department_templates: Record<string, string>;
137
+ allow_employee_overrides: boolean;
138
+ cold_sends_variant: "full" | "compact";
139
+ native_enabled: boolean;
140
+ }
141
+
142
+ export interface SignaturePolicy {
143
+ version: number;
144
+ data: Partial<PolicyData>;
145
+ }
146
+
147
+ export interface SignatureMember {
148
+ id: string;
149
+ name?: string | null;
150
+ email?: string | null;
151
+ department?: string | null;
152
+ }
153
+
154
+ export interface Assignment {
155
+ member_id?: string;
156
+ template_id?: string | null;
157
+ account_subject?: string;
158
+ send_as_email?: string;
159
+ integration_id?: string | null;
160
+ overrides?: Record<string, string>;
161
+ native_requested?: boolean;
162
+ }
163
+
164
+ /** One mailbox's signature deployment. `revision` is the concurrency token every per-mailbox write sends back. */
165
+ export interface Deployment {
166
+ mailbox_id: number;
167
+ email?: string | null;
168
+ revision: number;
169
+ status?: string | null;
170
+ error?: string | null;
171
+ alert?: boolean;
172
+ assignment?: Assignment;
173
+ attempts?: number;
174
+ next_attempt?: string | null;
175
+ native_coverage?: string | null;
176
+ updated_at?: string | null;
177
+ }
178
+
179
+ export interface MailboxRecord {
180
+ id: number;
181
+ email?: string | null;
182
+ display_name?: string | null;
183
+ provider?: string | null;
184
+ is_archived?: boolean | null;
185
+ }
186
+
187
+ /** A mailbox with its signature deployment, when it has one. */
188
+ export interface SignatureMailbox {
189
+ id: number;
190
+ email: string;
191
+ display_name?: string | null;
192
+ deployment?: Deployment;
193
+ }
194
+
195
+ export interface CampaignBlock extends Partial<TemplateBlock> {
196
+ kind?: BlockKind;
197
+ }
198
+
199
+ export interface CampaignData {
200
+ name?: string;
201
+ block?: CampaignBlock;
202
+ starts_at?: string;
203
+ ends_at?: string;
204
+ status?: string;
205
+ priority?: number;
206
+ mailbox_ids?: number[];
207
+ recipient_domains?: string[];
208
+ excluded_domains?: string[];
209
+ external_only?: boolean;
210
+ departments?: string[];
211
+ recipient_list_ids?: number[];
212
+ native_static?: boolean;
213
+ }
214
+
215
+ export interface SignatureCampaign {
216
+ id: string;
217
+ revision: number;
218
+ data: CampaignData;
219
+ }
220
+
221
+ export interface Artifact {
222
+ html: string;
223
+ plain: string;
224
+ digest?: string;
225
+ required_html?: string;
226
+ required_plain?: string;
227
+ }
228
+
229
+ export type PreviewVariant = "full" | "compact" | "campaign";
230
+ export type TemplatePreview = Partial<Record<PreviewVariant, Artifact>>;
231
+
232
+ export interface CampaignPreview {
233
+ html: string;
234
+ plain: string;
235
+ native_static: boolean;
236
+ matching?: Record<string, unknown> | null;
237
+ }
238
+
239
+ export interface ReportMetric {
240
+ kind: string;
241
+ mailbox_id?: number | null;
242
+ campaign_id?: string | null;
243
+ count: number;
244
+ source?: string | null;
245
+ }
246
+
247
+ export interface SignatureReport {
248
+ native_verifications?: number;
249
+ retention_days?: number;
250
+ metrics?: ReportMetric[];
251
+ tracking_available?: boolean;
252
+ limitations?: string[];
253
+ }
254
+
255
+ // ---------- templates ----------
256
+
257
+ /** The blocks a new template starts with: the one required, locked identity block every template needs. */
258
+ export function starterBlocks(): TemplateBlock[] {
259
+ return [{ id: "identity", kind: "identity", text: "{{member.name}}\n{{member.title}}", url: "", image: "", locked: true, required: true }];
260
+ }
261
+
262
+ /** The API's own defaults for a new template (signatures/domain/models.py Template), so a created body states what it stores. */
263
+ export function blankTemplate(): TemplateData {
264
+ return { name: "", layout: "classic-stacked", blocks: starterBlocks(), primary_color: "#222222", font: "Arial", brand_source: "brand_kit" };
265
+ }
266
+
267
+ const str = (v: unknown): string => (typeof v === "string" ? v : "");
268
+
269
+ /** A stored template as editable form state. Only known keys are kept: the API forbids any other on write. */
270
+ export function draftFromTemplate(t: SignatureTemplate): TemplateData {
271
+ const d = t.data ?? {};
272
+ return {
273
+ name: str(d.name),
274
+ layout: LAYOUTS.some((l) => l.value === d.layout) ? (d.layout as SignatureLayout) : "classic-stacked",
275
+ blocks: (d.blocks ?? []).map((b) => ({
276
+ id: str(b.id),
277
+ kind: BLOCK_KINDS.some((k) => k.value === b.kind) ? (b.kind as BlockKind) : "custom_html",
278
+ text: str(b.text),
279
+ url: str(b.url),
280
+ image: str(b.image),
281
+ locked: Boolean(b.locked),
282
+ required: Boolean(b.required),
283
+ })),
284
+ primary_color: /^#[a-fA-F0-9]{6}$/.test(str(d.primary_color)) ? str(d.primary_color) : "#222222",
285
+ font: FONTS.includes(d.font as SignatureFont) ? (d.font as SignatureFont) : "Arial",
286
+ brand_source: d.brand_source === "custom" ? "custom" : "brand_kit",
287
+ };
288
+ }
289
+
290
+ /** A fresh, unique block id for a kind (`cta`, `cta-2`, ...). */
291
+ export function nextBlockId(kind: string, blocks: readonly TemplateBlock[]): string {
292
+ const used = new Set(blocks.map((b) => b.id));
293
+ if (!used.has(kind)) return kind;
294
+ for (let n = 2; ; n++) if (!used.has(`${kind}-${n}`)) return `${kind}-${n}`;
295
+ }
296
+
297
+ /** The product rules the API enforces on a template, checked before a save so the person sees the reason at once. */
298
+ export function templateProblems(t: TemplateData): string[] {
299
+ const out: string[] = [];
300
+ if (t.name.trim() === "") out.push("Give the template a name.");
301
+ if (t.name.length > 150) out.push("The name can be at most 150 characters.");
302
+ if (t.blocks.length === 0) out.push("Add at least one block.");
303
+ if (t.blocks.length > 30) out.push("A template can have at most 30 blocks.");
304
+ if (!t.blocks.some((b) => b.kind === "identity" && b.required)) out.push("A required identity block is necessary.");
305
+ if (t.blocks.some((b) => b.required && !b.locked)) out.push("Required blocks must be locked.");
306
+ if (t.blocks.filter((b) => b.kind === "banner" && !b.required).length > 1) out.push("Use at most one optional banner slot.");
307
+ if (new Set(t.blocks.map((b) => b.id)).size !== t.blocks.length) out.push("Block ids must be unique.");
308
+ if (t.blocks.some((b) => !/^[a-zA-Z0-9_-]{1,64}$/.test(b.id))) out.push("A block id can use letters, numbers, - and _ (up to 64).");
309
+ if (t.blocks.some((b) => b.text.length > 10000)) out.push("Block text can be at most 10,000 characters.");
310
+ if (t.blocks.some((b) => b.url.length > 2048 || b.image.length > 2048)) out.push("Links and image addresses can be at most 2,048 characters.");
311
+ return out;
312
+ }
313
+
314
+ export const sameTemplate = (a: TemplateData, b: TemplateData): boolean => JSON.stringify(a) === JSON.stringify(b);
315
+
316
+ /** The name a template goes by in a menu. */
317
+ export const templateName = (t: SignatureTemplate | undefined | null): string => (t?.data?.name ? String(t.data.name) : "Untitled template");
318
+
319
+ // ---------- people and mailboxes ----------
320
+
321
+ export const memberLabel = (m: SignatureMember | undefined | null): string => m?.name || m?.email || "Unknown person";
322
+
323
+ /** Mailboxes the API lists, merged with the signature deployment each one has (a deployment alone is kept when the mailbox is not listed). */
324
+ export function mergeMailboxes(records: MailboxRecord[], deployments: Deployment[]): SignatureMailbox[] {
325
+ const byId = new Map(deployments.map((d) => [d.mailbox_id, d]));
326
+ const out: SignatureMailbox[] = [];
327
+ for (const r of records) {
328
+ const deployment = byId.get(r.id);
329
+ byId.delete(r.id); // an archived mailbox is hidden, and must not come back through its deployment
330
+ if (r.is_archived) continue;
331
+ out.push({ id: r.id, email: r.email ?? "", display_name: r.display_name, deployment });
332
+ }
333
+ for (const d of byId.values()) out.push({ id: d.mailbox_id, email: d.email ?? "", deployment: d });
334
+ return out.sort((a, b) => a.email.localeCompare(b.email));
335
+ }
336
+
337
+ /** A deployment that graph8 manages (not given back to the mailbox's own signature). */
338
+ export const isManaged = (d: Deployment | undefined): boolean => Boolean(d) && d!.status !== "restored";
339
+
340
+ export function statusTone(d: Deployment): "positive" | "info" | "warning" | "negative" | "neutral" {
341
+ const s = String(d.status ?? "").toLowerCase();
342
+ if (d.alert) return s === "needs_assignment" || s === "drifted" ? "warning" : "negative";
343
+ if (["pending", "retrying", "leased"].includes(s)) return "info";
344
+ if (["restored", "management_off"].includes(s)) return "neutral";
345
+ if (["active", "applied", "installed", "synced", "ok"].includes(s)) return "positive";
346
+ return "neutral";
347
+ }
348
+
349
+ // ---------- policy ----------
350
+
351
+ const POLICY_KEYS: (keyof PolicyData)[] = ["mode", "default_template_id", "department_templates", "allow_employee_overrides", "cold_sends_variant", "native_enabled"];
352
+
353
+ /** Only the keys the API's policy model knows: a stored legacy key would be a 422 (it forbids extras). */
354
+ export function knownPolicy(data: Partial<PolicyData> | undefined): Partial<PolicyData> {
355
+ const out: Record<string, unknown> = {};
356
+ for (const k of POLICY_KEYS) if (data && k in data && data[k] !== undefined) out[k] = data[k];
357
+ return out as Partial<PolicyData>;
358
+ }
359
+
360
+ // ---------- campaigns ----------
361
+
362
+ /** `2026-10-01T09:30` for a datetime-local input, in the viewer's time zone, from an ISO instant. */
363
+ export function toLocalInput(iso: string | undefined): string {
364
+ if (!iso) return "";
365
+ const d = new Date(iso);
366
+ if (Number.isNaN(d.getTime())) return "";
367
+ const p = (n: number): string => String(n).padStart(2, "0");
368
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`;
369
+ }
370
+
371
+ /** The ISO instant (timezone-aware, as the API requires) for a datetime-local value, or undefined when it is empty or invalid. */
372
+ export function fromLocalInput(local: string): string | undefined {
373
+ if (!local) return undefined;
374
+ const d = new Date(local);
375
+ return Number.isNaN(d.getTime()) ? undefined : d.toISOString();
376
+ }
377
+
378
+ export function formatWhen(iso: string | null | undefined): string {
379
+ if (!iso) return "";
380
+ const d = new Date(iso);
381
+ return Number.isNaN(d.getTime()) ? "" : d.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" });
382
+ }
383
+
384
+ /** "a.com, b.com" -> ["a.com", "b.com"] (trimmed, lower-cased, no blanks, no repeats). */
385
+ export function splitList(text: string): string[] {
386
+ return [...new Set(text.split(/[\s,;]+/).map((s) => s.trim().toLowerCase()).filter(Boolean))];
387
+ }
388
+
389
+ /** A 409 from a versioned write: someone else changed the record since it was read. */
390
+ export function isConflict(error: unknown): boolean {
391
+ return (error as { status?: number } | null | undefined)?.status === 409;
392
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @title Give a new hire's mailbox a managed signature
3
+ * @when Onboarding. This is a TAKEOVER: the mailbox's current signature is captured for restore and replaced, so a person
4
+ * must have agreed (the API demands `confirm_takeover: true`). The employee account must be the employee's own directory email
5
+ * and the send-as address the mailbox address, so both are read, never typed. The mailbox's revision (0 when it has no
6
+ * assignment yet) is sent so a concurrent change answers 409 instead of being overwritten.
7
+ */
8
+ import { g8 } from "@graph8/sdk";
9
+
10
+ g8.init({ apiKey: process.env.G8_API_KEY! });
11
+
12
+ export async function assignSignature(employeeEmail: string): Promise<{ mailboxId: number; revision: number }> {
13
+ const members = await g8.api.signatures.listSignatureMembers({});
14
+ const member = members.data.find((m) => m.email?.toLowerCase() === employeeEmail.toLowerCase());
15
+ if (!member?.email) throw new Error(`${employeeEmail} is not in the directory`);
16
+
17
+ const mailboxes = await g8.api.gtmContext.listMailboxes({ query: { limit: 500 } });
18
+ const mailbox = (mailboxes.data as Array<{ id: number; email?: string | null }>).find((m) => m.email?.toLowerCase() === employeeEmail.toLowerCase());
19
+ if (!mailbox?.email) throw new Error(`No mailbox for ${employeeEmail}`);
20
+
21
+ const deployments = await g8.api.signatures.listSignatureDeployments({});
22
+ const current = (deployments.data as Array<{ mailbox_id: number; revision: number }>).find((d) => d.mailbox_id === mailbox.id);
23
+ const assignment = { member_id: member.id, account_subject: member.email, send_as_email: mailbox.email };
24
+
25
+ // A dry run against the mail provider: the account and the send-as address must resolve before anything is replaced.
26
+ await g8.api.signatures.previewSignatureSetup({ body: { mailbox_id: mailbox.id, data: assignment } });
27
+
28
+ const result = await g8.api.signatures.assignSignatureIdentity({
29
+ body: { mailbox_ids: [mailbox.id], expected_revisions: { [mailbox.id]: current?.revision ?? 0 }, confirm_takeover: true, data: assignment },
30
+ });
31
+ return { mailboxId: mailbox.id, revision: result.data[0].revision };
32
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @title Create a signature template and make it the organisation default
3
+ * @when Setting up managed signatures for the first time. Creating a template changes nothing on any mailbox. Making it the
4
+ * default changes what every mailbox that follows the organisation default SENDS, so have a person confirm that first. The
5
+ * policy is a separate document with its own version: set-default sends the POLICY version read from getSignatureSettings.
6
+ * Every template needs one required, locked identity block.
7
+ */
8
+ import { g8 } from "@graph8/sdk";
9
+
10
+ g8.init({ apiKey: process.env.G8_API_KEY! });
11
+
12
+ export async function createDefaultTemplate(name: string): Promise<{ templateId: string; policyVersion: number }> {
13
+ const created = await g8.api.signatures.createSignatureTemplate({
14
+ body: {
15
+ data: {
16
+ name,
17
+ layout: "classic-stacked",
18
+ font: "Arial",
19
+ primary_color: "#222222",
20
+ brand_source: "brand_kit",
21
+ blocks: [
22
+ { id: "identity", kind: "identity", text: "{{member.name}}\n{{member.title|Team}}", locked: true, required: true },
23
+ { id: "contact", kind: "contact", text: "{{member.email}} {{member.phone|}}" },
24
+ ],
25
+ },
26
+ },
27
+ });
28
+
29
+ const policy = await g8.api.signatures.getSignatureSettings({});
30
+ const made = await g8.api.signatures.setDefaultSignatureTemplate({
31
+ path: { template_id: created.data.id },
32
+ body: { version: policy.data.version },
33
+ });
34
+ return { templateId: created.data.id, policyVersion: made.data.version };
35
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @title Report signature impressions and clicks by mailbox and campaign
3
+ * @when A weekly digest or a dashboard backend. Reads only. Treat the numbers with the report's own `limitations` and
4
+ * `tracking_available`: a natively installed signature reports no impressions, and without click tracking there are no clicks.
5
+ */
6
+ import { g8 } from "@graph8/sdk";
7
+
8
+ g8.init({ apiKey: process.env.G8_API_KEY! });
9
+
10
+ export async function signatureReport(): Promise<{ byKind: Record<string, number>; trackingAvailable: boolean; limitations: string[] }> {
11
+ const report = await g8.api.signatures.getSignatureReport({});
12
+ const data = report.data as { metrics?: Array<{ kind: string; count: number }>; tracking_available?: boolean; limitations?: string[] };
13
+ const byKind: Record<string, number> = {};
14
+ for (const m of data.metrics ?? []) byKind[m.kind] = (byKind[m.kind] ?? 0) + m.count;
15
+ return { byKind, trackingAvailable: Boolean(data.tracking_available), limitations: data.limitations ?? [] };
16
+ }
@@ -15,6 +15,15 @@ added in graph8 settings, so it links out). Every paid step goes through the con
15
15
  Also a **Jobs** tab (`?job=`, `EnrichmentJobDetail`: live progress, counts, credits, provider errors, cancel; the jobs running now come from `GET /enrichment/active-jobs` and jobs started this session are listed, so no id has to be pasted; an older job can still be opened by id) and a pipeline detail (`?pipeline=`: steps, credential status, missing keys). `?view=full` opens any detail full-page.
16
16
  Slots: `toolbarActions`, `renderDetailActions(contact)`, `extraDetailTabs`.
17
17
 
18
+ Enrichment depth (slice 1) adds four tabs and job controls, all additive:
19
+
20
+ - **List pipelines** (`?tab=list-pipelines&list=<id>`, open one with `?lp=<pipeline id>`): ordered chains of enrichment steps that run over a whole list. Start from a **template** (saved turned off; adding one the list already has resets it, confirmed) or build one in the sheet (steps come from the configs the list can use, in order, each can be turned off / fill only empty values / skip recently enriched records). Open a pipeline to **Run** it: the run is priced first by `POST .../pipelines/{id}/estimate` (free) for that exact list, and the confirm shows the estimate's `required_credits` (an upper bound: run conditions are ignored when pricing), the per-step records and your balance (`available_credits`). A run the estimate refuses shows the API's reason and never reaches the confirm. The run is polled with per-step progress, can be stopped, and finished runs are listed. Turn on/off and Delete are confirmed. Saving never runs a pipeline.
21
+ - **Auto-enrichment** (`?tab=auto&list=<id>`): what a list enriches by itself as records are ADDED (an insert trigger: existing records are not touched, edits do not re-run it), with the workspace-wide switch and merge rule (keep edits and fill the rest / never overwrite / matched data wins) for contacts and companies. Every change is confirmed and says it spends credits with nobody confirming each record. The API publishes no per-record price for it, so no total is shown.
22
+ - **Jobs** gain **Pause** (free, stops further charges) and **Resume** (confirmed; names the records still to enrich, not a credit figure the API does not quote). The state shown is re-read from the server after each.
23
+ - **Activity** (`?tab=activity`): records enriched, success rate, results over time, daily activity and fields filled for a chosen window. Completed jobs only; the API's cost figure is its own estimate at a fixed rate, so it is not shown.
24
+
25
+ A list's detail links here ("Enrich this list") in the apollo, clay and zoominfo stacks, through the lists block's `renderDetailActions` slot.
26
+
18
27
  Prerequisites: `crm` and `lists` installed, proxy allowlist `enrichment`, `lists`, `contacts`, `companies`; provider keys and credits as below.
19
28
 
20
29
  ## What you get
@@ -29,7 +38,7 @@ Prerequisites: `crm` and `lists` installed, proxy allowlist `enrichment`, `lists
29
38
  `BulkEnrichPanel` with no props is a complete page (choose a list, tick contacts, enrich), so it can be mounted as a
30
39
  route as is; give it `listId` and `contactIds` to embed it. For that reason this block depends on `lists`.
31
40
 
32
- Hook `useProviders()`; helpers in `lib/enrichment-types.ts` (`estimateCredits`, `isJobTerminal`, the price constants).
41
+ Hook `useProviders()`; helpers in `lib/enrichment-types.ts` (`quotedCredits`, `isJobTerminal`, the price constants).
33
42
 
34
43
  ## Cost, in plain numbers (all billable steps are confirmed)
35
44
 
@@ -37,11 +46,20 @@ Hook `useProviders()`; helpers in `lib/enrichment-types.ts` (`estimateCredits`,
37
46
  |---|---|---|
38
47
  | Person or company lookup | 2 credits, **only if a match is found** (a miss is free) | `ConfirmSpendDialog` (billable) showing 2 credits |
39
48
  | Email verification | 1 credit | `ConfirmSpendDialog` (billable) |
40
- | Bulk enrichment | quoted by the preflight (`max_total_credits`, the most it can charge); flat 2 credits per record when the preflight cannot price it. Providers on your own key cost 0 | `ConfirmSpendDialog` (billable) with that estimate |
49
+ | Bulk enrichment | quoted by the preflight (`max_total_credits`, the most it can charge); no total is shown, and the confirm says so, when the preflight cannot price the run. Providers on your own key cost 0 | `ConfirmSpendDialog` (billable) with that quote (or with no total when there is none) |
41
50
  | Create a waterfall pipeline | creating does not run it; running spends per the providers used | `ConfirmSpendDialog` |
42
51
 
43
- There is **no balance read** in the API. Every number shown is an estimate of what a run can cost, never a check that
44
- you can afford it; an under-funded run fails with 402.
52
+ There is **no standalone balance read** in the API. Bulk-enrichment numbers are estimates of what a run can cost, never a
53
+ check that you can afford it; an under-funded run fails with 402. The one place a balance appears is a list pipeline's price
54
+ estimate (`available_credits`), which the run confirm shows beside the quote.
55
+
56
+ | List pipeline action | Cost | Confirmed with |
57
+ |---|---|---|
58
+ | Save (create or edit) a pipeline | nothing: it is saved turned off and does not run | `ConfirmSpendDialog` (billable tier) saying so |
59
+ | Turn on / off | nothing by itself: a pipeline runs when you run it | `ConfirmSpendDialog` (billable tier) saying so |
60
+ | Run | the estimate's `required_credits` for this list and pipeline (an upper bound), per record per step | `ConfirmSpendDialog` (billable) showing that number |
61
+ | Auto-enrichment (list or workspace) | every record added from then on, no price published | `ConfirmSpendDialog` (billable) stating the ongoing spend |
62
+ | Resume a paused job | the records it had not reached | `ConfirmSpendDialog` (billable) naming how many |
45
63
 
46
64
  ## The flow, with the ids that pass between steps
47
65