feat(integrations): add ZoomInfo, align Wiza, audit Apollo, refresh docs (#4776)

* feat(integrations): add ZoomInfo, align Wiza, audit Apollo, refresh docs

- Add ZoomInfo integration: search/enrich contacts & companies, intent, news (6 tools), proxy route, block, and icon
- Validate and align Wiza tools/block/outputs against live API docs
- Audit Apollo tools: tighten params, outputs, and types
- Update tool docs (.mdx), icons, icon mappings, and integrations.json

* fix(zoominfo): use useId for ZoomInfoIcon clipPath to avoid duplicate DOM ids

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(apollo): address PR review on sequence add and bulk enrich

- sequence_add_contacts: send large contact_ids/label_names arrays in the
  POST body (Rails merges query + body params) to avoid reverse-proxy URL
  length limits; keep scalar settings in the query string
- organization_bulk_enrich: add back-compat shim mapping the legacy
  `organizations` ({name, domain}[]) subBlock value to the new `domains`
  string array so saved workflows keep running

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(integrations): unique ZoomInfo icon clip id, numeric employee range filters

- ZoomInfoIcon: derive clipPath id from useId() so multiple instances don't collide
- ZoomInfo company search: send employeeRangeMin/Max as numbers, matching revenueMin/Max

* fix(zoominfo): send employeeRange filters as strings per API schema

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(zoominfo): send contactAccuracyScoreMin as string per Contacts Search schema

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(apollo): harden people_search pagination, correct bulk-update output docs

- people_search: read pagination from both the nested `pagination` object
  (legacy /mixed_people/search) and top-level fields, avoiding silent
  fallback to defaults
- account_bulk_update: correct output descriptions — accounts support up to
  1000 per request and async is opt-in (not auto-triggered at 100 like contacts)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(wiza): correct company enrichment credits shape in output docs

Company enrichment returns api_credits { total, company_credits }, not the email/phone/scrape breakdown used by individual reveals. Description-only fix verified against docs.wiza.co.

* fix(apollo): send sequence add contact_ids/label_names as query params per docs

Apollo documents every field for emailer_campaigns/:id/add_contact_ids as a query parameter with no request body. Append contact_ids[]/label_names[] to the query string instead of the JSON body to match the documented contract.

* feat(apollo): expose account_stage_id uniform field for bulk update accounts

Apollo documents account_stage_id as a Body Param for /accounts/bulk_update ('when using account_ids, apply this account stage to all accounts'). Adds it to the tool params, body builder, type, block subBlock, and params mapper alongside name/owner_id.

* docs(apollo): correct contact_update typed_custom_fields description

Apollo's update-a-contact endpoint documents typed_custom_fields, so drop
the inaccurate "not officially documented" caveat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(zoominfo): default required outputFields on enrich; parse nested API error object

- ZoomInfo enrich endpoints require outputFields; send a curated default set when omitted so requests don't fail
- extractZoomInfoError now reads the GTM REST nested error object ({error:{code,message}}) instead of dropping it to a generic HTTP message

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* improvement(wiza): add wandConfig to complex prospect-search filter fields

Adds AI-assist wandConfig (json-object) with format examples to the structured filter inputs (job_title, job_company, past_company, company_industry, location, company_location) and the full filters object, completing the wandConfig checklist item for the Wiza block.

* fix(findymail): surface API .error messages and alphabetize registry

- transformResponse error branches now fall back to the response body's
  `error` field before the generic status string, so Findymail's actual
  messages ("Not enough credits" on 402, "Subscription is paused" on 423,
  "One identifier is required..." on 422) reach the user instead of a
  bare "Findymail API error: <status>". Applied to all 11 tools.
- alphabetize the findymail entries in tools/registry.ts to match the
  already-alphabetical import block and the integration guideline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Waleed
2026-05-28 15:30:25 -07:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 53eaa600af
commit c898e2e623
80 changed files with 4618 additions and 483 deletions
+81 -33
View File
@@ -30,29 +30,44 @@ export function AgentMailIcon(props: SVGProps<SVGSVGElement>) {
export function AgentPhoneIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 150 150' xmlns='http://www.w3.org/2000/svg'>
<svg {...props} viewBox='0 0 236 240' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
fill='#23AF58'
stroke='#007F3F'
strokeWidth='0.15'
strokeMiterlimit='10'
d='m139.6 53.3c-1.4-2.3-4.9-3.3-7.6-4.8-2.7-1.3-4.2-2.4-5.7-3.6-1.9-1-2.5-2.7-3.3-3.2s-2.7-1.4-4.5 1.3c-2 2.7-4.5 6.6-6.6 11.1-2.3 5.4-6.3 14.9-6.3 18.9 0.5 4.9 3.1 4.6 6.1 7.2 2.5 2.1 2.8 5.8 1.5 12.5-1.3 6.6-4 12.8-7.8 19.2-3.3 5.1-5.8 8.7-10 9.1-5.3 0.5-12.5-3.1-16.8-5.6-1-0.6-2.5-0.9-3.8-0.2-1.3 0.5-2.2 1.6-3.2 3.3-1.5 2.5-4.6 7.7-5.8 12.2-0.5 3 0 6.4 2.9 9 1.4 1.2 2.8 2.5 4.4 3.4 5 2.8 9.6 4.5 16.5 4.9 5.3 0.2 9.3-1 13.4-3.1 2.4-1.3 6.6-4.2 9.6-7.3l1.1-1.2c2.8-3.1 8.8-10 11.6-14.5 2.3-3.5 4.8-7.4 6.9-12.3 2.9-6.7 4.4-14 5-17.9 1.2-7 2.4-17.5 3.4-31.1 0.1-4.3-0.3-6.1-1-7.3zm-4.5 6.7c-0.5 9.5-1.9 23.3-3.1 30.1-0.9 4.5-2.4 9.6-3.8 13.4-1.1 2.6-3.1 7-5.6 10.8-3.4 5.3-8.4 11.6-12 15.8-6.4 6.6-10.2 9.6-14.2 10.8-2.2 0.9-3.8 1.2-7 1.2-3.4-0.1-8-0.7-11.3-2.2-3-1.2-7-4-6.9-6.8 0.4-3.2 3.3-9.6 5.2-11.9 0.2-0.3 0.5-0.3 0.7-0.2 2.5 1.1 6 3.2 9.6 4.5 2.4 0.9 4.8 1.4 7.3 1.4 3.9 0 6.7-1.2 9.5-3.2 5.6-4.6 9-10.8 12.1-17.5 2-4.3 4.1-11.6 4.4-18.3 0.1-4.9-1.1-8.9-4.5-12.2-1.1-0.7-3-2.1-3-2.8 0-4.2 3.9-13 8.9-22.9 0.2-0.7 0.5-1 1.1-0.7 1.1 0.6 3 1.4 4.6 2.4 2.1 1 5.4 2.4 7.1 3.9 0.9 0.4 1 3 0.9 4.4z'
fillRule='evenodd'
clipRule='evenodd'
d='M113.144 79.3516C126.849 79.3516 138.665 80.2967 143.628 85.2591C149.299 90.9306 151.662 103.455 151.662 120.705C151.662 129.921 150.481 136.538 146.936 141.737C140.32 151.19 119.761 154.734 95.6569 155.443C76.8148 155.443 70.5663 153.611 66.7692 149.046L66.5903 148.827C61.8642 143.628 59.501 129.922 58.7921 114.089C58.7921 108.181 59.0286 100.147 61.3917 95.1842C64.5966 88.3165 68.9105 86.1058 77.5557 83.611L78.4062 83.3683C89.5331 80.0303 104.452 79.3516 113.144 79.3516ZM103.868 23.8233C105.684 23.9222 107.236 25.5316 107.236 27.363L107.231 27.516C107.145 29.3418 105.531 30.9074 103.691 30.9075L103.536 30.9035C101.921 30.8184 100.147 29.4233 100.147 27.363C100.147 27.2958 100.149 27.2288 100.153 27.1623C100.271 25.257 101.99 23.7721 103.868 23.8233ZM92.3822 114.379C91.7601 109.217 89.9576 101.341 83.841 101.568C78.406 102.041 75.3339 107.948 76.0428 116.455C76.5154 122.599 79.115 129.216 84.3137 129.216C91.6341 129.704 92.9389 121.325 92.5283 115.743C92.5003 115.363 92.4465 114.913 92.3822 114.379ZM119.287 112.166C118.753 106.772 120.089 97.9051 126.849 97.3172C133.466 97.5536 135.829 106.769 136.065 112.204C136.302 116.222 135.593 120.948 133.23 123.311C131.575 125.201 127.322 125.674 124.959 124.02C121.501 122.176 119.842 116.731 119.325 112.517L119.287 112.166ZM106.078 135.137C104.869 135.173 103.537 135.175 102.037 135.128C101.584 135.128 101.069 135.084 100.534 135.039C98.2803 134.848 95.657 134.627 95.6569 137.491C95.6569 141.508 100.855 144.58 107.235 145.053C115.506 145.289 120.941 139.382 120.705 136.31C120.705 134.656 119.76 133.001 117.633 133.474C117.046 133.591 116.481 133.709 115.927 133.824C112.836 134.466 109.926 135.019 106.078 135.137Z'
fill='white'
/>
<path
fill='#23AF58'
d='m104.7 27.8c-1.3-1.5-3.3-1.3-6.2-1.5l-1.9 0.2-7-0.2-31.5 0.2 1.5-9.3c2-1.1 5.1-3.5 5.8-6.3 1-2.8 0.2-5.9-2-7.4-2.3-1.9-5.8-2.4-9.3-0.8-1.6 1-4.7 3.4-5.4 6.9-0.8 4.1 2.4 6.7 4.7 7.9l-1.5 9.1-17.2 0.9c-12.3 1.1-16.3 1.2-20.6 4.3-2 1.3-3 4.5-3.4 9.8-0.6 11.3-0.7 18.7-0.6 28.3 0.4 11.2 0 36.6 3 39.8l-1.2 0.3c-3.8 0.6-4 6.2-0.5 6.6l15.5-1 69.7-7.6c2.5-0.4 4.3-0.9 4.6-4.3l3.7-71.5c0-1.9 0.2-3.6-0.2-4.4zm-49.6-17.3c0.3-2.2 2.4-3 3.3-2.8 0.7 0.4 1 1.8 0 2.8-1.5 2-3.3 1.7-3.3 0zm40 90.2c-4 1-5.5 1.5-11.5 2.4-7.7 1-19.7 2.1-31.2 3.4l-33.8 2.9c-0.7 0.2-1-0.4-1-1-0.6-6.5-1.2-20.5-1.5-39.5l0.3-23.3c0.6-7.5 0.7-8.7 4.6-9.7 5.1-0.9 7.4-1.4 14.9-1.8l19.5-0.5 41.1-0.5c1.4 0 1.9 0.4 1.9 1.5l-3.3 66.1z'
fillRule='evenodd'
clipRule='evenodd'
d='M17.9127 164.383C18.008 173.453 21.1026 182.054 26.8907 189C37.0521 200.815 55.9566 210.504 79.5873 211.922C83.1319 212.158 87.1496 211.685 90.2217 210.976C98.4924 208.85 103.455 202.705 103.455 195.143C103.455 188.999 98.9651 179.311 85.9683 177.657C70.773 176.441 55.4867 174.057 45.3225 161.351C51.7029 165.369 62.5734 169.858 81.2419 170.567C90.4497 170.567 95.3749 172.384 103.441 175.613L103.641 175.389C111.111 167.007 116.993 160.406 128.031 160.406C134.033 160.256 138.129 162.487 141.046 164.076C142.723 164.99 144.011 165.691 145.046 165.605C148.355 165.367 164.66 145.045 168.677 132.994C169.297 130.926 169.375 129.22 169.384 128.509C169.393 126.948 167.209 125.547 165.778 124.629L165.762 124.619C165.526 124.468 165.311 124.33 165.131 124.207C164.405 123.712 163.73 123.251 163.242 122.832C158.752 119.524 154.971 114.089 154.971 108.417C154.971 99.4375 160.879 86.9131 167.968 75.334C164.991 67.5485 159.13 58.2099 147.374 53.7683L146.809 53.5606C133.948 49.0707 118.229 48.4536 104.637 49.8128L106.527 36.8154C109.599 35.8702 113.616 33.0344 113.616 27.8357C113.849 23.1833 109.96 18.073 103.978 17.9145L103.691 17.9105C98.0199 17.9105 94.0026 22.4008 93.53 26.8907C93.2939 31.3804 96.1297 34.9247 99.438 36.5788L97.3111 49.8128C87.8587 50.5217 78.8788 51.4668 68.9538 53.3573C61.8583 54.8852 54.5424 56.4829 47.9859 59.7016C39.4376 63.9402 32.7425 69.8213 29.2533 79.8238C25.7086 88.0947 24.7636 105.582 24.5273 123.069L25.2361 141.265C20.979 148.205 17.9106 155.63 17.9105 163.95L17.9127 164.383ZM143.628 85.2591C138.665 80.2967 126.849 79.3516 113.144 79.3516C104.452 79.3516 89.5331 80.0303 78.4062 83.3683L77.5557 83.611C68.9105 86.1058 64.5966 88.3165 61.3917 95.1842C59.0286 100.147 58.7921 108.181 58.7921 114.089C59.501 129.922 61.8642 143.628 66.5903 148.827L66.7692 149.046C70.5663 153.611 76.8148 155.443 95.6569 155.443C119.761 154.734 140.32 151.19 146.936 141.737C150.481 136.538 151.662 129.921 151.662 120.705C151.662 103.455 149.299 90.9306 143.628 85.2591ZM107.236 27.363C107.236 25.5316 105.684 23.9222 103.868 23.8233C101.99 23.7721 100.271 25.257 100.153 27.1623C100.149 27.2288 100.147 27.2958 100.147 27.363C100.147 29.4233 101.921 30.8184 103.536 30.9035L103.691 30.9075C105.531 30.9074 107.145 29.3418 107.231 27.516L107.236 27.363Z'
fill='#171717'
/>
<path
fill='#23AF58'
d='m38.9 52.4c-1.8 0-4 1.1-4.5 3.3-1 3.9 1 7.6 4.5 7.7 3.8 0 5-3.8 4.7-6.3-0.2-2-2-4.7-4.7-4.7z'
d='M92.3822 114.379C91.7601 109.217 89.9576 101.341 83.841 101.568C78.406 102.041 75.3339 107.948 76.0428 116.455C76.5154 122.599 79.115 129.216 84.3137 129.216C91.6341 129.704 92.9389 121.325 92.5283 115.743C92.5003 115.363 92.4465 114.913 92.3822 114.379Z'
fill='#171717'
/>
<path
fill='#23AF58'
d='m73.5 53.9c-1.8 0-4.3 1.5-4.4 4.5-0.1 3.2 2 5.3 4.3 5.3 2.5 0 4.2-1.7 4.2-4.8 0-3.2-1.7-4.8-4.1-5z'
d='M106.078 135.137C104.869 135.173 103.537 135.175 102.037 135.128C101.584 135.128 101.069 135.084 100.534 135.039C98.2803 134.848 95.657 134.627 95.6569 137.491C95.6569 141.508 100.855 144.58 107.235 145.053C115.506 145.289 120.941 139.382 120.705 136.31C120.705 134.656 119.76 133.001 117.633 133.474C117.046 133.591 116.481 133.709 115.927 133.824C112.836 134.466 109.926 135.019 106.078 135.137Z'
fill='#171717'
/>
<path
fill='#23AF58'
d='m72.1 77.1c-2.7 3.4-7.2 7.4-14.7 8.3-7.3 0.3-13.9-2.9-20-8.5-3.5-3.4-8 0-6.2 2.7 1.7 2.5 6.4 6.6 10.4 8.8 3.5 2 7.3 3.3 13.8 3.5 4.7 0 9.2-0.8 12.7-2.4 2.9-1.1 5-2.8 6-3.8 2.3-2.1 3.8-4.1 3.5-7.3-0.9-2.5-3.6-2.8-5.5-1.3z'
d='M126.849 97.3172C120.089 97.9051 118.753 106.772 119.287 112.166L119.325 112.517C119.842 116.731 121.501 122.176 124.959 124.02C127.322 125.674 131.575 125.201 133.23 123.311C135.593 120.948 136.302 116.222 136.065 112.204C135.829 106.769 133.466 97.5536 126.849 97.3172Z'
fill='#171717'
/>
<path
d='M185.219 73.4437C174.348 74.1526 165.132 91.6394 162.06 104.873C161.115 109.835 163.242 114.089 166.314 117.397C167.351 118.261 168.443 119.027 169.503 119.77C172.792 122.074 175.766 124.157 175.766 128.268C175.643 141.817 163.304 156.584 154.498 165.841C149.063 171.749 145.754 174.584 138.901 170.567C131.812 165.841 124.959 164.187 117.161 171.276C113.853 174.348 109.599 179.311 106.764 183.092C108.181 186.164 109.363 189.472 109.363 194.907C109.126 199.397 107.236 204.596 104.4 209.086C124.532 231.455 155.463 220.906 173.89 202.636C178.181 198.381 182.187 193.802 185.219 189.709C185.567 189.238 185.576 189.054 185.219 188.763C181.438 185.691 178.129 179.783 178.129 171.513C178.129 160.17 186.394 147.984 197.034 146.464C200.502 145.968 203.651 145.754 209.558 147.409C214.285 135.593 217.829 122.833 217.593 111.253C217.593 89.7493 201.76 73.9165 185.219 73.4437Z'
fill='#171717'
/>
<path
d='M201.76 151.191C194.671 150.955 188.763 154.973 185.691 162.062C182.855 169.151 183.564 177.185 189.945 183.093C192.308 185.456 195.38 188.292 201.76 187.82C206.339 186.904 210.698 184.435 213.544 179.982C217.787 173.223 219.336 163.499 213.576 157.099C212.867 156.154 211.212 154.5 209.322 153.554C206.722 151.9 204.123 151.191 201.76 151.191Z'
fill='#171717'
/>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M151.95 36.3891C145.291 34.1404 138.251 32.7457 131.234 31.9893C133.604 15.5474 120.526 0 103.691 0C88.0053 2.13949e-05 77.0585 12.2798 75.7179 25.0157C75.404 28.0007 75.6665 31.074 76.3755 33.9837C72.1393 34.5787 67.9129 35.2599 63.7306 36.1608L63.7017 36.1669L63.6733 36.1731C41.5302 41.016 21.0899 49.6694 12.5431 73.3623C6.35339 88.5152 6.83579 106.747 6.6185 122.827L7.1406 136.86C3.17535 144.406 4.86162e-05 153.458 0 163.95C6.1323e-06 177.201 4.61489 190.596 13.3109 200.678C31.0027 221.249 69.4846 237.244 97.8231 227.374C107.065 234.696 119.549 239.953 133.779 239.992C152.316 240.281 166.927 232.388 177.969 223.12C184.191 217.971 190.489 211.587 195.611 205.442C209.558 207 220.449 197.5 226 187.82C232.491 176.5 235 165 228.722 147.33C232.661 136.158 235.728 123.512 235.502 111.051C235.402 81.2695 213.171 56.3248 185.73 55.5406C183.203 55.5867 180.728 55.8668 178.279 56.5314C171.906 47.1088 162.702 40.0778 151.95 36.3891ZM185.219 73.4437C174.348 74.1526 165.132 91.6394 162.06 104.873C161.115 109.835 163.242 114.089 166.314 117.397C167.351 118.261 168.443 119.027 169.503 119.77C172.792 122.074 175.766 124.157 175.766 128.268C175.643 141.817 163.304 156.584 154.498 165.841C149.063 171.749 145.754 174.584 138.901 170.567C131.812 165.841 124.959 164.187 117.161 171.276C113.853 174.348 109.599 179.311 106.764 183.092C108.181 186.164 109.363 189.472 109.363 194.907C109.126 199.397 107.236 204.596 104.4 209.086C124.532 231.455 155.463 220.906 173.89 202.636C178.181 198.381 182.187 193.802 185.219 189.709C185.567 189.238 185.576 189.054 185.219 188.763C181.438 185.691 178.129 179.783 178.129 171.513C178.129 160.17 186.394 147.984 197.034 146.464C200.502 145.968 203.651 145.754 209.558 147.409C214.285 135.593 217.829 122.833 217.593 111.253C217.593 89.7493 201.76 73.9165 185.219 73.4437ZM201.76 151.191C194.671 150.955 188.763 154.973 185.691 162.062C182.855 169.151 183.564 177.185 189.945 183.093C192.308 185.456 195.38 188.292 201.76 187.82C206.339 186.904 210.698 184.435 213.544 179.982C217.787 173.223 219.336 163.499 213.576 157.099C212.867 156.154 211.212 154.5 209.322 153.554C206.722 151.9 204.123 151.191 201.76 151.191ZM17.9127 164.383C18.008 173.453 21.1026 182.054 26.8907 189C37.0521 200.815 55.9566 210.504 79.5873 211.922C83.1319 212.158 87.1496 211.685 90.2217 210.976C98.4924 208.85 103.455 202.705 103.455 195.143C103.455 188.999 98.9651 179.311 85.9683 177.657C70.773 176.441 55.4867 174.057 45.3225 161.351C51.7029 165.369 62.5734 169.858 81.2419 170.567C90.4497 170.567 95.3749 172.384 103.441 175.613L103.641 175.389C111.111 167.007 116.993 160.406 128.031 160.406C134.033 160.256 138.129 162.487 141.046 164.076C142.723 164.99 144.011 165.691 145.046 165.605C148.355 165.367 164.66 145.045 168.677 132.994C169.297 130.926 169.375 129.22 169.384 128.509C169.393 126.948 167.209 125.547 165.778 124.629L165.762 124.619C165.526 124.468 165.311 124.33 165.131 124.207C164.405 123.712 163.73 123.251 163.242 122.832C158.752 119.524 154.971 114.089 154.971 108.417C154.971 99.4375 160.879 86.9131 167.968 75.334C164.991 67.5485 159.13 58.2099 147.374 53.7683L146.809 53.5606C133.948 49.0707 118.229 48.4536 104.637 49.8128L106.527 36.8154C109.599 35.8702 113.616 33.0344 113.616 27.8357C113.849 23.1833 109.96 18.073 103.978 17.9145L103.691 17.9105C98.0199 17.9105 94.0026 22.4008 93.53 26.8907C93.2939 31.3804 96.1297 34.9247 99.438 36.5788L97.3111 49.8128C87.8587 50.5217 78.8788 51.4668 68.9538 53.3573C61.8583 54.8852 54.5424 56.4829 47.9859 59.7016C39.4376 63.9402 32.7425 69.8213 29.2533 79.8238C25.7086 88.0947 24.7636 105.582 24.5273 123.069L25.2361 141.265C20.979 148.205 17.9106 155.63 17.9105 163.95L17.9127 164.383Z'
fill='white'
/>
</svg>
)
@@ -536,7 +551,7 @@ export function HubspotIcon(props: SVGProps<SVGSVGElement>) {
xmlns='http://www.w3.org/2000/svg'
fill='currentColor'
>
<path d='M18.16 7.93V5.08a2.2 2.2 0 1.27-1.98v-.067A2.2 2.2 0 17.24.845h-.067a2.2 2.2 0 00-2.19 2.19v.067a2.2 2.2 0 1.25 1.97l.13.01v2.85a6.22 6.22 0 00-2.97 1.31l.012-.01-7.83-6.09A2.5 2.5 0 104.3 4.66l-.12.01 7.7 5.99a6.18 6.18 0 00-1.04 3.45c0 1.34.425 2.59 1.15 3.61l-.013-.02-2.34 2.34a1.97 1.97 0 00-.58-.095h-.002a2.03 2.03 0 102.03 2.03 1.98 1.98 0 00-.1-.595l.5.01 2.32-2.32a6.25 6.25 0 104.78-11.13l-.036-.005zm-.964 9.38a3.21 3.21 0 113.22-3.21v.002a3.21 3.21 0 01-3.21 3.21z' />
<path d='M18.164 7.93V5.084a2.198 2.198 0 0 0 1.27-1.978v-.067A2.2 2.2 0 0 0 17.238.845h-.067a2.2 2.2 0 0 0-2.193 2.194v.067a2.196 2.196 0 0 0 1.27 1.978v2.85a6.21 6.21 0 0 0-2.974 1.31L5.443.515A2.5 2.5 0 1 0 4.3 4.664l-.123.013 7.728 6.013a6.182 6.182 0 0 0-1.043 3.45c0 1.336.43 2.605 1.157 3.62l-2.35 2.348a2.022 2.022 0 0 0-.585-.1 2.026 2.026 0 1 0 2.026 2.026 1.98 1.98 0 0 0-.1-.584l2.318-2.323A6.249 6.249 0 1 0 18.166 7.93Zm-.96 9.371a3.21 3.21 0 1 1 0-6.421 3.21 3.21 0 0 1 0 6.42z' />
</svg>
)
}
@@ -2296,23 +2311,26 @@ export function ElevenLabsIcon(props: SVGProps<SVGSVGElement>) {
}
export function FindymailIcon(props: SVGProps<SVGSVGElement>) {
const id = useId()
const gradient0 = `findymail_paint0_${id}`
const gradient1 = `findymail_paint1_${id}`
return (
<svg {...props} xmlns='http://www.w3.org/2000/svg' viewBox='0 0 45.4462 31.2952' fill='none'>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M27.7788 8.18066C26.628 8.18066 25.479 8.62026 24.6016 9.49766L22.7235 11.0666L20.8454 9.49766C19.968 8.62026 18.8189 8.18066 17.6681 8.18066C16.5191 8.18066 15.3683 8.62026 14.4909 9.49766C12.7361 11.2525 12.7361 14.0991 14.4909 15.8521L20.9722 22.0922C21.9497 23.0339 23.4972 23.0339 24.4747 22.0922L30.9578 15.8521C32.7126 14.0991 32.7126 11.2525 30.9578 9.49766C30.0804 8.62026 28.9296 8.18066 27.7788 8.18066Z'
fill='url(#findymail_paint0)'
fill={`url(#${gradient0})`}
/>
<path
fillRule='evenodd'
clipRule='evenodd'
d='M42.3815 27.2995C42.3815 27.8571 41.9312 28.3074 41.3737 28.3074H4.0725C3.51497 28.3074 3.06644 27.8571 3.06644 27.2995V7.29083C3.06644 6.39914 4.1279 5.9381 4.77835 6.54745L9.53884 11.0042C9.95341 11.3937 10.6182 11.1971 10.7718 10.6485C10.8791 10.2715 11.0167 9.90338 11.1846 9.54956C11.4062 9.08138 11.2579 8.51848 10.8791 8.16466L7.1854 4.70509C6.52958 4.09037 6.96382 2.9896 7.86445 2.9896H37.58C38.4806 2.9896 38.9166 4.09216 38.259 4.70688L34.5671 8.16109C34.1901 8.51491 34.04 9.07959 34.2633 9.54777C34.4295 9.90159 34.5671 10.2679 34.6743 10.645C34.828 11.1918 35.4928 11.3884 35.9073 11.0006L40.6678 6.54567C41.3183 5.93631 42.3815 6.39735 42.3815 7.28726V27.2995ZM42.9141 0H2.53213C1.13294 0 0 1.13294 0 2.53213V28.7631C0 30.1622 1.13294 31.2952 2.53213 31.2952H42.9141C44.3132 31.2952 45.4462 30.1622 45.4462 28.7631V2.53213C45.4462 1.13294 44.3132 0 42.9141 0Z'
fill='url(#findymail_paint1)'
fill={`url(#${gradient1})`}
/>
<defs>
<linearGradient
id='findymail_paint0'
id={gradient0}
x1='20.5769'
y1='8.68821'
x2='24.6922'
@@ -2324,7 +2342,7 @@ export function FindymailIcon(props: SVGProps<SVGSVGElement>) {
<stop offset='1' stopColor='#B91C1C' />
</linearGradient>
<linearGradient
id='findymail_paint1'
id={gradient1}
x1='22.7231'
y1='0'
x2='22.7231'
@@ -3984,14 +4002,7 @@ export const SMSIcon = (props: SVGProps<SVGSVGElement>) => (
)
export const ResendIcon = (props: SVGProps<SVGSVGElement>) => (
<svg
{...props}
width='1800'
height='1800'
viewBox='0 0 1800 1800'
fill='none'
xmlns='http://www.w3.org/2000/svg'
>
<svg {...props} viewBox='360 360 1080 1080' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M1000.46 450C1174.77 450 1278.43 553.67 1278.43 691.28C1278.43 828.9 1174.77 932.56 1000.46 932.56H912.38L1350 1350H1040.82L707.79 1033.48C683.94 1011.47 672.94 985.78 672.94 963.77C672.94 932.57 694.96 905.05 737.16 893.12L908.71 847.24C973.85 829.81 1018.81 779.35 1018.81 713.3C1018.8 632.57 952.75 585.78 871.1 585.78H450V450H1000.46Z'
fill='#FDFDFD'
@@ -4455,6 +4466,20 @@ export function VllmIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function LitellmIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} fill='none' viewBox='0 0 72 72' xmlns='http://www.w3.org/2000/svg'>
<title>LiteLLM</title>
<image
href='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEgAAABICAMAAABiM0N1AAAC+lBMVEUAAABmZmXo6uvl5uQ+S3bd4uPl5t5DRk/n6ed6nbji4tYvPmuosLyIqcOApcAnJB86Rm8xLSpgc5Oguszi49zs7eakqaPq6uRNXY7T08M5SYc0QGsoJiRRT0mFhXZ0d3CnuMRHS1LZ2cr4+PIoNGM0MyslIR9KV341Mi0qO22WnrrD0tuLkqBpj7AqM1STrMdlZU+amXyfn4SyxtPn6ONOWXzj49urwtLs7OOeoZ/F09xMX5C6yNY0NkPDytJ5eWBteqQvQGxndpGarMBGRDs0MyqlpY5LSD+RkXZqc48wPWTZ2dGtraZETnHX18xXV0YhHxt9fnSvtclUaZ87OjFvb1f4+PJ5eV9zc1vk5Nj8/Pbq6t/m5trt7eRoaFL19u/i4tXd3c/6+vTz8+zv7+dsbFba2szT08Po6Nzf39J2dl3s7OHW1scmNWM4RnksPnR+fmNkZE7x8elml7rNzbw8SX2BgWWIiGwmNmcrPHAhLFNlZVDCwq1MX6ArO2soNlwnN2zQ0MBEW6VQVVhEQTk2MSyjo4YxQXdwnsDLy7nGxrJFXas2SIQdJ0fX18saIj1IRzsuKiWxz+amx+GQt9Z3pMZLZbtIYbO7u6RBV6A9UpWnjF+Ks9Fgk7itrZI2SYk+ToQwQXyWl3kzQG6FhWl7qMlpmr+7vaq+vqi2tqI7TYxHVIQvO2ZNVV6fwdtThatJXJqyspkzRYB+gHiPj3FUWV0dGxeDrMvNz8fIyLY/VZs6TpFEVY6bmnxUaHqSlHg8OTSQmrQ4RXI8RGGZvNdxjqZSZqRGWZXGycC9wb2xt7NmeK6BlKRjhKNOWX5+gXt7fXZPWHNsgL9Ra7yirrKqqp9RY56kpJOuk2A0PVXqq1RKS0ZTU0Nxmrhug55kfZVCUolJUmxfYFpCRUaTs85ajrR2krGUpa1LZK2RnaRaWkzppUXa29h6jMWFo7XAwbRaia5ecH6rkmMqMkg2Okayu9N2h5KUloeXiWRqamBFU3qbhFjglzLFgynBltUQAAAAVXRSTlMACgUOhiGTMS/98YYc/v7+h4X+29dmU0n59PPy2Rz9/fr59fPz7urMoZ2Zgzn++/jz8tC/tKmooH9xZl1VTPv079zZ08q1m5h+dV3l49/U0rGreG9fuSw7vQAABwlJREFUWMPs0z+M0lAcwHFzBHIQYRCwYSM6KrkBIoRcHHTUOJJU2tAW0j+hU+vowlaXDnIkDmU8DAuO4OJEk0JggzABywFx4WC52d/j8eeiIODiwjcBpvfh995rH5w6derU/+rMZrefE4TfH4s5oH8ybHbCH3sR8flcLhfL8rosC4HjB7ETjojPxfKykMt9WPfp2FkIx7uAAEBO5lmJISkxmc3SUPJI6Pnbbq/X63a75a+339l0Kg0pipLJZI/b1sPHr+bzH6vmZUqkcKTrmMO5jL52hmbfNg2b4tIB6FAlHvV6NLUIqWppmWpaFAkxjHQYZLuMet1IUUuaVoCucQWtZUmSBE8A/+QAxh4PX3hUFRAQqlXDMK6WGUZ1kuN59CDthc7On3ndfaQAAkS9XqlU8rjKlVH1dAQZEgL7GY9aVJECCBCfF+UrN27nONSGzKYA7YHWDFKWCCLM9vARNBwMzFarkxNgqL9B9g2DlXx9YQzv7lo/O82GVavpus6zDMq1+6biYfeGyddvPOP2YNCaNBs1XYJXQ0zisjhq1+EQ4Qt1VCxp13BFaDMmDNGo8QyFltK41W8GonccjiPoHI0WZ1zojxcGS4rr/8eLlXQ6lUhwqETi6dZd+X1BGEfVNK3vnDQtnbm3ExoAWI/i0FcKSgC07cojL6ezGbwJ/WmnsToP+ICBCQ73XuRZHiVTAG3Z1ZvGxAz1ndOgxTMkBQwgyMAIAKtYGk+WygL0566Ej+Uvt5YFd8OQJAUQIDAJNu7HMSmOQ046qSj0b+P8ar7OQ5oM4wCOv7NjfySoUSJ5ZXhGdEFBdN8HRKdtYpnSYW7tSFrNpducW1LOzH90M8i5BelaUOug3Ky1wKiBHdJsFeEBYakEVnRDv+f3vDu0Wf/2wcF42fN9n+d5977MhTNuGwylRwsPF0AGOrlkR7ASUthLHmZwoQqFxQDe5BzanTNid6ZOKW9uKT2FGViVECvBmcD54fT7Pw8NPSM8k++x3no8K0K/yRNnnDY0nyo8QzI4F7y8/sLefcX79whzhZ876+rq6uurqjSmsvb29jLC2TgtdFlHmw2nyyFTlBOoELvYBFw6MPisDktVGhryer1lXqc1EIpYsvpRS2n54YKCohwh7i6CCDYOEfhQnQMV2jGayvysen9o3PYVHc9PHT4IGbxIezECEyEJGM7Kz1/WCRVcmLFVdqWs7AoRDEXOnCy59BAzxbDBAGYizGUT+Sx4qh4cqqcZ6DSIH0DkAbhisk9jO3FNjna4nfbg3mAkkChCBdT0/ipAMq0NDreTZJzObqesUozrmhlnb61qv5WLN2LxnlwsBIcfBPTJfMujAUaYTYPD2mgxOaHSbTJ1iyXx2HE2GqvqPYPkaZC7Ox8DdCiMRYWse0bQSjONepsMIiaZTGaqVEIoYlEc6XR6BoWH8ougcCY4tBzhjwTikbcBOBwOq7VRb29SiWUyMXDLLPJUhomlnU7PrQIcHzLyNnGaKgVvHFZIwFz0kKmUKCwQcbstFrFSlMpMWKVv1fT392u8H3B0YGgpamYZwDsrJuz2pibIlCgVNguyuZWqVGaj3mHUaOAaxLWwYy8GNRtaiKvofAcESEIiKSlRq+WiapUNiSwixWwmSw/LJqe69BxO2mLoOhfi5NXzQV/udBBPqZ6enk+++1RPz/0UJstuorP93kXOevXrr4GXaGBg4OePrpMBbUmPwbWgJ73JcOAJSk5mspSSykoyWfcX+vmv54aHh1+gc20n25AZHJitUilCVFfX1FSjmtmKmhpmkkqhEhH3k8zgrrnNfNfPbL4ecDy79wargkpx9fX1vSIqUvpSGO5KrCoUvuTjI11gHQNSqfTYaNI8voAnEPB4Ani/nGEWx8McwcdvPGkYPCAA/D/l8XlIIOVn8xkorVRgqDebzxcgHkWHZwcd+AN7OC9vLgNmZVgU1RCCw4j2+HRg3t+cOAF/CEIgckeGW+X7JuCFEECHfjjEkTFBiKYWL8hISgru8TEpj6wJXv8QWGPob+hF69bNnz9/3rx55uvHpbhHuE2j4SaOIlg++j+eyMhZsbGxi7b1Vly+DF+ZywEVfu/Ba3AWaMlLe1a7ZcMGJixuhkokVyuBXC5CKj+fVqt1uXS62qioqJt+WznhO5wFNrlaXUJvcoQx7H0is1iTuX5NGsTQzaiba8cIbYzHjkQCIXVISC6ykY5rfSInghNTqwOkpouKCd+ZtZl2SAkog2w+l/aVNo2Ly0+vxSXqXLXp3DEWxnYqgZglQ+4+2GRXDF1ITJROS8CEIsKGlqZiiJZCM2KxD65QWmYie8KYdJiOrjYdwuFwstQlADOkYLoUEJ+QlrB2Z2AYZ2lmQkJC5tIwHcTdNImKjo4eHxANNnG53BGjOImJiRzmP/Ubf4ltRM+YtyIAAAAASUVORK5CYII='
width='72'
height='72'
preserveAspectRatio='xMidYMid meet'
/>
</svg>
)
}
export function PosthogIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
@@ -4650,17 +4675,40 @@ export function ZendeskIcon(props: SVGProps<SVGSVGElement>) {
}
export function ZoomIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg {...props} viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg' aria-hidden='true'>
<rect x='2' y='6' width='14' height='12' rx='2.5' fill='#0B5CFF' />
<path d='M16 9 L22 6 V18 L16 15 Z' fill='#0B5CFF' />
</svg>
)
}
export function ZoomInfoIcon(props: SVGProps<SVGSVGElement>) {
const id = useId()
const clipId = `zoominfo-clip_${id}`
return (
<svg
{...props}
fill='currentColor'
width='800px'
height='800px'
viewBox='-1 9.5 34 13'
version='1.1'
viewBox='0 0 65 65'
fill='none'
xmlns='http://www.w3.org/2000/svg'
aria-hidden='true'
>
<path d='M19.28 17.4c-0.37 0.37-0.88 0.61-1.44 0.61-1.12 0-2.02-0.91-2.02-2.02s0.91-2.02 2.02-2.02c0.93 0 1.71 0.63 1.95 1.48l0 0.01c0.05 0.16 0.07 0.34 0.07 0.53 0 0.55-0.22 1.05-0.58 1.42l0-0zM15.47 13.59c-0.65 0.62-1.05 1.48-1.05 2.45 0 1.86 1.51 3.37 3.37 3.37s3.37-1.51 3.37-3.37c0-1.54-1.03-2.84-2.44-3.24l-0.02-0.01c-0.27-0.08-0.58-0.12-0.9-0.12-0.9 0-1.72 0.35-2.32 0.93l00zM28.3 12.6c-0.8 0-1.52 0.35-2.02 0.91l0 0c-0.5-0.56-1.22-0.91-2.02-0.91-0.56 0-1.09 0.17-1.52 0.47l0.01-0.01c-0.32-0.28-0.73-0.45-1.18-0.46l0-0v6.74l0.34-0.02c0.54-0.01 0.98-0.45 1-0.99l00 0.02-0.34v-2.36l0.02-0.34c00 00 00 0-0.24 0.06-0.48 0.17-0.68l0 0.01c0.24-0.4 0.67-0.67 1.17-0.67s0.93 0.27 1.16 0.66l0 0.01c0.1 0.2 0.16 0.43 0.17 0.68v0l0.02 0.34v2.36l0.02 0.34c0.02 0.54 0.45 0.97 0.99 1l0 0 0.34 0.02v-3.71l0.02-0.34c00 00 00 0-0.25 0.06-0.48 0.17-0.68l0 0.01c0.24-0.4 0.67-0.67 1.17-0.67 0.5 0 0.93 0.27 1.16 0.67l0 0.01c0.1 0.2 0.16 0.43 0.16 0.67 0 0 0 0 0 0v-0l0.02 0.34v2.36l0.02 0.34c0.02 0.54 0.45 0.98 0.99 1l0 0 0.34 0.02v-4.04c0-1.49-1.21-2.69-2.7-2.690 00 00 0h0zM12.21 17.4c-0.37 0.39-0.89 0.64-1.47 0.64-1.12 0-2.02-0.91-2.02-2.02s0.91-2.02 2.02-2.02c0.92 0 1.7 0.62 1.95 1.47l0 0.01c0.05 0.16 0.08 0.35 0.08 0.54 0 0.54-0.21 1.02-0.55 1.39l00zM10.78 12.6h0c-1.86 0-3.37 1.51-3.37 3.37s1.51 3.37 3.37 3.37 3.37-1.51 3.37-3.37c0-1.86-1.51-3.37-3.37-3.37h-0zM6.73 18.01l-0.34-0.01h-3.03l4.04-4.04-0.02-0.34c-0.01-0.54-0.45-0.98-0.99-0.99l0-0-0.34-0.02h-5.05l0.02 0.34c0.03 0.54 0.46 0.97 0.99 1l0 0 0.34 0.02h3.04l-4.05 4.05 0.02 0.34c0.02 0.54 0.45 0.98 0.99 1l0 0 0.34 0.02h5.06l-0.02-0.34c-0.02-0.54-0.45-0.97-0.99-0.99l0-0z' />
<g clipPath={`url(#${clipId})`}>
<path
d='M0.272461 8.27246C0.272461 3.85418 3.85418 0.272461 8.27246 0.272461H56.2725C60.6907 0.272461 64.2725 3.85418 64.2725 8.27246V56.2725C64.2725 60.6907 60.6907 64.2725 56.2725 64.2725H8.27246C3.85418 64.2725 0.272461 60.6907 0.272461 56.2725V8.27246Z'
fill='#EA1B15'
/>
<path
d='M44.2725 16.2725H47.7096C48.0212 16.2725 48.2726 16.5239 48.2726 16.8356V25.6993C48.2726 26.011 48.0313 26.2624 47.7196 26.2725H44.8355C44.5239 26.2725 44.2725 26.0211 44.2725 25.7093V23.1162L22.0381 44.2725H27.7095C28.0212 44.2725 28.2725 44.524 28.2725 44.8357V47.7094C28.2725 48.0211 28.0212 48.2726 27.7095 48.2726H16.8355C16.5238 48.2726 16.2725 48.0211 16.2725 47.7094V44.2725L41.4757 20.2725H38.8355C38.5239 20.2725 38.2725 20.0211 38.2725 19.7093V16.8457C38.2725 16.5339 38.5239 16.2825 38.8355 16.2825L44.2725 16.2725ZM24.2725 20.2725L33.7095 20.2624C34.0212 20.2624 34.2725 20.011 34.2725 19.6992V16.8356C34.2725 16.5239 34.0212 16.2725 33.7196 16.2725H16.8556C16.5439 16.2725 16.2926 16.5239 16.2926 16.8356V25.7093C16.2926 26.0211 16.5439 26.2725 16.8556 26.2725H19.7195C20.0312 26.2725 20.2725 26.0211 20.2725 25.7093V20.2725H24.2725ZM44.2725 44.2725H32.8355C32.5238 44.2725 32.2725 44.524 32.2725 44.8357V47.7094C32.2725 48.0211 32.5238 48.2726 32.8355 48.2726H47.7096C48.0212 48.2726 48.2726 48.0211 48.2726 47.7094V38.7888C48.2726 38.4771 48.0212 38.2257 47.7096 38.2257H44.8355C44.5239 38.2257 44.2725 38.4771 44.2725 38.7888V44.2725Z'
fill='white'
/>
</g>
<defs>
<clipPath id={clipId}>
<rect width='64' height='64' fill='white' transform='translate(0.272461 0.272461)' />
</clipPath>
</defs>
</svg>
)
}
+2
View File
@@ -209,6 +209,7 @@ import {
ZendeskIcon,
ZepIcon,
ZoomIcon,
ZoomInfoIcon,
} from '@/components/icons'
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
@@ -448,4 +449,5 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
zendesk: ZendeskIcon,
zep: ZepIcon,
zoom: ZoomIcon,
zoominfo: ZoomInfoIcon,
}
@@ -262,6 +262,8 @@ Get details of a specific email message in AgentMail
| `subject` | string | Message subject |
| `text` | string | Plain text content |
| `html` | string | HTML content |
| `labels` | array | Labels assigned to the message |
| `timestamp` | string | Time the message was sent or drafted |
| `createdAt` | string | Creation timestamp |
### `agentmail_get_thread`
@@ -298,6 +300,8 @@ Get details of a specific email thread including messages in AgentMail
| ↳ `subject` | string | Message subject |
| ↳ `text` | string | Plain text content |
| ↳ `html` | string | HTML content |
| ↳ `labels` | array | Labels assigned to the message |
| ↳ `timestamp` | string | Time the message was sent or drafted |
| ↳ `createdAt` | string | Creation timestamp |
### `agentmail_list_drafts`
@@ -380,6 +384,7 @@ List messages in an inbox in AgentMail
| ↳ `to` | array | Recipient email addresses |
| ↳ `subject` | string | Message subject |
| ↳ `preview` | string | Message preview text |
| ↳ `timestamp` | string | Time the message was sent or drafted |
| ↳ `createdAt` | string | Creation timestamp |
| `count` | number | Total number of messages |
| `nextPageToken` | string | Token for retrieving the next page |
@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="agentphone"
color="linear-gradient(135deg, #1a1a1a 0%, #0a2a14 100%)"
color="#000000"
/>
{/* MANUAL-CONTENT-START:intro */}
@@ -254,6 +254,7 @@ Get a conversation along with its recent messages
| ↳ `direction` | string | inbound or outbound |
| ↳ `channel` | string | sms, mms, or imessage |
| ↳ `mediaUrl` | string | Attached media URL |
| ↳ `mediaUrls` | array | All attached media URLs |
| ↳ `receivedAt` | string | ISO 8601 timestamp |
### `agentphone_get_conversation_messages`
@@ -282,6 +283,7 @@ Get paginated messages for a conversation
| ↳ `direction` | string | inbound or outbound |
| ↳ `channel` | string | sms, mms, or imessage |
| ↳ `mediaUrl` | string | Attached media URL |
| ↳ `mediaUrls` | array | All attached media URLs |
| ↳ `receivedAt` | string | ISO 8601 timestamp |
| `hasMore` | boolean | Whether more messages are available |
@@ -425,7 +427,7 @@ List contacts for this AgentPhone account
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | AgentPhone API key |
| `search` | string | No | Filter by name or phone number \(case-insensitive contains\) |
| `limit` | number | No | Number of results to return \(default 50\) |
| `limit` | number | No | Number of results to return \(default 50, max 200\) |
| `offset` | number | No | Number of results to skip \(min 0\) |
#### Output
@@ -624,6 +626,7 @@ Update conversation metadata (stored state). Pass null to clear existing metadat
| ↳ `direction` | string | inbound or outbound |
| ↳ `channel` | string | Channel \(sms, mms, etc.\) |
| ↳ `mediaUrl` | string | Media URL if any |
| ↳ `mediaUrls` | array | All attached media URLs |
| ↳ `receivedAt` | string | ISO 8601 timestamp |
+9 -4
View File
@@ -179,7 +179,7 @@ Enrich data for up to 10 organizations at once using Apollo
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `apiKey` | string | Yes | Apollo API key |
| `organizations` | array | Yes | Array of organizations to enrich \(max 10\). Each item requires `name` and may include `domain` \(e.g., \[\{"name": "Example Corp", "domain": "example.com"\}\]\) |
| `domains` | array | Yes | Array of company domains to enrich \(max 10, no www. or @, e.g., \["apollo.io", "stripe.com"\]\) |
#### Output
@@ -325,8 +325,10 @@ Update up to 100 existing contacts at once in your Apollo database. Each contact
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Confirmation message from Apollo |
| `job_id` | string | Async job ID \(returned for &gt;100 contacts\) |
| `contacts` | json | Updated contacts \(synchronous response, ≤100 contacts\) |
| `entity_progress_job` | json | Async job descriptor \(&gt;100 contacts or async=true\): \{id, status, ...\} |
| `job_id` | string | Async job ID extracted from entity_progress_job |
| `message` | string | Optional confirmation message from Apollo |
### `apollo_account_create`
@@ -445,8 +447,11 @@ Update up to 1000 existing accounts at once in your Apollo database (higher limi
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `message` | string | Confirmation message from Apollo |
| `accounts` | json | Updated accounts \(synchronous response, ≤100 accounts\): \[\{id, account_stage_id, ...\}\] |
| `account_ids` | json | IDs of accounts that were updated |
| `entity_progress_job` | json | Async job descriptor \(&gt;100 accounts or async=true\) |
| `job_id` | string | Async job ID extracted from entity_progress_job |
| `message` | string | Optional confirmation message from Apollo |
### `apollo_opportunity_create`
@@ -373,7 +373,7 @@ Fetch full details of a single work item by ID from Azure DevOps, including titl
### `azure_devops_get_work_items_batch`
Fetch full details for multiple work items by ID from Azure DevOps. Pass comma-separated IDs (e.g.
Fetch full details for multiple work items by ID from Azure DevOps. Pass comma-separated IDs (e.g.
#### Input
+2 -1
View File
@@ -171,7 +171,8 @@ Download a signed document from a completed DocuSign envelope
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `base64Content` | string | Base64-encoded document content |
| `file` | file | Stored downloaded document file |
| `base64Content` | string | Deprecated legacy inline content. New downloads return file. |
| `mimeType` | string | MIME type of the document |
| `fileName` | string | Original file name |
@@ -1,6 +1,6 @@
---
title: ElevenLabs
description: Convert TTS using ElevenLabs
description: Convert text to speech with ElevenLabs
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
@@ -35,7 +35,7 @@ Integrate ElevenLabs into the workflow. Can convert text to speech.
### `elevenlabs_tts`
Convert TTS using ElevenLabs voices
Convert text to speech using ElevenLabs voices
#### Input
@@ -45,7 +45,7 @@ Convert TTS using ElevenLabs voices
| `voiceId` | string | Yes | The ID of the voice to use \(e.g., "21m00Tcm4TlvDq8ikWAM" for Rachel\) |
| `modelId` | string | No | The ID of the model to use \(e.g., "eleven_multilingual_v2", "eleven_turbo_v2"\). Defaults to eleven_monolingual_v1 |
| `stability` | number | No | Voice stability setting from 0.0 to 1.0 \(e.g., 0.5 for balanced, 0.75 for more stable\). Higher values produce more consistent output |
| `similarity` | number | No | Similarity boost setting from 0.0 to 1.0 \(e.g., 0.75 for natural, 1.0 for maximum similarity\). Higher values make the voice more similar to the original |
| `similarityBoost` | number | No | Similarity boost setting from 0.0 to 1.0 \(e.g., 0.75 for natural, 1.0 for maximum similarity\). Higher values make the voice more similar to the original |
| `apiKey` | string | Yes | Your ElevenLabs API key |
#### Output
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -26,7 +26,7 @@ In Sim, the HubSpot integration enables your agents to interact with your CRM da
## Usage Instructions
Integrate HubSpot into your workflow. Manage contacts, companies, deals, tickets, and other CRM objects with powerful automation capabilities. Can be used in trigger mode to start workflows when contacts are created, deleted, or updated.
Integrate HubSpot into your workflow. Manage contacts, companies, deals, tickets, and other CRM objects with powerful automation capabilities. Can be used in trigger mode to start workflows when records are created, updated, a specific property changes, or a contact joins a list.
@@ -58,6 +58,9 @@ Generate images with OpenAI GPT Image, Google Nano Banana, or Fal.ai image model
| `enableSafetyChecker` | boolean | No | Enable the Fal.ai safety checker when supported |
| `enableWebSearch` | boolean | No | Enable web search grounding when supported by the selected Fal.ai model |
| `thinkingLevel` | string | No | Fal.ai thinking level when supported: minimal or high |
| `pricing` | custom | No | No description |
| `metadata` | string | No | No description |
| `rateLimit` | string | No | No description |
#### Output
+95 -91
View File
@@ -36,11 +36,15 @@ Retrieves Instantly V2 leads with search, campaign, list, and pagination filters
| `items` | string | No | No description |
| `excluded_ids` | array | No | Lead IDs to exclude |
| `items` | string | No | No description |
| `organization_user_ids` | array | No | Organization user IDs to filter leads |
| `items` | string | No | No description |
| `smart_view_id` | string | No | Smart view ID to filter leads |
| `contacts` | array | No | Lead email addresses to include |
| `items` | string | No | No description |
| `limit` | number | No | Number of leads to return, from 1 to 100 |
| `starting_after` | string | No | Forward pagination cursor from next_starting_after |
| `distinct_contacts` | boolean | No | Whether to return distinct contacts |
| `is_website_visitor` | boolean | No | Whether the lead is a website visitor |
| `enrichment_status` | number | No | Enrichment status filter |
| `esg_code` | string | No | Email security gateway code filter |
@@ -48,14 +52,14 @@ Retrieves Instantly V2 leads with search, campaign, list, and pagination filters
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `leads` | array | List of leads \(id, email, first_name, last_name, campaign, status\) |
| `lead` | json | Lead details \(id, email, first_name, last_name, company_name, job_title, campaign, status, payload\) |
| `campaigns` | array | List of campaigns \(id, name, status, daily_limit\) |
| `campaign` | json | Campaign details \(id, name, status, daily_limit, daily_max_leads, open_tracking\) |
| `emails` | array | List of emails \(id, subject, from_address_email, lead, thread_id\) |
| `email` | json | Email details \(id, subject, from_address_email, to_address_email_list, thread_id, content_preview\) |
| `lead_lists` | array | List of lead lists \(id, name, has_enrichment_task, timestamp_created\) |
| `lead_list` | json | Lead list details \(id, organization_id, has_enrichment_task, owned_by, name, timestamp_created\) |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
@@ -82,14 +86,14 @@ Retrieves an Instantly V2 lead by ID.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `leads` | array | List of leads \(id, email, first_name, last_name, campaign, status\) |
| `lead` | json | Lead details \(id, email, first_name, last_name, company_name, job_title, campaign, status, payload\) |
| `campaigns` | array | List of campaigns \(id, name, status, daily_limit\) |
| `campaign` | json | Campaign details \(id, name, status, daily_limit, daily_max_leads, open_tracking\) |
| `emails` | array | List of emails \(id, subject, from_address_email, lead, thread_id\) |
| `email` | json | Email details \(id, subject, from_address_email, to_address_email_list, thread_id, content_preview\) |
| `lead_lists` | array | List of lead lists \(id, name, has_enrichment_task, timestamp_created\) |
| `lead_list` | json | Lead list details \(id, organization_id, has_enrichment_task, owned_by, name, timestamp_created\) |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
@@ -127,6 +131,7 @@ Creates an Instantly V2 lead in a campaign or lead list.
| `skip_if_in_campaign` | boolean | No | Skip if the lead already exists in the campaign |
| `skip_if_in_list` | boolean | No | Skip if the lead already exists in the list |
| `blocklist_id` | string | No | Blocklist ID to check for the lead |
| `verify_leads_for_lead_finder` | boolean | No | Whether to verify leads imported from Lead Finder |
| `verify_leads_on_import` | boolean | No | Whether to verify leads on import |
| `custom_variables` | json | No | Custom variable object with string, number, boolean, or null values |
@@ -134,14 +139,14 @@ Creates an Instantly V2 lead in a campaign or lead list.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `leads` | array | List of leads \(id, email, first_name, last_name, campaign, status\) |
| `lead` | json | Lead details \(id, email, first_name, last_name, company_name, job_title, campaign, status, payload\) |
| `campaigns` | array | List of campaigns \(id, name, status, daily_limit\) |
| `campaign` | json | Campaign details \(id, name, status, daily_limit, daily_max_leads, open_tracking\) |
| `emails` | array | List of emails \(id, subject, from_address_email, lead, thread_id\) |
| `email` | json | Email details \(id, subject, from_address_email, to_address_email_list, thread_id, content_preview\) |
| `lead_lists` | array | List of lead lists \(id, name, has_enrichment_task, timestamp_created\) |
| `lead_list` | json | Lead list details \(id, organization_id, has_enrichment_task, owned_by, name, timestamp_created\) |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
@@ -184,7 +189,7 @@ Submits an Instantly V2 background job to update a lead interest status.
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `lead_email` | string | Yes | Lead email address |
| `interest_value` | number | Yes | Interest status value. Use null in JSON/tool input to reset to Lead. |
| `interest_value` | number | No | Interest status value. Leave empty in the block or pass null to reset to Lead. |
| `campaign_id` | string | No | Campaign ID for the lead |
| `list_id` | string | No | Lead list ID for the lead |
| `ai_interest_value` | number | No | AI interest value to set for the lead |
@@ -215,14 +220,14 @@ Retrieves Instantly V2 campaigns with search, status, tag, and pagination filter
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `leads` | array | List of leads \(id, email, first_name, last_name, campaign, status\) |
| `lead` | json | Lead details \(id, email, first_name, last_name, company_name, job_title, campaign, status, payload\) |
| `campaigns` | array | List of campaigns \(id, name, status, daily_limit\) |
| `campaign` | json | Campaign details \(id, name, status, daily_limit, daily_max_leads, open_tracking\) |
| `emails` | array | List of emails \(id, subject, from_address_email, lead, thread_id\) |
| `email` | json | Email details \(id, subject, from_address_email, to_address_email_list, thread_id, content_preview\) |
| `lead_lists` | array | List of lead lists \(id, name, has_enrichment_task, timestamp_created\) |
| `lead_list` | json | Lead list details \(id, organization_id, has_enrichment_task, owned_by, name, timestamp_created\) |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
@@ -262,14 +267,14 @@ Creates an Instantly V2 campaign using the documented campaign schedule schema.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `leads` | array | List of leads \(id, email, first_name, last_name, campaign, status\) |
| `lead` | json | Lead details \(id, email, first_name, last_name, company_name, job_title, campaign, status, payload\) |
| `campaigns` | array | List of campaigns \(id, name, status, daily_limit\) |
| `campaign` | json | Campaign details \(id, name, status, daily_limit, daily_max_leads, open_tracking\) |
| `emails` | array | List of emails \(id, subject, from_address_email, lead, thread_id\) |
| `email` | json | Email details \(id, subject, from_address_email, to_address_email_list, thread_id, content_preview\) |
| `lead_lists` | array | List of lead lists \(id, name, has_enrichment_task, timestamp_created\) |
| `lead_list` | json | Lead list details \(id, organization_id, has_enrichment_task, owned_by, name, timestamp_created\) |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
@@ -310,14 +315,14 @@ Updates documented Instantly V2 campaign fields.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `leads` | array | List of leads \(id, email, first_name, last_name, campaign, status\) |
| `lead` | json | Lead details \(id, email, first_name, last_name, company_name, job_title, campaign, status, payload\) |
| `campaigns` | array | List of campaigns \(id, name, status, daily_limit\) |
| `campaign` | json | Campaign details \(id, name, status, daily_limit, daily_max_leads, open_tracking\) |
| `emails` | array | List of emails \(id, subject, from_address_email, lead, thread_id\) |
| `email` | json | Email details \(id, subject, from_address_email, to_address_email_list, thread_id, content_preview\) |
| `lead_lists` | array | List of lead lists \(id, name, has_enrichment_task, timestamp_created\) |
| `lead_list` | json | Lead list details \(id, organization_id, has_enrichment_task, owned_by, name, timestamp_created\) |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
@@ -344,14 +349,14 @@ Activates, starts, or resumes an Instantly V2 campaign.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `leads` | array | List of leads \(id, email, first_name, last_name, campaign, status\) |
| `lead` | json | Lead details \(id, email, first_name, last_name, company_name, job_title, campaign, status, payload\) |
| `campaigns` | array | List of campaigns \(id, name, status, daily_limit\) |
| `campaign` | json | Campaign details \(id, name, status, daily_limit, daily_max_leads, open_tracking\) |
| `emails` | array | List of emails \(id, subject, from_address_email, lead, thread_id\) |
| `email` | json | Email details \(id, subject, from_address_email, to_address_email_list, thread_id, content_preview\) |
| `lead_lists` | array | List of lead lists \(id, name, has_enrichment_task, timestamp_created\) |
| `lead_list` | json | Lead list details \(id, organization_id, has_enrichment_task, owned_by, name, timestamp_created\) |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
@@ -380,21 +385,20 @@ Retrieves Instantly V2 Unibox emails with search and pagination filters.
| `i_status` | number | No | Email interest status filter |
| `eaccount` | string | No | Sending email account filter |
| `lead` | string | No | Lead email address filter |
| `lead_id` | string | No | Lead ID filter |
| `is_unread` | number | No | Unread status filter |
| `is_unread` | boolean | No | Unread status filter |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `leads` | array | List of leads \(id, email, first_name, last_name, campaign, status\) |
| `lead` | json | Lead details \(id, email, first_name, last_name, company_name, job_title, campaign, status, payload\) |
| `campaigns` | array | List of campaigns \(id, name, status, daily_limit\) |
| `campaign` | json | Campaign details \(id, name, status, daily_limit, daily_max_leads, open_tracking\) |
| `emails` | array | List of emails \(id, subject, from_address_email, lead, thread_id\) |
| `email` | json | Email details \(id, subject, from_address_email, to_address_email_list, thread_id, content_preview\) |
| `lead_lists` | array | List of lead lists \(id, name, has_enrichment_task, timestamp_created\) |
| `lead_list` | json | Lead list details \(id, organization_id, has_enrichment_task, owned_by, name, timestamp_created\) |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
@@ -426,14 +430,14 @@ Sends an Instantly V2 reply to an existing Unibox email.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `leads` | array | List of leads \(id, email, first_name, last_name, campaign, status\) |
| `lead` | json | Lead details \(id, email, first_name, last_name, company_name, job_title, campaign, status, payload\) |
| `campaigns` | array | List of campaigns \(id, name, status, daily_limit\) |
| `campaign` | json | Campaign details \(id, name, status, daily_limit, daily_max_leads, open_tracking\) |
| `emails` | array | List of emails \(id, subject, from_address_email, lead, thread_id\) |
| `email` | json | Email details \(id, subject, from_address_email, to_address_email_list, thread_id, content_preview\) |
| `lead_lists` | array | List of lead lists \(id, name, has_enrichment_task, timestamp_created\) |
| `lead_list` | json | Lead list details \(id, organization_id, has_enrichment_task, owned_by, name, timestamp_created\) |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
@@ -463,14 +467,14 @@ Retrieves Instantly V2 lead lists with search and pagination filters.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `leads` | array | List of leads \(id, email, first_name, last_name, campaign, status\) |
| `lead` | json | Lead details \(id, email, first_name, last_name, company_name, job_title, campaign, status, payload\) |
| `campaigns` | array | List of campaigns \(id, name, status, daily_limit\) |
| `campaign` | json | Campaign details \(id, name, status, daily_limit, daily_max_leads, open_tracking\) |
| `emails` | array | List of emails \(id, subject, from_address_email, lead, thread_id\) |
| `email` | json | Email details \(id, subject, from_address_email, to_address_email_list, thread_id, content_preview\) |
| `lead_lists` | array | List of lead lists \(id, name, has_enrichment_task, timestamp_created\) |
| `lead_list` | json | Lead list details \(id, organization_id, has_enrichment_task, owned_by, name, timestamp_created\) |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
@@ -499,14 +503,14 @@ Creates an Instantly V2 lead list.
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `leads` | array | List of leads |
| `lead` | json | Lead details |
| `campaigns` | array | List of campaigns |
| `campaign` | json | Campaign details |
| `emails` | array | List of emails |
| `email` | json | Email details |
| `lead_lists` | array | List of lead lists |
| `lead_list` | json | Lead list details |
| `leads` | array | List of leads \(id, email, first_name, last_name, campaign, status\) |
| `lead` | json | Lead details \(id, email, first_name, last_name, company_name, job_title, campaign, status, payload\) |
| `campaigns` | array | List of campaigns \(id, name, status, daily_limit\) |
| `campaign` | json | Campaign details \(id, name, status, daily_limit, daily_max_leads, open_tracking\) |
| `emails` | array | List of emails \(id, subject, from_address_email, lead, thread_id\) |
| `email` | json | Email details \(id, subject, from_address_email, to_address_email_list, thread_id, content_preview\) |
| `lead_lists` | array | List of lead lists \(id, name, has_enrichment_task, timestamp_created\) |
| `lead_list` | json | Lead list details \(id, organization_id, has_enrichment_task, owned_by, name, timestamp_created\) |
| `count` | number | Returned or affected record count |
| `next_starting_after` | string | Cursor for the next page |
| `id` | string | Record ID |
+2 -1
View File
@@ -207,6 +207,7 @@
"youtube",
"zendesk",
"zep",
"zoom"
"zoom",
"zoominfo"
]
}
@@ -5,7 +5,7 @@ description: Query observability data and record deployments in New Relic
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
<BlockInfoCard
type="new_relic"
color="#000000"
/>
@@ -141,3 +141,5 @@ Record a deployment change event in New Relic change tracking.
| ↳ `guid` | string | Entity GUID |
| ↳ `name` | string | Entity name |
| `messages` | array | Messages returned by New Relic for the created change event |
@@ -124,5 +124,6 @@ Enrich company data with additional information and find associated people using
| `structuredData` | json | Enriched company data matching the requested struct fields |
| `references` | json | Source URLs and descriptions used for enrichment |
| `confidenceScore` | number | Quality score for the returned data \(0-10\) |
| `orgChart` | json | Org chart returned when fullOrgChart is enabled |
@@ -175,6 +175,9 @@ Generate videos using Fal.ai with access to Veo 3.1, Sora 2, Seedance 2.0, Kling
| `resolution` | string | No | Video resolution \(varies by model\): 480p, 580p, 720p, 1080p, true_1080p, 1440p, 2160p, 4k |
| `promptOptimizer` | boolean | No | Enable prompt optimization for MiniMax models \(default: true\) |
| `generateAudio` | boolean | No | Generate native audio when supported by the selected Fal.ai model |
| `pricing` | custom | No | No description |
| `metadata` | string | No | No description |
| `rateLimit` | string | No | No description |
#### Output
+2 -2
View File
@@ -149,7 +149,7 @@ Enrich a company by name, domain, LinkedIn ID, or LinkedIn slug with detailed fi
| `company_region` | string | State/region |
| `company_postal_code` | string | Postal code |
| `company_country` | string | Country |
| `credits` | json | Remaining API credits |
| `credits` | json | Credits deducted for this enrichment \(api_credits: \{ total, email_credits, phone_credits, scrape_credits \}\) |
### `wiza_start_individual_reveal`
@@ -227,7 +227,7 @@ Retrieve the status and enriched data for an individual reveal by ID
| `company_linkedin` | string | Company LinkedIn URL |
| `company_location` | string | Full company location |
| `company_description` | string | Company description |
| `credits` | json | Remaining credits balance |
| `credits` | json | Credits deducted for this reveal \(api_credits: \{ total, email_credits, phone_credits, scrape_credits \}\) |
### `wiza_get_credits`
@@ -0,0 +1,203 @@
---
title: ZoomInfo
description: Search and enrich B2B company and contact data with ZoomInfo.
---
import { BlockInfoCard } from "@/components/ui/block-info-card"
<BlockInfoCard
type="zoominfo"
color="#EA1B15"
/>
{/* MANUAL-CONTENT-START:intro */}
[ZoomInfo](https://www.zoominfo.com/) is a B2B go-to-market platform with one of the largest verified databases of company and contact data. It pairs deep firmographic and contact records with buyer intent signals and company news, so your sales, marketing, and operations teams can find, prioritize, and reach the right accounts.
With ZoomInfo, you can:
- **Search companies**: Find organizations by name, industry, location, revenue, and employee size
- **Search contacts**: Discover people by name, job title, department, and management level
- **Enrich companies and contacts**: Resolve detailed firmographics, verified emails, and phone numbers in bulk
- **Track buyer intent**: Surface companies showing intent signals on the topics you care about
- **Monitor company news**: Pull recent articles by category, source, or date range
In Sim, the ZoomInfo integration lets your agents power prospecting, enrichment, and account research workflows end-to-end:
- **Search Companies**: Use `zoominfo_search_companies` to find accounts matching firmographic filters.
- **Search Contacts**: Use `zoominfo_search_contacts` to find people by role and company. Search results do not include emails or phone numbers — use Enrich Contacts for engagement data.
- **Enrich Companies**: Use `zoominfo_enrich_companies` to resolve up to 25 companies per request from a name, domain, ticker, or ZoomInfo ID.
- **Enrich Contacts**: Use `zoominfo_enrich_contacts` to resolve up to 25 contacts per request, returning verified emails, phones, and job details.
- **Search Intent**: Use `zoominfo_search_intent` to find companies researching specific topics, with signal scores and recommended contacts.
- **Search News**: Use `zoominfo_search_news` to monitor company news by category, source URL, or publish date.
ZoomInfo authenticates with the OAuth 2.0 client credentials flow: Sim exchanges your client ID and secret for a short-lived bearer token automatically on each call, so you only ever provide your credentials. A common pattern is to search first to identify the records you want, then pass their IDs into the matching enrich tool to pull full detail efficiently.
{/* MANUAL-CONTENT-END */}
## Usage Instructions
Integrates ZoomInfo into the workflow. Search companies and contacts, enrich firmographic and contact data, find intent signals, and pull news — all using the ZoomInfo GTM API.
## Tools
### `zoominfo_search_companies`
Search the ZoomInfo company database by name, industry, location, and size.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | ZoomInfo OAuth client ID |
| `clientSecret` | string | Yes | ZoomInfo OAuth client secret |
| `companyName` | string | No | Company name to search for |
| `companyWebsite` | string | No | Company website \(comma-separated for multiple\) |
| `companyTicker` | string | No | Stock ticker symbols — JSON array, comma-separated list, or single ticker. Sent to the API as an array. |
| `industryCodes` | string | No | Industry codes — JSON array or comma-separated list. Sent to the API as a comma-separated string. |
| `country` | string | No | Country name |
| `state` | string | No | State or province |
| `metroRegion` | string | No | US/Canada metro region |
| `revenueMin` | number | No | Minimum annual revenue in thousands USD |
| `revenueMax` | number | No | Maximum annual revenue in thousands USD |
| `employeeRangeMin` | number | No | Minimum employee count |
| `employeeRangeMax` | number | No | Maximum employee count |
| `excludeDefunctCompanies` | boolean | No | Exclude inactive companies |
| `page` | number | No | Page number \(1-based\) |
| `rpp` | number | No | Results per page \(1-100, default 25\) |
| `sortBy` | string | No | Field to sort by |
| `sortOrder` | string | No | Sort order \(asc or desc\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `companies` | array | Matching companies |
### `zoominfo_search_contacts`
Search ZoomInfo for contacts (people) by name, job title, company, and other filters. Does not return emails or phone numbers — use Enrich Contacts for engagement data.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | ZoomInfo OAuth client ID |
| `clientSecret` | string | Yes | ZoomInfo OAuth client secret |
| `firstName` | string | No | First name |
| `lastName` | string | No | Last name |
| `fullName` | string | No | Full name |
| `emailAddress` | string | No | Email address |
| `jobTitle` | string | No | Job title |
| `managementLevel` | string | No | Management level — JSON array or comma-separated list. Sent to the API as a comma-separated string. |
| `department` | string | No | Department — JSON array or comma-separated list. Sent to the API as a comma-separated string. |
| `companyId` | string | No | ZoomInfo company ID |
| `companyName` | string | No | Company name |
| `contactAccuracyScoreMin` | number | No | Minimum accuracy score \(70-99\) |
| `requiredFields` | string | No | Fields that must exist in results — JSON array or comma-separated list. Sent to the API as a comma-separated string. |
| `excludePartialProfiles` | boolean | No | Exclude partial profiles |
| `page` | number | No | Page number \(1-based\) |
| `rpp` | number | No | Results per page \(1-100, default 25\) |
| `sortBy` | string | No | Field to sort by |
| `sortOrder` | string | No | Sort order \(asc or desc\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `contacts` | array | Matching contacts \(without emails or phone numbers\) |
### `zoominfo_enrich_companies`
Enrich up to 25 companies in one request with detailed firmographics, industry, financials, and more.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | ZoomInfo OAuth client ID |
| `clientSecret` | string | Yes | ZoomInfo OAuth client secret |
| `matchCompanyInput` | string | Yes | JSON array \(1-25 items\) of company matching criteria, e.g. \[\{"companyName":"Acme","companyWebsite":"acme.com"\}\] |
| `outputFields` | string | No | JSON array or comma-separated list of fields to return \(e.g. \["id","name","website","revenue","employeeCount"\]\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `results` | array | Enrichment results, one per input with match status and attributes |
### `zoominfo_enrich_contacts`
Enrich up to 25 contacts in one request with verified emails, phone numbers, job details, and more.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | ZoomInfo OAuth client ID |
| `clientSecret` | string | Yes | ZoomInfo OAuth client secret |
| `matchPersonInput` | string | Yes | JSON array \(1-25 items\) of contact matching criteria, e.g. \[\{"firstName":"Jane","lastName":"Doe","companyName":"Acme"\}\] |
| `outputFields` | string | No | JSON array or comma-separated list of fields to return \(e.g. \["id","firstName","email","phone","jobTitle"\]\) |
| `requiredFields` | string | No | JSON array or comma-separated list of fields that must exist in results \(e.g. \["email"\]\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `results` | array | Enrichment results, one per input with match status and attributes |
### `zoominfo_search_intent`
Search for companies showing intent signals on specific topics.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | ZoomInfo OAuth client ID |
| `clientSecret` | string | Yes | ZoomInfo OAuth client secret |
| `topics` | string | Yes | Up to 50 intent topics as JSON array or comma-separated list \(e.g. \["CRM Software","Marketing Automation"\]\) |
| `signalStartDate` | string | No | Earliest signal date \(YYYY-MM-DD\) |
| `signalEndDate` | string | No | Latest signal date \(YYYY-MM-DD\) |
| `signalScoreMin` | number | No | Minimum signal score \(60-100\) |
| `signalScoreMax` | number | No | Maximum signal score \(60-100\) |
| `audienceStrengthMin` | string | No | Minimum audience strength \(A-E, A is largest\) |
| `audienceStrengthMax` | string | No | Maximum audience strength \(A-E, A is largest\) |
| `findRecommendedContacts` | boolean | No | Include recommended contacts \(default true\) |
| `country` | string | No | Country filter |
| `state` | string | No | State filter |
| `industryCodes` | string | No | Industry codes — JSON array or comma-separated list. Sent to the API as a comma-separated string. |
| `page` | number | No | Page number \(1-based\) |
| `rpp` | number | No | Results per page \(1-100, default 25\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `signals` | array | Intent signals with topic, score, audience strength, and company |
### `zoominfo_search_news`
Search ZoomInfo news articles by category, URL, or date range.
#### Input
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `clientId` | string | Yes | ZoomInfo OAuth client ID |
| `clientSecret` | string | Yes | ZoomInfo OAuth client secret |
| `categories` | string | No | News categories as JSON array or comma-separated list |
| `url` | string | No | News source URLs as JSON array or comma-separated list |
| `pageDateMin` | string | No | Earliest publish date \(YYYY-MM-DD\) |
| `pageDateMax` | string | No | Latest publish date \(YYYY-MM-DD\) |
| `page` | number | No | Page number \(1-based\) |
| `rpp` | number | No | Results per page \(1-100, default 25\) |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `articles` | array | News articles matching the filters |
+22 -14
View File
@@ -16,34 +16,42 @@ All triggers below are **polling-based** — they check for new data on a schedu
## Triggers
### HubSpot Trigger
### HubSpot CRM Trigger
Triggers when a HubSpot record (contact, company, deal, ticket, or custom object) is created or updated
Triggers when HubSpot CRM records (contacts, companies, deals, tickets, custom objects) are created or updated, or when contacts join a list
#### Configuration
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `triggerCredentials` | string | Yes | Connect a HubSpot account so Sim can poll your CRM on your behalf. |
| `objectType` | string | Yes | Which HubSpot CRM object to watch. Pick |
| `customObjectTypeId` | string | Yes | HubSpot custom object type ID \(e.g. |
| `eventType` | string | Yes | Created fires once per new record. Updated fires whenever the record changes \(and on creation\). |
| `properties` | string | No | Comma- or newline-separated list of HubSpot property names to include on each record. Leave empty to use sensible defaults. Sim always includes the timestamp properties Sim needs internally, regardless of this list. |
| `filterPropertyName` | string | No | Only emit records where this property equals the value below. Leave both fields empty to emit every change. |
| `filterPropertyValue` | string | No | Value the filter property must match \(exact match, case-sensitive\). |
| `maxRecordsPerPoll` | string | No | Cap on records emitted per poll \(default 50, max 1000\). Excess rolls over to the next poll. |
| `objectType` | string | Yes | What you want to watch. |
| `customObjectTypeId` | string | No | HubSpot custom object type ID \(e.g. |
| `listId` | string | No | The HubSpot list to watch for new members. |
| `eventType` | string | No | Created fires once per new record. Updated fires on any modification. Property Changed fires only when the chosen property changes value. |
| `targetPropertyName` | string | No | Fires only when this specific property changes value on a record. |
| `properties` | string | No | Properties to include on each record. Leave empty to use sensible defaults. Sim always includes the timestamps it needs internally. |
| `pipelineId` | string | No | Restrict to a single pipeline. |
| `stageId` | string | No | Restrict to a single stage within the selected pipeline. |
| `ownerId` | string | No | Restrict to records owned by a specific HubSpot user. |
| `filters` | string | No | JSON array of HubSpot search filters, AND-combined. Each item: \{ |
| `maxRecordsPerPoll` | string | No | Soft cap on records emitted per poll \(default 50, max 1000\). Excess rolls over to the next poll. |
#### Output
| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `objectType` | string | HubSpot object type that fired the trigger \(contact, company, deal, ticket, or custom object type ID\) |
| `eventType` | string | Event type that fired the trigger \(created or updated\) |
| `objectId` | string | HubSpot ID of the affected record |
| `occurredAt` | string | ISO timestamp of the create or update on the record \(sourced from the relevant HubSpot timestamp property\) |
| `properties` | json | HubSpot properties returned for the record \(object of property name to value\) |
| `objectType` | string | HubSpot object type \(contact, company, deal, ticket, custom object id, or list_membership\) |
| `eventType` | string | Event type \(created, updated, property_changed, or joined\) |
| `objectId` | string | HubSpot ID of the affected record \(or contact id for list memberships\) |
| `occurredAt` | string | ISO timestamp of when the change happened in HubSpot |
| `properties` | json | HubSpot properties on the record as a key-value object \(property internal name → value\). Default keys per object type \(override via "Properties to Fetch"\): Contact → firstname, lastname, email, phone, company, lifecyclestage, hs_lead_status, hubspot_owner_id, createdate, lastmodifieddate. Company → name, domain, industry, lifecyclestage, hubspot_owner_id, createdate, hs_lastmodifieddate. Deal → dealname, amount, dealstage, pipeline, closedate, hubspot_owner_id, createdate, hs_lastmodifieddate. Ticket → subject, content, hs_pipeline, hs_pipeline_stage, hs_ticket_priority, hubspot_owner_id, createdate, hs_lastmodifieddate. Custom and user-requested properties appear keyed by their HubSpot internal name. |
| `createdAt` | string | ISO timestamp when the record was created in HubSpot |
| `updatedAt` | string | ISO timestamp when the record was last updated in HubSpot |
| `archived` | boolean | Whether the record is archived |
| `propertyName` | string | Name of the property that changed \(property_changed events only\) |
| `propertyValue` | string | New value of the changed property \(property_changed events only\) |
| `previousValue` | string | Previous value before the change \(property_changed events only\) |
| `listId` | string | HubSpot list ID \(list_membership events only\) |
| `timestamp` | string | ISO timestamp when Sim emitted the event |
@@ -208,6 +208,7 @@ import {
ZendeskIcon,
ZepIcon,
ZoomIcon,
ZoomInfoIcon,
} from '@/components/icons'
type IconComponent = ComponentType<SVGProps<SVGSVGElement>>
@@ -418,4 +419,5 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
zendesk: ZendeskIcon,
zep: ZepIcon,
zoom: ZoomIcon,
zoominfo: ZoomInfoIcon,
}
@@ -214,7 +214,7 @@
"name": "AgentPhone",
"description": "Provision numbers, send SMS and iMessage, and place voice calls with AgentPhone",
"longDescription": "Give your workflow a phone. Provision SMS- and voice-enabled numbers, send messages and tapback reactions, place outbound voice calls, manage conversations and contacts, and track usage — all through a single AgentPhone API key.",
"bgColor": "linear-gradient(135deg, #1a1a1a 0%, #0a2a14 100%)",
"bgColor": "#000000",
"iconName": "AgentPhoneIcon",
"docsUrl": "https://docs.sim.ai/tools/agentphone",
"operations": [
@@ -3730,7 +3730,7 @@
"type": "elevenlabs",
"slug": "elevenlabs",
"name": "ElevenLabs",
"description": "Convert TTS using ElevenLabs",
"description": "Convert text to speech with ElevenLabs",
"longDescription": "Integrate ElevenLabs into the workflow. Can convert text to speech.",
"bgColor": "#181C1E",
"iconName": "ElevenLabsIcon",
@@ -5785,7 +5785,7 @@
"slug": "google-slides",
"name": "Google Slides",
"description": "Read, write, and create presentations",
"longDescription": "Integrate Google Slides into the workflow. Can read, write, create presentations, replace text, add slides, add images, get thumbnails, get page details, delete objects, duplicate objects, reorder slides, create tables, create shapes, and insert text.",
"longDescription": "Build, edit, and export branded Google Slides presentations end-to-end. Copy a template, replace text and image tokens, embed Sheets charts, style text and shapes with brand fonts and colors, manage tables and layouts, group elements, run atomic batch updates, and export to PDF or PPTX.",
"bgColor": "#E0E0E0",
"iconName": "GoogleSlidesIcon",
"docsUrl": "https://docs.sim.ai/tools/google_slides",
@@ -5802,10 +5802,34 @@
"name": "Create Presentation",
"description": "Create a new Google Slides presentation"
},
{
"name": "Copy Presentation",
"description": "Copy a template presentation in Drive to a new file. Use this before merging data so the original template is never modified."
},
{
"name": "Export Presentation",
"description": "Export a presentation to PDF, PPTX, ODP, TXT, PNG, JPEG, or SVG via the Drive export endpoint. Stores the exported file as an execution file when execution context is available."
},
{
"name": "Batch Update (Raw)",
"description": "Run a raw Slides API batchUpdate with a list of Request objects. Use this when the higher-level tools do not cover an operation, or to bundle multiple operations into a single atomic batch (all-or-nothing)."
},
{
"name": "Replace All Text",
"description": "Find and replace all occurrences of text throughout a Google Slides presentation"
},
{
"name": "Replace All Shapes With Image",
"description": "Find every shape whose text matches the given token (e.g. {{cover-image}}) and replace it with an image, preserving the shape"
},
{
"name": "Replace Image",
"description": "Replace the source of an existing image with a new image URL, preserving the image"
},
{
"name": "Update Image Properties",
"description": "Update image properties — brightness, contrast, transparency, crop, outline, link — on an existing image."
},
{
"name": "Add Slide",
"description": "Add a new slide to a Google Slides presentation with a specified layout"
@@ -5842,12 +5866,140 @@
"name": "Create Shape",
"description": "Create a shape (rectangle, ellipse, text box, arrow, etc.) on a slide in a Google Slides presentation"
},
{
"name": "Create Line",
"description": "Create a line or connector on a slide."
},
{
"name": "Insert Text",
"description": "Insert text into a shape or table cell in a Google Slides presentation. Use this to add text to text boxes, shapes, or table cells."
},
{
"name": "Delete Text",
"description": "Delete text from a shape or table cell. Use range type ALL to clear all text, or FIXED_RANGE / FROM_START_INDEX to delete a specific span."
},
{
"name": "Update Text Style",
"description": "Update the styling of text in a shape or table cell (bold, italic, font family, font size, foreground/background color, link, etc.). Only the fields you set are applied."
},
{
"name": "Update Paragraph Style",
"description": "Update paragraph styling — alignment, line spacing, indents, space above/below — for text in a shape or table cell."
},
{
"name": "Create Paragraph Bullets",
"description": "Convert paragraphs in a shape or table cell into a bulleted or numbered list using a Google Slides bullet preset."
},
{
"name": "Delete Paragraph Bullets",
"description": "Remove bullets/numbering from paragraphs in a shape or table cell."
},
{
"name": "Update Shape Properties",
"description": "Update a shape"
},
{
"name": "Update Page Properties",
"description": "Update slide/page background — solid color or stretched picture — and other page properties."
},
{
"name": "Update Slide Properties",
"description": "Update slide-specific properties such as whether the slide is skipped during presentation."
},
{
"name": "Update Alt Text",
"description": "Set the accessibility title and/or description (alt text) of a page element such as an image, shape, or group."
},
{
"name": "Update Element Transform",
"description": "Move, resize, scale, or shear a page element. Translate is specified in points; applyMode controls whether the transform is absolute (default) or relative (multiplied with the current transform)."
},
{
"name": "Update Z-Order",
"description": "Bring elements to front, send to back, or step them one layer forward/backward."
},
{
"name": "Group Objects",
"description": "Group two or more page elements on the same slide into a single object group."
},
{
"name": "Ungroup Objects",
"description": "Ungroup one or more object groups, releasing their children back to the slide."
},
{
"name": "Update Line Properties",
"description": "Update line appearance — color, weight, dash style, arrows, link."
},
{
"name": "Update Line Category",
"description": "Change a connector line"
},
{
"name": "Reroute Line",
"description": "Reroute a connector line so it efficiently connects its endpoint shapes — useful after moving the shapes the line connects."
},
{
"name": "Insert Table Rows",
"description": "Insert one or more rows into a table, above or below a reference cell."
},
{
"name": "Insert Table Columns",
"description": "Insert one or more columns into a table, left or right of a reference cell."
},
{
"name": "Delete Table Row",
"description": "Delete the row containing the reference cell from a table."
},
{
"name": "Delete Table Column",
"description": "Delete the column containing the reference cell from a table."
},
{
"name": "Merge Table Cells",
"description": "Merge a rectangular range of table cells into a single cell. The range starts at (rowIndex, columnIndex) and covers rowSpan × columnSpan cells."
},
{
"name": "Unmerge Table Cells",
"description": "Unmerge any merged cells within the given table range."
},
{
"name": "Update Table Cell Properties",
"description": "Update background fill and content alignment for a range of table cells."
},
{
"name": "Update Table Border Properties",
"description": "Update border color, weight, and dash style for a position (e.g. ALL, INNER, OUTER) in a table range."
},
{
"name": "Update Table Column Properties",
"description": "Update column widths and other column-level table properties."
},
{
"name": "Update Table Row Properties",
"description": "Update minimum row heights and other row-level table properties."
},
{
"name": "Embed Sheets Chart",
"description": "Embed a chart from a Google Sheets spreadsheet onto a slide. LINKED charts can be refreshed; NOT_LINKED_IMAGE inserts a static image of the chart."
},
{
"name": "Refresh Sheets Chart",
"description": "Refresh an embedded linked Sheets chart so it reflects the latest spreadsheet data."
},
{
"name": "Replace All Shapes With Sheets Chart",
"description": "Find every shape matching a text token (e.g. {{revenue-chart}}) and replace each with the same embedded Sheets chart, preserving the shape"
},
{
"name": "Embed Video",
"description": "Embed a YouTube or Google Drive video on a slide."
},
{
"name": "Update Video Properties",
"description": "Update video playback options (autoPlay, mute, start/end) and outline."
}
],
"operationCount": 14,
"operationCount": 52,
"triggers": [],
"triggerCount": 0,
"authType": "oauth",
@@ -6417,7 +6569,7 @@
"slug": "hubspot",
"name": "HubSpot",
"description": "Interact with HubSpot CRM or trigger workflows from HubSpot events",
"longDescription": "Integrate HubSpot into your workflow. Manage contacts, companies, deals, tickets, and other CRM objects with powerful automation capabilities. Can be used in trigger mode to start workflows when contacts are created, deleted, or updated.",
"longDescription": "Integrate HubSpot into your workflow. Manage contacts, companies, deals, tickets, and other CRM objects with powerful automation capabilities. Can be used in trigger mode to start workflows when records are created, updated, a specific property changes, or a contact joins a list.",
"bgColor": "#FF7A59",
"iconName": "HubspotIcon",
"docsUrl": "https://docs.sim.ai/tools/hubspot",
@@ -6542,142 +6694,12 @@
"operationCount": 29,
"triggers": [
{
"id": "hubspot_contact_created",
"name": "HubSpot Contact Created",
"description": "Trigger workflow when a new contact is created in HubSpot"
},
{
"id": "hubspot_contact_deleted",
"name": "HubSpot Contact Deleted",
"description": "Trigger workflow when a contact is deleted in HubSpot"
},
{
"id": "hubspot_contact_merged",
"name": "HubSpot Contact Merged",
"description": "Trigger workflow when contacts are merged in HubSpot"
},
{
"id": "hubspot_contact_privacy_deleted",
"name": "HubSpot Contact Privacy Deleted",
"description": "Trigger workflow when a contact is deleted for privacy compliance (GDPR, CCPA, etc.) in HubSpot"
},
{
"id": "hubspot_contact_property_changed",
"name": "HubSpot Contact Property Changed",
"description": "Trigger workflow when any property of a contact is updated in HubSpot"
},
{
"id": "hubspot_contact_restored",
"name": "HubSpot Contact Restored",
"description": "Trigger workflow when a deleted contact is restored in HubSpot"
},
{
"id": "hubspot_company_created",
"name": "HubSpot Company Created",
"description": "Trigger workflow when a new company is created in HubSpot"
},
{
"id": "hubspot_company_deleted",
"name": "HubSpot Company Deleted",
"description": "Trigger workflow when a company is deleted in HubSpot"
},
{
"id": "hubspot_company_merged",
"name": "HubSpot Company Merged",
"description": "Trigger workflow when companies are merged in HubSpot"
},
{
"id": "hubspot_company_property_changed",
"name": "HubSpot Company Property Changed",
"description": "Trigger workflow when any property of a company is updated in HubSpot"
},
{
"id": "hubspot_company_restored",
"name": "HubSpot Company Restored",
"description": "Trigger workflow when a deleted company is restored in HubSpot"
},
{
"id": "hubspot_conversation_creation",
"name": "HubSpot Conversation Creation",
"description": "Trigger workflow when a new conversation is created in HubSpot"
},
{
"id": "hubspot_conversation_deletion",
"name": "HubSpot Conversation Deletion",
"description": "Trigger workflow when a conversation is deleted in HubSpot"
},
{
"id": "hubspot_conversation_new_message",
"name": "HubSpot Conversation New Message",
"description": "Trigger workflow when a new message is added to a conversation in HubSpot"
},
{
"id": "hubspot_conversation_privacy_deletion",
"name": "HubSpot Conversation Privacy Deletion",
"description": "Trigger workflow when a conversation is deleted for privacy compliance (GDPR, CCPA, etc.) in HubSpot"
},
{
"id": "hubspot_conversation_property_changed",
"name": "HubSpot Conversation Property Changed",
"description": "Trigger workflow when any property of a conversation is updated in HubSpot"
},
{
"id": "hubspot_deal_created",
"name": "HubSpot Deal Created",
"description": "Trigger workflow when a new deal is created in HubSpot"
},
{
"id": "hubspot_deal_deleted",
"name": "HubSpot Deal Deleted",
"description": "Trigger workflow when a deal is deleted in HubSpot"
},
{
"id": "hubspot_deal_merged",
"name": "HubSpot Deal Merged",
"description": "Trigger workflow when deals are merged in HubSpot"
},
{
"id": "hubspot_deal_property_changed",
"name": "HubSpot Deal Property Changed",
"description": "Trigger workflow when any property of a deal is updated in HubSpot"
},
{
"id": "hubspot_deal_restored",
"name": "HubSpot Deal Restored",
"description": "Trigger workflow when a deleted deal is restored in HubSpot"
},
{
"id": "hubspot_ticket_created",
"name": "HubSpot Ticket Created",
"description": "Trigger workflow when a new ticket is created in HubSpot"
},
{
"id": "hubspot_ticket_deleted",
"name": "HubSpot Ticket Deleted",
"description": "Trigger workflow when a ticket is deleted in HubSpot"
},
{
"id": "hubspot_ticket_merged",
"name": "HubSpot Ticket Merged",
"description": "Trigger workflow when tickets are merged in HubSpot"
},
{
"id": "hubspot_ticket_property_changed",
"name": "HubSpot Ticket Property Changed",
"description": "Trigger workflow when any property of a ticket is updated in HubSpot"
},
{
"id": "hubspot_ticket_restored",
"name": "HubSpot Ticket Restored",
"description": "Trigger workflow when a deleted ticket is restored in HubSpot"
},
{
"id": "hubspot_webhook",
"name": "HubSpot Webhook (All Events)",
"description": "Trigger workflow on any HubSpot webhook event"
"id": "hubspot_poller",
"name": "HubSpot CRM Trigger",
"description": "Triggers when HubSpot CRM records (contacts, companies, deals, tickets, custom objects) are created or updated, or when contacts join a list"
}
],
"triggerCount": 27,
"triggerCount": 1,
"authType": "oauth",
"category": "tools",
"integrationTypes": ["crm", "analytics", "customer-support", "sales"],
@@ -15257,5 +15279,48 @@
"category": "tools",
"integrationTypes": ["communication", "productivity"],
"tags": ["meeting", "calendar", "scheduling"]
},
{
"type": "zoominfo",
"slug": "zoominfo",
"name": "ZoomInfo",
"description": "Search and enrich B2B company and contact data with ZoomInfo.",
"longDescription": "Integrates ZoomInfo into the workflow. Search companies and contacts, enrich firmographic and contact data, find intent signals, and pull news — all using the ZoomInfo GTM API.",
"bgColor": "#EA1B15",
"iconName": "ZoomInfoIcon",
"docsUrl": "https://docs.sim.ai/tools/zoominfo",
"operations": [
{
"name": "Search Companies",
"description": "Search the ZoomInfo company database by name, industry, location, and size."
},
{
"name": "Search Contacts",
"description": "Search ZoomInfo for contacts (people) by name, job title, company, and other filters. Does not return emails or phone numbers — use Enrich Contacts for engagement data."
},
{
"name": "Enrich Companies",
"description": "Enrich up to 25 companies in one request with detailed firmographics, industry, financials, and more."
},
{
"name": "Enrich Contacts",
"description": "Enrich up to 25 contacts in one request with verified emails, phone numbers, job details, and more."
},
{
"name": "Search Intent",
"description": "Search for companies showing intent signals on specific topics."
},
{
"name": "Search News",
"description": "Search ZoomInfo news articles by category, URL, or date range."
}
],
"operationCount": 6,
"triggers": [],
"triggerCount": 0,
"authType": "none",
"category": "tools",
"integrationTypes": ["sales"],
"tags": ["enrichment", "sales-engagement"]
}
]
@@ -0,0 +1,125 @@
import { createLogger } from '@sim/logger'
import { toError } from '@sim/utils/errors'
import { type NextRequest, NextResponse } from 'next/server'
import { getValidationErrorMessage, isZodError } from '@/lib/api/server'
import { checkInternalAuth } from '@/lib/auth/hybrid'
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
import { generateRequestId } from '@/lib/core/utils/request'
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
import {
assertSafeZoomInfoUrl,
extractZoomInfoError,
fetchZoomInfoAccessToken,
ZOOMINFO_API_BASE,
ZOOMINFO_OUTBOUND_FETCH_TIMEOUT_MS,
type ZoomInfoProxyRequest,
ZoomInfoProxyRequestSchema,
} from '@/app/api/tools/zoominfo/shared'
export const dynamic = 'force-dynamic'
const logger = createLogger('ZoomInfoProxyAPI')
function buildApiUrl(req: ZoomInfoProxyRequest): string {
const subPath = req.path.startsWith('/') ? req.path : `/${req.path}`
const url = `${ZOOMINFO_API_BASE}${subPath}`
if (!req.query || Object.keys(req.query).length === 0) {
return url
}
const search = new URLSearchParams()
for (const [key, value] of Object.entries(req.query)) {
if (value === undefined || value === null) continue
search.append(key, String(value))
}
const queryString = search.toString()
if (!queryString) return url
return url.includes('?') ? `${url}&${queryString}` : `${url}?${queryString}`
}
interface Invocation {
status: number
body: unknown
}
async function callZoomInfo(req: ZoomInfoProxyRequest, accessToken: string): Promise<Invocation> {
const url = assertSafeZoomInfoUrl(buildApiUrl(req), 'apiUrl').toString()
const hasBody = req.body !== undefined && req.body !== null
const headers: Record<string, string> = {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/json',
}
if (hasBody) headers['Content-Type'] = 'application/json'
const response = await secureFetchWithValidation(
url,
{
method: req.method,
headers,
body: hasBody
? typeof req.body === 'string'
? req.body
: JSON.stringify(req.body)
: undefined,
timeout: ZOOMINFO_OUTBOUND_FETCH_TIMEOUT_MS,
},
'apiUrl'
)
const raw = await response.text()
let parsed: unknown = null
if (raw.length > 0) {
try {
parsed = JSON.parse(raw)
} catch {
parsed = raw
}
}
return { status: response.status, body: parsed }
}
export const POST = withRouteHandler(async (request: NextRequest) => {
const requestId = generateRequestId()
try {
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
if (!authResult.success) {
logger.warn(`[${requestId}] Unauthorized ZoomInfo proxy request: ${authResult.error}`)
return NextResponse.json(
{ success: false, error: authResult.error || 'Authentication required' },
{ status: 401 }
)
}
// boundary-raw-json: internal proxy envelope validated by ZoomInfoProxyRequestSchema below; not a public boundary
const json = await request.json()
const proxyReq = ZoomInfoProxyRequestSchema.parse(json)
const accessToken = await fetchZoomInfoAccessToken(proxyReq, requestId)
const invocation = await callZoomInfo(proxyReq, accessToken)
if (invocation.status >= 200 && invocation.status < 300) {
const data = invocation.status === 204 ? null : invocation.body
return NextResponse.json({ success: true, output: { status: invocation.status, data } })
}
const message = extractZoomInfoError(invocation.body, invocation.status)
logger.warn(
`[${requestId}] ZoomInfo API error (${invocation.status}) ${proxyReq.path}: ${message}`
)
return NextResponse.json(
{ success: false, error: message, status: invocation.status },
{ status: invocation.status }
)
} catch (error) {
if (isZodError(error)) {
logger.warn(`[${requestId}] Validation error:`, error.issues)
return NextResponse.json(
{ success: false, error: getValidationErrorMessage(error, 'Validation failed') },
{ status: 400 }
)
}
logger.error(`[${requestId}] Unexpected ZoomInfo proxy error:`, error)
return NextResponse.json({ success: false, error: toError(error).message }, { status: 500 })
}
})
+204
View File
@@ -0,0 +1,204 @@
import { createHash } from 'node:crypto'
import { createLogger } from '@sim/logger'
import { z } from 'zod'
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
const logger = createLogger('ZoomInfoShared')
export const ZOOMINFO_API_BASE = 'https://api.zoominfo.com/gtm'
export const ZOOMINFO_TOKEN_URL = `${ZOOMINFO_API_BASE}/oauth/v1/token`
export const ZOOMINFO_OUTBOUND_FETCH_TIMEOUT_MS = 30_000
export const ZoomInfoAuthSchema = z.object({
clientId: z.string().min(1, 'clientId is required'),
clientSecret: z.string().min(1, 'clientSecret is required'),
})
export type ZoomInfoAuth = z.infer<typeof ZoomInfoAuthSchema>
export const ZoomInfoHttpMethod = z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE'])
export const ZoomInfoProxyPath = z
.string()
.min(1, 'path is required')
.refine(
(p) =>
!p.split(/[/\\]/).some((seg) => seg === '..' || seg === '.') &&
!p.includes('#') &&
!/%(?:2[eEfF]|5[cC]|23)/.test(p),
{
message:
'path must not contain ".." or "." segments, "#", or percent-encoded path/fragment characters',
}
)
export const ZoomInfoProxyRequestSchema = ZoomInfoAuthSchema.extend({
path: ZoomInfoProxyPath,
method: ZoomInfoHttpMethod.default('POST'),
query: z.record(z.string(), z.union([z.string(), z.number(), z.boolean()])).optional(),
body: z.unknown().optional(),
})
export type ZoomInfoProxyRequest = z.infer<typeof ZoomInfoProxyRequestSchema>
const FORBIDDEN_HOSTS = new Set([
'localhost',
'0.0.0.0',
'127.0.0.1',
'169.254.169.254',
'metadata.google.internal',
'metadata',
'[::1]',
'[::]',
])
function isPrivateIPv4(host: string): boolean {
const match = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/)
if (!match) return false
const octets = match.slice(1, 5).map(Number) as [number, number, number, number]
if (octets.some((o) => o < 0 || o > 255)) return false
const [a, b] = octets
if (a === 10) return true
if (a === 172 && b >= 16 && b <= 31) return true
if (a === 192 && b === 168) return true
if (a === 127) return true
if (a === 169 && b === 254) return true
if (a === 0) return true
return false
}
export function assertSafeZoomInfoUrl(rawUrl: string, label: string): URL {
let parsed: URL
try {
parsed = new URL(rawUrl)
} catch {
throw new Error(`${label} must be a valid URL`)
}
if (parsed.protocol !== 'https:') {
throw new Error(`${label} must use https://`)
}
const host = parsed.hostname.toLowerCase()
if (FORBIDDEN_HOSTS.has(host)) {
throw new Error(`${label} host is not allowed`)
}
if (isPrivateIPv4(host)) {
throw new Error(`${label} host is not allowed (private/loopback range)`)
}
if (host !== 'api.zoominfo.com') {
throw new Error(`${label} host must be api.zoominfo.com`)
}
return parsed
}
interface CachedToken {
accessToken: string
expiresAt: number
}
const TOKEN_CACHE = new Map<string, CachedToken>()
const TOKEN_CACHE_MAX_ENTRIES = 500
const TOKEN_SAFETY_WINDOW_MS = 60_000
function tokenCacheKey(auth: ZoomInfoAuth): string {
const secretHash = createHash('sha256').update(auth.clientSecret).digest('hex').slice(0, 16)
return `${auth.clientId}::${secretHash}`
}
function rememberToken(key: string, token: CachedToken): void {
if (TOKEN_CACHE.has(key)) TOKEN_CACHE.delete(key)
TOKEN_CACHE.set(key, token)
while (TOKEN_CACHE.size > TOKEN_CACHE_MAX_ENTRIES) {
const oldestKey = TOKEN_CACHE.keys().next().value
if (oldestKey === undefined) break
TOKEN_CACHE.delete(oldestKey)
}
}
export async function fetchZoomInfoAccessToken(
auth: ZoomInfoAuth,
requestId: string
): Promise<string> {
const cacheKey = tokenCacheKey(auth)
const cached = TOKEN_CACHE.get(cacheKey)
if (cached && cached.expiresAt - TOKEN_SAFETY_WINDOW_MS > Date.now()) {
return cached.accessToken
}
const tokenUrl = assertSafeZoomInfoUrl(ZOOMINFO_TOKEN_URL, 'tokenUrl').toString()
const basic = Buffer.from(`${auth.clientId}:${auth.clientSecret}`).toString('base64')
const params = new URLSearchParams()
params.set('grant_type', 'client_credentials')
const response = await secureFetchWithValidation(
tokenUrl,
{
method: 'POST',
headers: {
Authorization: `Basic ${basic}`,
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json',
},
body: params.toString(),
timeout: ZOOMINFO_OUTBOUND_FETCH_TIMEOUT_MS,
},
'tokenUrl'
)
if (!response.ok) {
const text = await response.text().catch(() => '')
logger.warn(`[${requestId}] ZoomInfo token fetch failed (${response.status}): ${text}`)
throw new Error(`ZoomInfo token request failed: HTTP ${response.status}`)
}
const data = (await response.json()) as {
access_token?: string
expires_in?: number
}
if (!data.access_token) {
throw new Error('ZoomInfo token response missing access_token')
}
const expiresInMs = (data.expires_in ?? 3300) * 1000
rememberToken(cacheKey, {
accessToken: data.access_token,
expiresAt: Date.now() + expiresInMs,
})
return data.access_token
}
export function extractZoomInfoError(body: unknown, status: number): string {
if (body && typeof body === 'object') {
const obj = body as Record<string, unknown>
if (obj.error && typeof obj.error === 'object') {
const eo = obj.error as Record<string, unknown>
const message = typeof eo.message === 'string' ? eo.message : ''
const code = typeof eo.code === 'string' ? eo.code : ''
if (message) return code ? `[${code}] ${message}` : message
}
if (typeof obj.error === 'string' && obj.error.length > 0) {
const desc = typeof obj.error_description === 'string' ? `: ${obj.error_description}` : ''
return `${obj.error}${desc}`
}
if (typeof obj.message === 'string' && obj.message.length > 0) {
return obj.message
}
if (Array.isArray(obj.errors) && obj.errors.length > 0) {
return obj.errors
.map((e) => {
if (e && typeof e === 'object') {
const eo = e as Record<string, unknown>
const title = typeof eo.title === 'string' ? eo.title : ''
const detail = typeof eo.detail === 'string' ? `: ${eo.detail}` : ''
return `${title}${detail}`.trim()
}
return String(e)
})
.filter(Boolean)
.join('; ')
}
}
if (typeof body === 'string' && body.length > 0) return body
return `ZoomInfo request failed with HTTP ${status}`
}
+206 -6
View File
@@ -224,11 +224,10 @@ export const ApolloBlock: BlockConfig<ApolloResponse> = {
required: true,
},
{
id: 'organizations',
title: 'Organizations (JSON Array)',
id: 'domains',
title: 'Domains (JSON Array)',
type: 'code',
placeholder:
'[{"name": "Company A", "domain": "companya.com"}, {"name": "Company B", "domain": "companyb.com"}]',
placeholder: '["apollo.io", "stripe.com"]',
condition: { field: 'operation', value: 'organization_bulk_enrich' },
required: true,
},
@@ -589,6 +588,14 @@ export const ApolloBlock: BlockConfig<ApolloResponse> = {
condition: { field: 'operation', value: 'account_bulk_update' },
mode: 'advanced',
},
{
id: 'account_bulk_update_account_stage_id',
title: 'Uniform Account Stage ID (used with Account IDs)',
type: 'short-input',
placeholder: 'Apollo account stage ID',
condition: { field: 'operation', value: 'account_bulk_update' },
mode: 'advanced',
},
{
id: 'account_attributes',
title: 'Account Attributes (JSON Array of Objects)',
@@ -1027,6 +1034,7 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes
'account_stage_ids',
'account_label_ids',
'people',
'domains',
'organizations',
'contacts',
'accounts',
@@ -1075,6 +1083,29 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes
}
}
if (params.operation === 'organization_bulk_enrich') {
// Back-compat: workflows saved before the `organizations` → `domains` rename stored an
// array of { name, domain? } objects (or plain strings) under `organizations`. Derive
// `domains` from it so those workflows keep running without manual migration.
if (parsedParams.domains === undefined && parsedParams.organizations !== undefined) {
const legacy = parsedParams.organizations
if (Array.isArray(legacy)) {
const derived = legacy
.map((item) => {
if (typeof item === 'string') return item
if (item && typeof item === 'object' && 'domain' in item) {
const domain = (item as Record<string, unknown>).domain
return typeof domain === 'string' ? domain : undefined
}
return undefined
})
.filter((domain): domain is string => typeof domain === 'string' && domain !== '')
if (derived.length > 0) parsedParams.domains = derived
}
}
parsedParams.organizations = undefined
}
if (params.operation === 'contact_bulk_update') {
const { ids, attributes } = splitBulkUpdateInput(parsedParams.contacts)
if (attributes) {
@@ -1103,8 +1134,12 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes
if (rest.account_bulk_update_owner_id) {
parsedParams.owner_id = rest.account_bulk_update_owner_id
}
if (rest.account_bulk_update_account_stage_id) {
parsedParams.account_stage_id = rest.account_bulk_update_account_stage_id
}
parsedParams.account_bulk_update_name = undefined
parsedParams.account_bulk_update_owner_id = undefined
parsedParams.account_bulk_update_account_stage_id = undefined
}
if (params.operation === 'contact_create') {
@@ -1168,7 +1203,172 @@ Return ONLY the timestamp string in ISO 8601 format - no explanations, no quotes
operation: { type: 'string', description: 'Apollo operation to perform' },
},
outputs: {
success: { type: 'boolean', description: 'Whether the operation was successful' },
output: { type: 'json', description: 'Output data from the Apollo operation' },
people: {
type: 'json',
description:
'Array of people (people_search): [{id, first_name, last_name, name, title, email, organization_name, linkedin_url, phone_numbers}]',
},
person: {
type: 'json',
description:
'Enriched person (people_enrich): {id, first_name, last_name, name, title, email, organization_name, linkedin_url, phone_numbers}',
},
matches: {
type: 'json',
description: 'Array of enriched people (people_bulk_enrich), null entries indicate no match',
},
organizations: {
type: 'json',
description:
'Array of organizations (organization_search, organization_bulk_enrich): [{id, name, website_url, linkedin_url, industry, phone, employees, founded_year}]',
},
organization: {
type: 'json',
description:
'Enriched organization (organization_enrich): {id, name, website_url, linkedin_url, industry, phone, employees, founded_year}',
},
contact: {
type: 'json',
description:
'Contact (contact_create, contact_update): {id, first_name, last_name, email, title, account_id, owner_id, created_at}',
},
contacts: {
type: 'json',
description: 'Array of contacts (contact_search)',
},
created_contacts: {
type: 'json',
description: 'Newly created contacts (contact_bulk_create)',
},
existing_contacts: {
type: 'json',
description: 'Existing contacts (contact_bulk_create with dedupe)',
},
account: {
type: 'json',
description:
'Account (account_create, account_update): {id, name, domain, website_url, phone, owner_id, account_stage_id, created_at}',
},
accounts: {
type: 'json',
description: 'Array of accounts (account_search)',
},
created_accounts: {
type: 'json',
description: 'Newly created accounts (account_bulk_create)',
},
existing_accounts: {
type: 'json',
description: 'Existing accounts (account_bulk_create with dedupe)',
},
failed_accounts: {
type: 'json',
description: 'Accounts that failed (account_bulk_create)',
},
account_ids: {
type: 'json',
description: 'IDs of updated accounts (account_bulk_update)',
},
entity_progress_job: {
type: 'json',
description: 'Async job descriptor (contact_bulk_update, account_bulk_update async path)',
},
opportunity: {
type: 'json',
description:
'Opportunity (opportunity_create, opportunity_update, opportunity_get): {id, name, account_id, amount, opportunity_stage_id, owner_id, closed_date, is_closed, is_won, currency, created_at}',
},
opportunities: {
type: 'json',
description: 'Array of opportunities (opportunity_search)',
},
sequences: {
type: 'json',
description:
'Array of sequences (sequence_search): [{id, name, active, num_steps, num_contacts, created_at}]',
},
added: {
type: 'json',
description:
'Contacts added to sequence (sequence_add): [{id, first_name, last_name, email, status}]',
},
skipped: {
type: 'json',
description: 'Contacts skipped by sequence add (sequence_add)',
},
skipped_contact_ids: {
type: 'json',
description: 'Skipped contact IDs (sequence_add): array of IDs or {id: reason} map',
},
emailer_campaign: {
type: 'json',
description: 'Emailer campaign details (sequence_add): {id, name}',
},
sequence_id: {
type: 'string',
description: 'Sequence ID contacts were added to (sequence_add)',
},
tasks: {
type: 'json',
description:
'Array of tasks (task_create, task_search): [{id, user_id, contact_id, type, priority, status, due_at, note, created_at}]',
},
email_accounts: {
type: 'json',
description:
'Linked email accounts (email_accounts): [{id, email, type, active, default, linked_at}]',
},
pagination: {
type: 'json',
description: 'Pagination info (contact_search, account_search, task_search)',
},
page: { type: 'number', description: 'Current page (search operations)' },
per_page: { type: 'number', description: 'Results per page (search operations)' },
total_entries: {
type: 'number',
description: 'Total entries matching search (search operations)',
},
total_added: { type: 'number', description: 'Contacts added (sequence_add)' },
total_skipped: { type: 'number', description: 'Contacts skipped (sequence_add)' },
total_submitted: {
type: 'number',
description: 'Total submitted (contact_bulk_create, account_bulk_create)',
},
created: {
type: 'boolean',
description: 'Created flag for single-item create operations',
},
updated: { type: 'boolean', description: 'Updated flag for single-item update operations' },
found: { type: 'boolean', description: 'Found flag (opportunity_get)' },
enriched: {
type: 'boolean',
description: 'Enriched flag (people_enrich, organization_enrich)',
},
message: { type: 'string', description: 'Message (bulk_update operations)' },
job_id: { type: 'string', description: 'Async job ID (bulk_update operations)' },
total: {
type: 'number',
description: 'Total count (organization_bulk_enrich requested domains; email_accounts count)',
},
total_requested_enrichments: {
type: 'number',
description: 'Total requested enrichments (people_bulk_enrich)',
},
unique_enriched_records: {
type: 'number',
description: 'Unique enriched records (people_bulk_enrich)',
},
unique_domains: {
type: 'number',
description: 'Unique domains processed (organization_bulk_enrich)',
},
missing_records: {
type: 'number',
description: 'Missing records (people_bulk_enrich, organization_bulk_enrich)',
},
credits_consumed: {
type: 'number',
description: 'Credits consumed (people_bulk_enrich)',
},
},
}
+218 -3
View File
@@ -54,6 +54,15 @@ export const WizaBlock: BlockConfig<WizaResponse> = {
placeholder: '[{"v":"CEO","s":"i"},{"v":"Founder","s":"i"}]',
condition: { field: 'operation', value: 'prospect_search' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a Wiza job_title filter: a JSON array of include/exclude objects.
Each object is {"v": "<job title>", "s": "i" | "e"} where "i" includes and "e" excludes the title. Use double quotes around a phrase for an exact match.
Example: [{"v":"CEO","s":"i"},{"v":"intern","s":"e"}]
Return ONLY the JSON array - no explanations, no extra text.`,
placeholder: 'Describe the job titles to include/exclude...',
generationType: 'json-object',
},
},
{
id: 'job_title_level',
@@ -102,6 +111,15 @@ export const WizaBlock: BlockConfig<WizaResponse> = {
placeholder: '[{"v":{"country":"united states"},"b":"city","s":"i"}]',
condition: { field: 'operation', value: 'prospect_search' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a Wiza person location filter: a JSON array of location objects.
Each object is {"v": {"country": "...", "state": "...", "city": "..."}, "b": "country" | "state" | "city", "s": "i" | "e"} where "b" is the level to match, "s" includes ("i") or excludes ("e").
Example: [{"v":{"country":"united states","state":"california"},"b":"state","s":"i"}]
Return ONLY the JSON array - no explanations, no extra text.`,
placeholder: 'Describe the person locations to include/exclude...',
generationType: 'json-object',
},
},
{
id: 'skill',
@@ -142,6 +160,15 @@ export const WizaBlock: BlockConfig<WizaResponse> = {
placeholder: '[{"v":"wiza","s":"i"}]',
condition: { field: 'operation', value: 'prospect_search' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a Wiza current-company filter: a JSON array of include/exclude objects.
Each object is {"v": "<company name>", "s": "i" | "e"} where "i" includes and "e" excludes the company.
Example: [{"v":"wiza","s":"i"},{"v":"acme","s":"e"}]
Return ONLY the JSON array - no explanations, no extra text.`,
placeholder: 'Describe the current companies to include/exclude...',
generationType: 'json-object',
},
},
{
id: 'past_company',
@@ -150,6 +177,15 @@ export const WizaBlock: BlockConfig<WizaResponse> = {
placeholder: '[{"v":"google","s":"i"}]',
condition: { field: 'operation', value: 'prospect_search' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a Wiza past-company filter: a JSON array of include/exclude objects.
Each object is {"v": "<company name>", "s": "i" | "e"} where "i" includes and "e" excludes the company.
Example: [{"v":"google","s":"i"}]
Return ONLY the JSON array - no explanations, no extra text.`,
placeholder: 'Describe the past companies to include/exclude...',
generationType: 'json-object',
},
},
{
id: 'company_location',
@@ -158,6 +194,15 @@ export const WizaBlock: BlockConfig<WizaResponse> = {
placeholder: '[{"v":{"country":"canada"},"b":"country","s":"i"}]',
condition: { field: 'operation', value: 'prospect_search' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a Wiza company HQ location filter: a JSON array of location objects.
Each object is {"v": {"country": "...", "state": "...", "city": "..."}, "b": "country" | "state" | "city", "s": "i" | "e"} where "b" is the level to match, "s" includes ("i") or excludes ("e").
Example: [{"v":{"country":"canada","state":"ontario"},"b":"state","s":"i"}]
Return ONLY the JSON array - no explanations, no extra text.`,
placeholder: 'Describe the company HQ locations to include/exclude...',
generationType: 'json-object',
},
},
{
id: 'company_industry',
@@ -166,6 +211,15 @@ export const WizaBlock: BlockConfig<WizaResponse> = {
placeholder: '[{"v":"computer software","s":"i"}]',
condition: { field: 'operation', value: 'prospect_search' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a Wiza company industry filter: a JSON array of include/exclude objects.
Each object is {"v": "<industry>", "s": "i" | "e"} where "i" includes and "e" excludes the industry.
Example: [{"v":"computer software","s":"i"},{"v":"retail","s":"e"}]
Return ONLY the JSON array - no explanations, no extra text.`,
placeholder: 'Describe the company industries to include/exclude...',
generationType: 'json-object',
},
},
{
id: 'company_size',
@@ -190,6 +244,15 @@ export const WizaBlock: BlockConfig<WizaResponse> = {
placeholder: '{"job_title":[{"v":"CEO","s":"i"}], "company_size":["11-50"]}',
condition: { field: 'operation', value: 'prospect_search' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt: `Generate a Wiza prospect-search filters object as JSON.
Available keys: first_name, last_name (string arrays); job_title, job_company, past_company, company_industry (arrays of {"v":"...","s":"i"|"e"}); location, company_location (arrays of {"v":{country,state,city},"b":"country"|"state"|"city","s":"i"|"e"}); job_title_level, job_role, job_sub_role, skill, school, major, linkedin_slug, company_size, company_type (string arrays).
Example: {"job_title":[{"v":"CEO","s":"i"}],"company_size":["11-50","51-200"],"company_location":[{"v":{"country":"united states"},"b":"country","s":"i"}]}
Return ONLY the JSON object - no explanations, no extra text.`,
placeholder: 'Describe the prospects to search for...',
generationType: 'json-object',
},
},
// Company Enrichment
@@ -235,7 +298,7 @@ export const WizaBlock: BlockConfig<WizaResponse> = {
{ label: 'Phone', id: 'phone' },
{ label: 'Full', id: 'full' },
],
value: () => 'full',
value: () => 'partial',
condition: { field: 'operation', value: 'start_individual_reveal' },
required: { field: 'operation', value: 'start_individual_reveal' },
},
@@ -421,7 +484,159 @@ export const WizaBlock: BlockConfig<WizaResponse> = {
},
outputs: {
success: { type: 'boolean', description: 'Whether the operation was successful' },
output: { type: 'json', description: 'Output data from the Wiza operation' },
total: {
type: 'number',
description: 'Total prospects matching filters (prospect_search)',
},
profiles: {
type: 'json',
description:
'Sample prospect profiles (prospect_search): [{full_name, linkedin_url, industry, job_title, job_title_role, job_title_sub_role, job_company_name, job_company_website, location_name}]',
},
id: {
type: 'number',
description: 'Reveal ID (start_individual_reveal, get_individual_reveal)',
},
status: {
type: 'string',
description:
'Reveal status (start_individual_reveal, get_individual_reveal): queued | resolving | finished | failed',
},
is_complete: {
type: 'boolean',
description:
'Whether the reveal has completed (start_individual_reveal, get_individual_reveal)',
},
name: { type: 'string', description: 'Full name (get_individual_reveal)' },
company: { type: 'string', description: 'Company name (get_individual_reveal)' },
enrichment_level: {
type: 'string',
description: 'Enrichment level used (get_individual_reveal)',
},
linkedin_profile_url: { type: 'string', description: 'LinkedIn URL (get_individual_reveal)' },
title: { type: 'string', description: 'Job title (get_individual_reveal)' },
location: { type: 'string', description: 'Location (get_individual_reveal)' },
email: { type: 'string', description: 'Primary email (get_individual_reveal)' },
email_type: { type: 'string', description: 'Primary email type (get_individual_reveal)' },
email_status: {
type: 'string',
description: 'Primary email status: valid | risky | unfound (get_individual_reveal)',
},
emails: {
type: 'json',
description: 'All emails found (get_individual_reveal): [{email, email_type, email_status}]',
},
mobile_phone: { type: 'string', description: 'Mobile phone (get_individual_reveal)' },
phone_number: { type: 'string', description: 'Direct/office phone (get_individual_reveal)' },
phone_status: {
type: 'string',
description: 'Phone status: found | unfound (get_individual_reveal)',
},
phones: {
type: 'json',
description: 'All phones found (get_individual_reveal): [{number, pretty_number, type}]',
},
company_name: {
type: 'string',
description: 'Company name (company_enrichment)',
},
company_domain: {
type: 'string',
description: 'Company domain (company_enrichment, get_individual_reveal)',
},
domain: { type: 'string', description: 'Domain (company_enrichment)' },
company_industry: {
type: 'string',
description: 'Industry (company_enrichment, get_individual_reveal)',
},
company_size: {
type: 'number',
description: 'Employee count (company_enrichment, get_individual_reveal)',
},
company_size_range: {
type: 'string',
description: 'Headcount range (company_enrichment, get_individual_reveal)',
},
company_founded: {
type: 'number',
description: 'Year founded (company_enrichment, get_individual_reveal)',
},
company_revenue_range: {
type: 'string',
description: 'Revenue range (company_enrichment)',
},
company_revenue: { type: 'string', description: 'Revenue (get_individual_reveal)' },
company_funding: {
type: 'string',
description: 'Total funding (company_enrichment, get_individual_reveal)',
},
company_type: {
type: 'string',
description: 'Company type (company_enrichment, get_individual_reveal)',
},
company_description: {
type: 'string',
description: 'Company description (company_enrichment, get_individual_reveal)',
},
company_ticker: { type: 'string', description: 'Stock ticker (company_enrichment)' },
company_last_funding_round: {
type: 'string',
description: 'Last funding round (company_enrichment)',
},
company_last_funding_amount: {
type: 'string',
description: 'Last funding amount (company_enrichment)',
},
company_last_funding_at: {
type: 'string',
description: 'Last funding date (company_enrichment)',
},
company_location: {
type: 'string',
description: 'Full location string (company_enrichment, get_individual_reveal)',
},
company_twitter: { type: 'string', description: 'Twitter URL (company_enrichment)' },
company_facebook: { type: 'string', description: 'Facebook URL (company_enrichment)' },
company_linkedin: {
type: 'string',
description: 'LinkedIn URL (company_enrichment, get_individual_reveal)',
},
company_linkedin_id: { type: 'string', description: 'LinkedIn ID (company_enrichment)' },
company_street: {
type: 'string',
description: 'Street address (company_enrichment, get_individual_reveal)',
},
company_locality: {
type: 'string',
description: 'City (company_enrichment, get_individual_reveal)',
},
company_region: {
type: 'string',
description: 'State/region (company_enrichment, get_individual_reveal)',
},
company_postal_code: {
type: 'string',
description: 'Postal code (company_enrichment, get_individual_reveal)',
},
company_country: {
type: 'string',
description: 'Country (company_enrichment, get_individual_reveal)',
},
company_subindustry: { type: 'string', description: 'Subindustry (get_individual_reveal)' },
credits: {
type: 'json',
description:
'Credits deducted — company_enrichment: { api_credits: { total, company_credits } }; get_individual_reveal: { api_credits: { total, email_credits, phone_credits, scrape_credits } }',
},
email_credits: {
type: 'json',
description: 'Remaining email credits — number or "unlimited" (get_credits)',
},
phone_credits: {
type: 'json',
description: 'Remaining phone credits — number or "unlimited" (get_credits)',
},
export_credits: { type: 'number', description: 'Remaining export credits (get_credits)' },
api_credits: { type: 'number', description: 'Remaining API credits (get_credits)' },
},
}
+554
View File
@@ -0,0 +1,554 @@
import { ZoomInfoIcon } from '@/components/icons'
import type { BlockConfig } from '@/blocks/types'
import { AuthMode, IntegrationType } from '@/blocks/types'
import type { ZoomInfoResponse } from '@/tools/zoominfo/types'
export const ZoomInfoBlock: BlockConfig<ZoomInfoResponse> = {
type: 'zoominfo',
name: 'ZoomInfo',
description: 'Search and enrich B2B company and contact data with ZoomInfo.',
authMode: AuthMode.ApiKey,
longDescription:
'Integrates ZoomInfo into the workflow. Search companies and contacts, enrich firmographic and contact data, find intent signals, and pull news — all using the ZoomInfo GTM API.',
docsLink: 'https://docs.sim.ai/tools/zoominfo',
category: 'tools',
integrationType: IntegrationType.Sales,
tags: ['enrichment', 'sales-engagement'],
bgColor: '#EA1B15',
icon: ZoomInfoIcon,
subBlocks: [
{
id: 'operation',
title: 'Operation',
type: 'dropdown',
options: [
{ label: 'Search Companies', id: 'search_companies' },
{ label: 'Search Contacts', id: 'search_contacts' },
{ label: 'Enrich Companies', id: 'enrich_companies' },
{ label: 'Enrich Contacts', id: 'enrich_contacts' },
{ label: 'Search Intent', id: 'search_intent' },
{ label: 'Search News', id: 'search_news' },
],
value: () => 'search_companies',
},
{
id: 'clientId',
title: 'ZoomInfo Client ID',
type: 'short-input',
placeholder: 'Enter your ZoomInfo OAuth client ID',
required: true,
},
{
id: 'clientSecret',
title: 'ZoomInfo Client Secret',
type: 'short-input',
placeholder: 'Enter your ZoomInfo OAuth client secret',
password: true,
required: true,
},
// Search Companies
{
id: 'companyName',
title: 'Company Name',
type: 'short-input',
placeholder: 'Acme Corp',
condition: { field: 'operation', value: ['search_companies', 'search_contacts'] },
},
{
id: 'companyWebsite',
title: 'Company Website',
type: 'short-input',
placeholder: 'acme.com (comma-separated for multiple)',
condition: { field: 'operation', value: 'search_companies' },
},
{
id: 'companyTicker',
title: 'Ticker Symbol',
type: 'short-input',
placeholder: 'ACME',
condition: { field: 'operation', value: 'search_companies' },
mode: 'advanced',
},
{
id: 'industryCodes',
title: 'Industry Codes',
type: 'code',
placeholder: '["software","saas"]',
condition: { field: 'operation', value: ['search_companies', 'search_intent'] },
mode: 'advanced',
},
{
id: 'country',
title: 'Country',
type: 'short-input',
placeholder: 'United States',
condition: { field: 'operation', value: ['search_companies', 'search_intent'] },
mode: 'advanced',
},
{
id: 'state',
title: 'State / Province',
type: 'short-input',
placeholder: 'California',
condition: { field: 'operation', value: ['search_companies', 'search_intent'] },
mode: 'advanced',
},
{
id: 'metroRegion',
title: 'Metro Region',
type: 'short-input',
placeholder: 'San Francisco Bay Area',
condition: { field: 'operation', value: 'search_companies' },
mode: 'advanced',
},
{
id: 'revenueMin',
title: 'Min Revenue (thousands USD)',
type: 'short-input',
placeholder: '1000',
condition: { field: 'operation', value: 'search_companies' },
mode: 'advanced',
},
{
id: 'revenueMax',
title: 'Max Revenue (thousands USD)',
type: 'short-input',
placeholder: '100000',
condition: { field: 'operation', value: 'search_companies' },
mode: 'advanced',
},
{
id: 'employeeRangeMin',
title: 'Min Employees',
type: 'short-input',
placeholder: '10',
condition: { field: 'operation', value: 'search_companies' },
mode: 'advanced',
},
{
id: 'employeeRangeMax',
title: 'Max Employees',
type: 'short-input',
placeholder: '5000',
condition: { field: 'operation', value: 'search_companies' },
mode: 'advanced',
},
{
id: 'excludeDefunctCompanies',
title: 'Exclude Defunct Companies',
type: 'switch',
condition: { field: 'operation', value: 'search_companies' },
mode: 'advanced',
},
// Search Contacts
{
id: 'firstName',
title: 'First Name',
type: 'short-input',
placeholder: 'Jane',
condition: { field: 'operation', value: 'search_contacts' },
},
{
id: 'lastName',
title: 'Last Name',
type: 'short-input',
placeholder: 'Doe',
condition: { field: 'operation', value: 'search_contacts' },
},
{
id: 'fullName',
title: 'Full Name',
type: 'short-input',
placeholder: 'Jane Doe',
condition: { field: 'operation', value: 'search_contacts' },
mode: 'advanced',
},
{
id: 'emailAddress',
title: 'Email Address',
type: 'short-input',
placeholder: 'jane@acme.com',
condition: { field: 'operation', value: 'search_contacts' },
},
{
id: 'jobTitle',
title: 'Job Title',
type: 'short-input',
placeholder: 'VP of Marketing',
condition: { field: 'operation', value: 'search_contacts' },
},
{
id: 'managementLevel',
title: 'Management Level',
type: 'code',
placeholder: '["vp","director","manager"]',
condition: { field: 'operation', value: 'search_contacts' },
mode: 'advanced',
},
{
id: 'department',
title: 'Department',
type: 'code',
placeholder: '["sales","marketing"]',
condition: { field: 'operation', value: 'search_contacts' },
mode: 'advanced',
},
{
id: 'companyId',
title: 'Company ID',
type: 'short-input',
placeholder: 'ZoomInfo company ID',
condition: { field: 'operation', value: 'search_contacts' },
mode: 'advanced',
},
{
id: 'contactAccuracyScoreMin',
title: 'Min Accuracy Score',
type: 'short-input',
placeholder: '70-99',
condition: { field: 'operation', value: 'search_contacts' },
mode: 'advanced',
},
{
id: 'requiredFields',
title: 'Required Fields',
type: 'code',
placeholder: '["email","phone"]',
condition: { field: 'operation', value: ['search_contacts', 'enrich_contacts'] },
mode: 'advanced',
},
{
id: 'excludePartialProfiles',
title: 'Exclude Partial Profiles',
type: 'switch',
condition: { field: 'operation', value: 'search_contacts' },
mode: 'advanced',
},
// Enrich Companies / Contacts
{
id: 'matchCompanyInput',
title: 'Companies to Enrich (JSON Array)',
type: 'code',
placeholder: '[{"companyName":"Acme","companyWebsite":"acme.com"}]',
condition: { field: 'operation', value: 'enrich_companies' },
required: { field: 'operation', value: 'enrich_companies' },
},
{
id: 'matchPersonInput',
title: 'Contacts to Enrich (JSON Array)',
type: 'code',
placeholder: '[{"firstName":"Jane","lastName":"Doe","companyName":"Acme"}]',
condition: { field: 'operation', value: 'enrich_contacts' },
required: { field: 'operation', value: 'enrich_contacts' },
},
{
id: 'outputFields',
title: 'Output Fields',
type: 'code',
placeholder: '["id","name","website","revenue","employeeCount"]',
condition: { field: 'operation', value: ['enrich_companies', 'enrich_contacts'] },
mode: 'advanced',
},
// Search Intent
{
id: 'topics',
title: 'Intent Topics',
type: 'code',
placeholder: '["CRM Software","Marketing Automation"]',
condition: { field: 'operation', value: 'search_intent' },
required: { field: 'operation', value: 'search_intent' },
},
{
id: 'signalStartDate',
title: 'Signal Start Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD',
condition: { field: 'operation', value: 'search_intent' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt:
'Generate a date in YYYY-MM-DD format. Return ONLY the date string, no quotes or explanation.',
placeholder: 'Describe the date (e.g., "30 days ago")...',
generationType: 'timestamp',
},
},
{
id: 'signalEndDate',
title: 'Signal End Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD',
condition: { field: 'operation', value: 'search_intent' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt:
'Generate a date in YYYY-MM-DD format. Return ONLY the date string, no quotes or explanation.',
placeholder: 'Describe the date (e.g., "today")...',
generationType: 'timestamp',
},
},
{
id: 'signalScoreMin',
title: 'Min Signal Score (60-100)',
type: 'short-input',
placeholder: '60',
condition: { field: 'operation', value: 'search_intent' },
mode: 'advanced',
},
{
id: 'signalScoreMax',
title: 'Max Signal Score (60-100)',
type: 'short-input',
placeholder: '100',
condition: { field: 'operation', value: 'search_intent' },
mode: 'advanced',
},
{
id: 'audienceStrengthMin',
title: 'Min Audience Strength (A-E)',
type: 'dropdown',
options: [
{ label: 'A (largest)', id: 'A' },
{ label: 'B', id: 'B' },
{ label: 'C', id: 'C' },
{ label: 'D', id: 'D' },
{ label: 'E', id: 'E' },
],
condition: { field: 'operation', value: 'search_intent' },
mode: 'advanced',
},
{
id: 'audienceStrengthMax',
title: 'Max Audience Strength (A-E)',
type: 'dropdown',
options: [
{ label: 'A (largest)', id: 'A' },
{ label: 'B', id: 'B' },
{ label: 'C', id: 'C' },
{ label: 'D', id: 'D' },
{ label: 'E', id: 'E' },
],
condition: { field: 'operation', value: 'search_intent' },
mode: 'advanced',
},
{
id: 'findRecommendedContacts',
title: 'Include Recommended Contacts',
type: 'switch',
condition: { field: 'operation', value: 'search_intent' },
mode: 'advanced',
},
// Search News
{
id: 'categories',
title: 'Categories',
type: 'code',
placeholder: '["funding","acquisition"]',
condition: { field: 'operation', value: 'search_news' },
},
{
id: 'url',
title: 'Source URLs',
type: 'code',
placeholder: '["https://techcrunch.com"]',
condition: { field: 'operation', value: 'search_news' },
mode: 'advanced',
},
{
id: 'pageDateMin',
title: 'Earliest Publish Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD',
condition: { field: 'operation', value: 'search_news' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt:
'Generate a date in YYYY-MM-DD format. Return ONLY the date string, no quotes or explanation.',
placeholder: 'Describe the date (e.g., "last week")...',
generationType: 'timestamp',
},
},
{
id: 'pageDateMax',
title: 'Latest Publish Date',
type: 'short-input',
placeholder: 'YYYY-MM-DD',
condition: { field: 'operation', value: 'search_news' },
mode: 'advanced',
wandConfig: {
enabled: true,
prompt:
'Generate a date in YYYY-MM-DD format. Return ONLY the date string, no quotes or explanation.',
placeholder: 'Describe the date (e.g., "today")...',
generationType: 'timestamp',
},
},
// Sort + Pagination
{
id: 'sortBy',
title: 'Sort By Field',
type: 'short-input',
placeholder: 'Field name',
condition: { field: 'operation', value: ['search_companies', 'search_contacts'] },
mode: 'advanced',
},
{
id: 'sortOrder',
title: 'Sort Order',
type: 'dropdown',
options: [
{ label: 'Ascending', id: 'asc' },
{ label: 'Descending', id: 'desc' },
],
condition: { field: 'operation', value: ['search_companies', 'search_contacts'] },
mode: 'advanced',
},
{
id: 'page',
title: 'Page Number',
type: 'short-input',
placeholder: '1',
condition: {
field: 'operation',
value: ['search_companies', 'search_contacts', 'search_intent', 'search_news'],
},
mode: 'advanced',
},
{
id: 'rpp',
title: 'Results Per Page',
type: 'short-input',
placeholder: '25 (max 100)',
condition: {
field: 'operation',
value: ['search_companies', 'search_contacts', 'search_intent', 'search_news'],
},
mode: 'advanced',
},
],
tools: {
access: [
'zoominfo_search_companies',
'zoominfo_search_contacts',
'zoominfo_enrich_companies',
'zoominfo_enrich_contacts',
'zoominfo_search_intent',
'zoominfo_search_news',
],
config: {
tool: (params) => {
switch (params.operation) {
case 'search_companies':
return 'zoominfo_search_companies'
case 'search_contacts':
return 'zoominfo_search_contacts'
case 'enrich_companies':
return 'zoominfo_enrich_companies'
case 'enrich_contacts':
return 'zoominfo_enrich_contacts'
case 'search_intent':
return 'zoominfo_search_intent'
case 'search_news':
return 'zoominfo_search_news'
default:
throw new Error(`Invalid ZoomInfo operation: ${params.operation}`)
}
},
params: (params) => {
const { operation: _operation, ...rest } = params as Record<string, unknown>
const parsed: Record<string, unknown> = { ...rest }
const toNumber = (key: string) => {
const v = parsed[key]
if (v === undefined || v === null || v === '') return
const n = Number(v)
if (Number.isFinite(n)) parsed[key] = n
}
for (const key of [
'revenueMin',
'revenueMax',
'employeeRangeMin',
'employeeRangeMax',
'contactAccuracyScoreMin',
'signalScoreMin',
'signalScoreMax',
'page',
'rpp',
]) {
toNumber(key)
}
return parsed
},
},
},
inputs: {
operation: { type: 'string', description: 'ZoomInfo operation to perform' },
clientId: { type: 'string', description: 'ZoomInfo OAuth client ID' },
clientSecret: { type: 'string', description: 'ZoomInfo OAuth client secret' },
companyName: { type: 'string', description: 'Company name' },
companyWebsite: { type: 'string', description: 'Company website' },
companyTicker: { type: 'string', description: 'Stock ticker' },
industryCodes: { type: 'string', description: 'Industry codes' },
country: { type: 'string', description: 'Country' },
state: { type: 'string', description: 'State' },
metroRegion: { type: 'string', description: 'Metro region' },
revenueMin: { type: 'number', description: 'Min revenue (thousands USD)' },
revenueMax: { type: 'number', description: 'Max revenue (thousands USD)' },
employeeRangeMin: { type: 'number', description: 'Min employees' },
employeeRangeMax: { type: 'number', description: 'Max employees' },
excludeDefunctCompanies: { type: 'boolean', description: 'Exclude defunct companies' },
firstName: { type: 'string', description: 'First name' },
lastName: { type: 'string', description: 'Last name' },
fullName: { type: 'string', description: 'Full name' },
emailAddress: { type: 'string', description: 'Email address' },
jobTitle: { type: 'string', description: 'Job title' },
managementLevel: { type: 'string', description: 'Management level' },
department: { type: 'string', description: 'Department' },
companyId: { type: 'string', description: 'Company ID' },
contactAccuracyScoreMin: { type: 'number', description: 'Min accuracy score' },
requiredFields: { type: 'string', description: 'Required fields' },
excludePartialProfiles: { type: 'boolean', description: 'Exclude partial profiles' },
matchCompanyInput: { type: 'string', description: 'Companies to enrich (JSON array)' },
matchPersonInput: { type: 'string', description: 'Contacts to enrich (JSON array)' },
outputFields: { type: 'string', description: 'Output fields' },
topics: { type: 'string', description: 'Intent topics' },
signalStartDate: { type: 'string', description: 'Signal start date' },
signalEndDate: { type: 'string', description: 'Signal end date' },
signalScoreMin: { type: 'number', description: 'Min signal score' },
signalScoreMax: { type: 'number', description: 'Max signal score' },
audienceStrengthMin: { type: 'string', description: 'Min audience strength (A-E)' },
audienceStrengthMax: { type: 'string', description: 'Max audience strength (A-E)' },
findRecommendedContacts: { type: 'boolean', description: 'Include recommended contacts' },
categories: { type: 'string', description: 'News categories' },
url: { type: 'string', description: 'News source URLs' },
pageDateMin: { type: 'string', description: 'Earliest publish date' },
pageDateMax: { type: 'string', description: 'Latest publish date' },
sortBy: { type: 'string', description: 'Sort field' },
sortOrder: { type: 'string', description: 'Sort order' },
page: { type: 'number', description: 'Page number' },
rpp: { type: 'number', description: 'Results per page' },
},
outputs: {
companies: { type: 'json', description: 'Matching companies (search_companies)' },
contacts: { type: 'json', description: 'Matching contacts (search_contacts)' },
results: {
type: 'json',
description: 'Enrichment results (enrich_companies / enrich_contacts)',
},
signals: { type: 'json', description: 'Intent signals (search_intent)' },
articles: { type: 'json', description: 'News articles (search_news)' },
totalResults: { type: 'number', description: 'Total matching results across all pages' },
currentPage: { type: 'number', description: 'Current page number' },
totalPages: { type: 'number', description: 'Total number of pages available' },
},
}
+2
View File
@@ -243,6 +243,7 @@ import { YouTubeBlock } from '@/blocks/blocks/youtube'
import { ZendeskBlock } from '@/blocks/blocks/zendesk'
import { ZepBlock } from '@/blocks/blocks/zep'
import { ZoomBlock } from '@/blocks/blocks/zoom'
import { ZoomInfoBlock } from '@/blocks/blocks/zoominfo'
import type { BlockConfig } from '@/blocks/types'
// Registry of all available blocks, alphabetically sorted
@@ -513,6 +514,7 @@ export const registry: Record<string, BlockConfig> = {
zendesk: ZendeskBlock,
zep: ZepBlock,
zoom: ZoomBlock,
zoominfo: ZoomInfoBlock,
}
export const getBlock = (type: string): BlockConfig | undefined => {
+30
View File
@@ -4683,6 +4683,36 @@ export function ZoomIcon(props: SVGProps<SVGSVGElement>) {
)
}
export function ZoomInfoIcon(props: SVGProps<SVGSVGElement>) {
const id = useId()
const clipId = `zoominfo-clip_${id}`
return (
<svg
{...props}
viewBox='0 0 65 65'
fill='none'
xmlns='http://www.w3.org/2000/svg'
aria-hidden='true'
>
<g clipPath={`url(#${clipId})`}>
<path
d='M0.272461 8.27246C0.272461 3.85418 3.85418 0.272461 8.27246 0.272461H56.2725C60.6907 0.272461 64.2725 3.85418 64.2725 8.27246V56.2725C64.2725 60.6907 60.6907 64.2725 56.2725 64.2725H8.27246C3.85418 64.2725 0.272461 60.6907 0.272461 56.2725V8.27246Z'
fill='#EA1B15'
/>
<path
d='M44.2725 16.2725H47.7096C48.0212 16.2725 48.2726 16.5239 48.2726 16.8356V25.6993C48.2726 26.011 48.0313 26.2624 47.7196 26.2725H44.8355C44.5239 26.2725 44.2725 26.0211 44.2725 25.7093V23.1162L22.0381 44.2725H27.7095C28.0212 44.2725 28.2725 44.524 28.2725 44.8357V47.7094C28.2725 48.0211 28.0212 48.2726 27.7095 48.2726H16.8355C16.5238 48.2726 16.2725 48.0211 16.2725 47.7094V44.2725L41.4757 20.2725H38.8355C38.5239 20.2725 38.2725 20.0211 38.2725 19.7093V16.8457C38.2725 16.5339 38.5239 16.2825 38.8355 16.2825L44.2725 16.2725ZM24.2725 20.2725L33.7095 20.2624C34.0212 20.2624 34.2725 20.011 34.2725 19.6992V16.8356C34.2725 16.5239 34.0212 16.2725 33.7196 16.2725H16.8556C16.5439 16.2725 16.2926 16.5239 16.2926 16.8356V25.7093C16.2926 26.0211 16.5439 26.2725 16.8556 26.2725H19.7195C20.0312 26.2725 20.2725 26.0211 20.2725 25.7093V20.2725H24.2725ZM44.2725 44.2725H32.8355C32.5238 44.2725 32.2725 44.524 32.2725 44.8357V47.7094C32.2725 48.0211 32.5238 48.2726 32.8355 48.2726H47.7096C48.0212 48.2726 48.2726 48.0211 48.2726 47.7094V38.7888C48.2726 38.4771 48.0212 38.2257 47.7096 38.2257H44.8355C44.5239 38.2257 44.2725 38.4771 44.2725 38.7888V44.2725Z'
fill='white'
/>
</g>
<defs>
<clipPath id={clipId}>
<rect width='64' height='64' fill='white' transform='translate(0.272461 0.272461)' />
</clipPath>
</defs>
</svg>
)
}
export function SendgridIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
+1 -1
View File
@@ -18,7 +18,7 @@ export const apolloAccountBulkCreateTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key (master key required)',
},
accounts: {
+44 -8
View File
@@ -18,7 +18,7 @@ export const apolloAccountBulkUpdateTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key (master key required)',
},
account_ids: {
@@ -40,6 +40,12 @@ export const apolloAccountBulkUpdateTool: ToolConfig<
visibility: 'user-or-llm',
description: 'When using account_ids, apply this owner to all accounts',
},
account_stage_id: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'When using account_ids, apply this account stage to all accounts',
},
account_attributes: {
type: 'json',
required: false,
@@ -71,6 +77,7 @@ export const apolloAccountBulkUpdateTool: ToolConfig<
}
if (params.name) body.name = params.name
if (params.owner_id) body.owner_id = params.owner_id
if (params.account_stage_id) body.account_stage_id = params.account_stage_id
if (params.account_attributes) {
if (Array.isArray(params.account_attributes)) {
if (params.account_attributes.length > 0) {
@@ -83,7 +90,8 @@ export const apolloAccountBulkUpdateTool: ToolConfig<
body.account_attributes = params.account_attributes
}
}
const hasUpdateFields = body.account_attributes || body.name || body.owner_id
const hasUpdateFields =
body.account_attributes || body.name || body.owner_id || body.account_stage_id
if (!hasUpdateFields) {
throw new Error(
'Apollo account bulk update requires update fields. Provide account_attributes (array of per-account updates with id, or single object paired with account_ids), or pair account_ids with name/owner_id to apply uniformly.'
@@ -116,25 +124,53 @@ export const apolloAccountBulkUpdateTool: ToolConfig<
}
const data = await response.json()
const accounts = Array.isArray(data?.accounts) ? data.accounts : []
const entityProgressJob = data?.entity_progress_job ?? null
const accountIds = Array.isArray(data?.account_ids)
? data.account_ids
: accounts.map((a: { id?: unknown }) => a?.id).filter((id: unknown) => typeof id === 'string')
const jobId =
typeof data?.job_id === 'string'
? data.job_id
: typeof entityProgressJob?.id === 'string'
? entityProgressJob.id
: null
return {
success: true,
output: {
message: data.message ?? null,
account_ids: data.account_ids ?? [],
accounts,
account_ids: accountIds,
entity_progress_job: entityProgressJob,
job_id: jobId,
message: data?.message ?? null,
},
}
},
outputs: {
message: {
type: 'string',
description: 'Confirmation message from Apollo',
optional: true,
accounts: {
type: 'json',
description: 'Updated accounts (synchronous response): [{id, account_stage_id, ...}]',
},
account_ids: {
type: 'json',
description: 'IDs of accounts that were updated',
},
entity_progress_job: {
type: 'json',
description: 'Async job descriptor (when async=true is passed with account_ids)',
optional: true,
},
job_id: {
type: 'string',
description: 'Async job ID extracted from entity_progress_job',
optional: true,
},
message: {
type: 'string',
description: 'Optional confirmation message from Apollo',
optional: true,
},
},
}
+1 -1
View File
@@ -14,7 +14,7 @@ export const apolloAccountCreateTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key (master key required)',
},
name: {
+1 -1
View File
@@ -15,7 +15,7 @@ export const apolloAccountSearchTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key (master key required)',
},
q_organization_name: {
+1 -1
View File
@@ -14,7 +14,7 @@ export const apolloAccountUpdateTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key',
},
account_id: {
+1 -1
View File
@@ -18,7 +18,7 @@ export const apolloContactBulkCreateTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key (master key required)',
},
contacts: {
+26 -7
View File
@@ -18,7 +18,7 @@ export const apolloContactBulkUpdateTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key (master key required)',
},
contact_ids: {
@@ -95,25 +95,44 @@ export const apolloContactBulkUpdateTool: ToolConfig<
}
const data = await response.json()
const contacts = Array.isArray(data?.contacts) ? data.contacts : []
const entityProgressJob = data?.entity_progress_job ?? null
const jobId =
typeof data?.job_id === 'string'
? data.job_id
: typeof entityProgressJob?.id === 'string'
? entityProgressJob.id
: null
return {
success: true,
output: {
message: data.message ?? null,
job_id: data.job_id ?? null,
contacts,
entity_progress_job: entityProgressJob,
job_id: jobId,
message: data?.message ?? null,
},
}
},
outputs: {
message: {
type: 'string',
description: 'Confirmation message from Apollo',
contacts: {
type: 'json',
description: 'Updated contacts (synchronous response, ≤100 contacts)',
},
entity_progress_job: {
type: 'json',
description: 'Async job descriptor (>100 contacts or async=true): {id, status, ...}',
optional: true,
},
job_id: {
type: 'string',
description: 'Async job ID (returned for >100 contacts)',
description: 'Async job ID extracted from entity_progress_job',
optional: true,
},
message: {
type: 'string',
description: 'Optional confirmation message from Apollo',
optional: true,
},
},
+1 -1
View File
@@ -14,7 +14,7 @@ export const apolloContactCreateTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key',
},
first_name: {
+1 -1
View File
@@ -14,7 +14,7 @@ export const apolloContactSearchTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key',
},
q_keywords: {
+2 -3
View File
@@ -14,7 +14,7 @@ export const apolloContactUpdateTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key',
},
contact_id: {
@@ -124,8 +124,7 @@ export const apolloContactUpdateTool: ToolConfig<
type: 'json',
required: false,
visibility: 'user-or-llm',
description:
'Custom field values keyed by custom field ID (accepted by Apollo but not officially documented for PATCH /contacts/{id})',
description: 'Custom field values keyed by custom field ID',
},
},
+1 -1
View File
@@ -14,7 +14,7 @@ export const apolloEmailAccountsTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key (master key required)',
},
},
+1 -1
View File
@@ -17,7 +17,7 @@ export const apolloOpportunityCreateTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key (master key required)',
},
name: {
+1 -1
View File
@@ -14,7 +14,7 @@ export const apolloOpportunityGetTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key',
},
opportunity_id: {
+1 -1
View File
@@ -17,7 +17,7 @@ export const apolloOpportunitySearchTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key',
},
sort_by_field: {
+1 -1
View File
@@ -17,7 +17,7 @@ export const apolloOpportunityUpdateTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key',
},
opportunity_id: {
@@ -17,29 +17,32 @@ export const apolloOrganizationBulkEnrichTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key',
},
organizations: {
domains: {
type: 'array',
required: true,
visibility: 'user-or-llm',
description:
'Array of organizations to enrich (max 10). Each item requires `name` and may include `domain` (e.g., [{"name": "Example Corp", "domain": "example.com"}])',
'Array of company domains to enrich (max 10, no www. or @, e.g., ["apollo.io", "stripe.com"])',
},
},
request: {
url: 'https://api.apollo.io/api/v1/organizations/bulk_enrich',
url: (params: ApolloOrganizationBulkEnrichParams) => {
const qs = new URLSearchParams()
for (const domain of params.domains.slice(0, 10)) {
const trimmed = typeof domain === 'string' ? domain.trim() : ''
if (trimmed) qs.append('domains[]', trimmed)
}
return `https://api.apollo.io/api/v1/organizations/bulk_enrich?${qs.toString()}`
},
method: 'POST',
headers: (params: ApolloOrganizationBulkEnrichParams) => ({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'X-Api-Key': params.apiKey,
}),
body: (params: ApolloOrganizationBulkEnrichParams) => ({
organizations: params.organizations.slice(0, 10),
}),
},
transformResponse: async (response: Response) => {
+9 -10
View File
@@ -17,7 +17,7 @@ export const apolloOrganizationEnrichTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key',
},
domain: {
@@ -29,20 +29,19 @@ export const apolloOrganizationEnrichTool: ToolConfig<
},
request: {
url: 'https://api.apollo.io/api/v1/organizations/enrich',
method: 'POST',
headers: (params: ApolloOrganizationEnrichParams) => ({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'X-Api-Key': params.apiKey,
}),
body: (params: ApolloOrganizationEnrichParams) => {
url: (params: ApolloOrganizationEnrichParams) => {
const domain = params.domain?.trim()
if (!domain) {
throw new Error('domain is required for organization enrichment')
}
return { domain }
const qs = new URLSearchParams({ domain })
return `https://api.apollo.io/api/v1/organizations/enrich?${qs.toString()}`
},
method: 'GET',
headers: (params: ApolloOrganizationEnrichParams) => ({
'Cache-Control': 'no-cache',
'X-Api-Key': params.apiKey,
}),
},
transformResponse: async (response: Response) => {
+22 -33
View File
@@ -17,7 +17,7 @@ export const apolloOrganizationSearchTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key',
},
organization_locations: {
@@ -78,43 +78,32 @@ export const apolloOrganizationSearchTool: ToolConfig<
},
request: {
url: 'https://api.apollo.io/api/v1/mixed_companies/search',
url: (params: ApolloOrganizationSearchParams) => {
const qs = new URLSearchParams()
qs.set('page', String(params.page || 1))
qs.set('per_page', String(Math.min(params.per_page || 25, 100)))
const appendArray = (key: string, values?: string[]) => {
if (!values?.length) return
for (const v of values) {
if (typeof v === 'string' && v) qs.append(`${key}[]`, v)
}
}
appendArray('organization_locations', params.organization_locations)
appendArray('organization_not_locations', params.organization_not_locations)
appendArray('organization_num_employees_ranges', params.organization_num_employees_ranges)
appendArray('q_organization_keyword_tags', params.q_organization_keyword_tags)
appendArray('organization_ids', params.organization_ids)
appendArray('q_organization_domains_list', params.q_organization_domains_list)
if (params.q_organization_name) qs.set('q_organization_name', params.q_organization_name)
return `https://api.apollo.io/api/v1/mixed_companies/search?${qs.toString()}`
},
method: 'POST',
headers: (params: ApolloOrganizationSearchParams) => ({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'X-Api-Key': params.apiKey,
}),
body: (params: ApolloOrganizationSearchParams) => {
const body: Record<string, unknown> = {
page: params.page || 1,
per_page: Math.min(params.per_page || 25, 100),
}
if (params.organization_locations?.length) {
body.organization_locations = params.organization_locations
}
if (params.organization_not_locations?.length) {
body.organization_not_locations = params.organization_not_locations
}
if (params.organization_num_employees_ranges?.length) {
body.organization_num_employees_ranges = params.organization_num_employees_ranges
}
if (params.q_organization_keyword_tags?.length) {
body.q_organization_keyword_tags = params.q_organization_keyword_tags
}
if (params.q_organization_name) {
body.q_organization_name = params.q_organization_name
}
if (params.organization_ids?.length) {
body.organization_ids = params.organization_ids
}
if (params.q_organization_domains_list?.length) {
body.q_organization_domains_list = params.q_organization_domains_list
}
return body
},
},
transformResponse: async (response: Response) => {
+1 -1
View File
@@ -17,7 +17,7 @@ export const apolloPeopleBulkEnrichTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key',
},
people: {
+10 -17
View File
@@ -14,7 +14,7 @@ export const apolloPeopleEnrichTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key',
},
first_name: {
@@ -95,6 +95,15 @@ export const apolloPeopleEnrichTool: ToolConfig<
request: {
url: (params: ApolloPeopleEnrichParams) => {
const qs = new URLSearchParams()
if (params.first_name) qs.set('first_name', params.first_name)
if (params.last_name) qs.set('last_name', params.last_name)
if (params.name) qs.set('name', params.name)
if (params.email) qs.set('email', params.email)
if (params.hashed_email) qs.set('hashed_email', params.hashed_email)
if (params.id) qs.set('id', params.id)
if (params.organization_name) qs.set('organization_name', params.organization_name)
if (params.domain) qs.set('domain', params.domain)
if (params.linkedin_url) qs.set('linkedin_url', params.linkedin_url)
if (params.reveal_personal_emails !== undefined) {
qs.set('reveal_personal_emails', String(params.reveal_personal_emails))
}
@@ -109,25 +118,9 @@ export const apolloPeopleEnrichTool: ToolConfig<
},
method: 'POST',
headers: (params: ApolloPeopleEnrichParams) => ({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'X-Api-Key': params.apiKey,
}),
body: (params: ApolloPeopleEnrichParams) => {
const body: Record<string, unknown> = {}
if (params.first_name) body.first_name = params.first_name
if (params.last_name) body.last_name = params.last_name
if (params.name) body.name = params.name
if (params.email) body.email = params.email
if (params.hashed_email) body.hashed_email = params.hashed_email
if (params.id) body.id = params.id
if (params.organization_name) body.organization_name = params.organization_name
if (params.domain) body.domain = params.domain
if (params.linkedin_url) body.linkedin_url = params.linkedin_url
return body
},
},
transformResponse: async (response: Response) => {
+8 -4
View File
@@ -14,7 +14,7 @@ export const apolloPeopleSearchTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key',
},
person_titles: {
@@ -165,13 +165,17 @@ export const apolloPeopleSearchTool: ToolConfig<
const data = await response.json()
// The legacy /mixed_people/search endpoint nests pagination under `pagination`,
// while api_search returns these top-level. Read both shapes defensively.
const pagination = data.pagination ?? {}
return {
success: true,
output: {
people: data.people || [],
page: data.pagination?.page || 1,
per_page: data.pagination?.per_page || 25,
total_entries: data.pagination?.total_entries || 0,
page: pagination.page ?? data.page ?? 1,
per_page: pagination.per_page ?? data.per_page ?? 25,
total_entries: pagination.total_entries ?? data.total_entries ?? 0,
},
}
},
+50 -29
View File
@@ -17,7 +17,7 @@ export const apolloSequenceAddContactsTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key (master key required)',
},
sequence_id: {
@@ -128,15 +128,12 @@ export const apolloSequenceAddContactsTool: ToolConfig<
},
request: {
url: (params: ApolloSequenceAddContactsParams) =>
`https://api.apollo.io/api/v1/emailer_campaigns/${params.sequence_id.trim()}/add_contact_ids`,
method: 'POST',
headers: (params: ApolloSequenceAddContactsParams) => ({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'X-Api-Key': params.apiKey,
}),
body: (params: ApolloSequenceAddContactsParams) => {
/**
* Apollo documents every field for this endpoint as a query parameter (no request body),
* so `contact_ids[]`/`label_names[]` are appended to the query string alongside the scalar
* settings, matching the documented contract.
*/
url: (params: ApolloSequenceAddContactsParams) => {
const hasContactIds = !!params.contact_ids?.length
const hasLabelNames = !!params.label_names?.length
if (!hasContactIds && !hasLabelNames) {
@@ -144,43 +141,67 @@ export const apolloSequenceAddContactsTool: ToolConfig<
'Apollo sequence add requires either contact_ids or label_names to be provided'
)
}
const body: Record<string, unknown> = {
emailer_campaign_id: params.sequence_id,
send_email_from_email_account_id: params.send_email_from_email_account_id,
const qs = new URLSearchParams()
qs.set('emailer_campaign_id', params.sequence_id)
qs.set('send_email_from_email_account_id', params.send_email_from_email_account_id)
for (const id of params.contact_ids ?? []) {
if (typeof id === 'string' && id.length > 0) qs.append('contact_ids[]', id)
}
for (const name of params.label_names ?? []) {
if (typeof name === 'string' && name.length > 0) qs.append('label_names[]', name)
}
if (hasContactIds) body.contact_ids = params.contact_ids
if (hasLabelNames) body.label_names = params.label_names
if (params.send_email_from_email_address) {
body.send_email_from_email_address = params.send_email_from_email_address
qs.set('send_email_from_email_address', params.send_email_from_email_address)
}
if (params.sequence_no_email !== undefined) {
qs.set('sequence_no_email', String(params.sequence_no_email))
}
if (params.sequence_no_email !== undefined) body.sequence_no_email = params.sequence_no_email
if (params.sequence_unverified_email !== undefined) {
body.sequence_unverified_email = params.sequence_unverified_email
qs.set('sequence_unverified_email', String(params.sequence_unverified_email))
}
if (params.sequence_job_change !== undefined) {
body.sequence_job_change = params.sequence_job_change
qs.set('sequence_job_change', String(params.sequence_job_change))
}
if (params.sequence_active_in_other_campaigns !== undefined) {
body.sequence_active_in_other_campaigns = params.sequence_active_in_other_campaigns
qs.set(
'sequence_active_in_other_campaigns',
String(params.sequence_active_in_other_campaigns)
)
}
if (params.sequence_finished_in_other_campaigns !== undefined) {
body.sequence_finished_in_other_campaigns = params.sequence_finished_in_other_campaigns
qs.set(
'sequence_finished_in_other_campaigns',
String(params.sequence_finished_in_other_campaigns)
)
}
if (params.sequence_same_company_in_same_campaign !== undefined) {
body.sequence_same_company_in_same_campaign = params.sequence_same_company_in_same_campaign
qs.set(
'sequence_same_company_in_same_campaign',
String(params.sequence_same_company_in_same_campaign)
)
}
if (params.contacts_without_ownership_permission !== undefined) {
body.contacts_without_ownership_permission = params.contacts_without_ownership_permission
qs.set(
'contacts_without_ownership_permission',
String(params.contacts_without_ownership_permission)
)
}
if (params.add_if_in_queue !== undefined) {
qs.set('add_if_in_queue', String(params.add_if_in_queue))
}
if (params.add_if_in_queue !== undefined) body.add_if_in_queue = params.add_if_in_queue
if (params.contact_verification_skipped !== undefined) {
body.contact_verification_skipped = params.contact_verification_skipped
qs.set('contact_verification_skipped', String(params.contact_verification_skipped))
}
if (params.user_id) body.user_id = params.user_id
if (params.status) body.status = params.status
if (params.auto_unpause_at) body.auto_unpause_at = params.auto_unpause_at
return body
if (params.user_id) qs.set('user_id', params.user_id)
if (params.status) qs.set('status', params.status)
if (params.auto_unpause_at) qs.set('auto_unpause_at', params.auto_unpause_at)
return `https://api.apollo.io/api/v1/emailer_campaigns/${params.sequence_id.trim()}/add_contact_ids?${qs.toString()}`
},
method: 'POST',
headers: (params: ApolloSequenceAddContactsParams) => ({
'Cache-Control': 'no-cache',
'X-Api-Key': params.apiKey,
}),
},
transformResponse: async (response: Response, params?: ApolloSequenceAddContactsParams) => {
+8 -11
View File
@@ -14,7 +14,7 @@ export const apolloSequenceSearchTool: ToolConfig<
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key (master key required)',
},
q_name: {
@@ -38,21 +38,18 @@ export const apolloSequenceSearchTool: ToolConfig<
},
request: {
url: 'https://api.apollo.io/api/v1/emailer_campaigns/search',
url: (params: ApolloSequenceSearchParams) => {
const qs = new URLSearchParams()
qs.set('page', String(params.page || 1))
qs.set('per_page', String(Math.min(params.per_page || 25, 100)))
if (params.q_name) qs.set('q_name', params.q_name)
return `https://api.apollo.io/api/v1/emailer_campaigns/search?${qs.toString()}`
},
method: 'POST',
headers: (params: ApolloSequenceSearchParams) => ({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'X-Api-Key': params.apiKey,
}),
body: (params: ApolloSequenceSearchParams) => {
const body: Record<string, unknown> = {
page: params.page || 1,
per_page: Math.min(params.per_page || 25, 100),
}
if (params.q_name) body.q_name = params.q_name
return body
},
},
transformResponse: async (response: Response) => {
+1 -1
View File
@@ -11,7 +11,7 @@ export const apolloTaskCreateTool: ToolConfig<ApolloTaskCreateParams, ApolloTask
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key (master key required)',
},
user_id: {
+13 -12
View File
@@ -11,7 +11,7 @@ export const apolloTaskSearchTool: ToolConfig<ApolloTaskSearchParams, ApolloTask
apiKey: {
type: 'string',
required: true,
visibility: 'hidden',
visibility: 'user-only',
description: 'Apollo API key (master key required)',
},
sort_by_field: {
@@ -42,22 +42,23 @@ export const apolloTaskSearchTool: ToolConfig<ApolloTaskSearchParams, ApolloTask
},
request: {
url: 'https://api.apollo.io/api/v1/tasks/search',
url: (params: ApolloTaskSearchParams) => {
const qs = new URLSearchParams()
qs.set('page', String(params.page || 1))
qs.set('per_page', String(Math.min(params.per_page || 25, 100)))
if (params.sort_by_field) qs.set('sort_by_field', params.sort_by_field)
if (params.open_factor_names?.length) {
for (const name of params.open_factor_names) {
if (typeof name === 'string' && name) qs.append('open_factor_names[]', name)
}
}
return `https://api.apollo.io/api/v1/tasks/search?${qs.toString()}`
},
method: 'POST',
headers: (params: ApolloTaskSearchParams) => ({
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'X-Api-Key': params.apiKey,
}),
body: (params: ApolloTaskSearchParams) => {
const body: Record<string, unknown> = {
page: params.page || 1,
per_page: Math.min(params.per_page || 25, 100),
}
if (params.sort_by_field) body.sort_by_field = params.sort_by_field
if (params.open_factor_names?.length) body.open_factor_names = params.open_factor_names
return body
},
},
transformResponse: async (response: Response) => {
+9 -3
View File
@@ -229,7 +229,7 @@ export interface ApolloOrganizationEnrichResponse extends ToolResponse {
// Bulk Organization Enrichment Types
export interface ApolloOrganizationBulkEnrichParams extends ApolloBaseParams {
organizations: Array<{ name: string; domain?: string }>
domains: string[]
}
export interface ApolloOrganizationBulkEnrichResponse extends ToolResponse {
@@ -344,8 +344,10 @@ export interface ApolloContactBulkUpdateParams extends ApolloBaseParams {
export interface ApolloContactBulkUpdateResponse extends ToolResponse {
output: {
message: string | null
contacts: ApolloContact[]
entity_progress_job: Record<string, unknown> | null
job_id: string | null
message: string | null
}
}
@@ -466,14 +468,18 @@ export interface ApolloAccountBulkUpdateParams extends ApolloBaseParams {
account_ids?: string[]
name?: string
owner_id?: string
account_stage_id?: string
account_attributes?: Array<{ id: string; [key: string]: unknown }> | Record<string, unknown>
async?: boolean
}
export interface ApolloAccountBulkUpdateResponse extends ToolResponse {
output: {
message: string | null
accounts: ApolloAccount[]
account_ids: string[]
entity_progress_job: Record<string, unknown> | null
job_id: string | null
message: string | null
}
}
@@ -49,6 +49,7 @@ export const findEmailFromLinkedInTool: ToolConfig<
success: false,
error:
(errorData as Record<string, string>).message ||
(errorData as Record<string, string>).error ||
`Findymail API error: ${response.status} ${response.statusText}`,
output: { contact: null },
}
@@ -54,6 +54,7 @@ export const findEmailFromNameTool: ToolConfig<
success: false,
error:
(errorData as Record<string, string>).message ||
(errorData as Record<string, string>).error ||
`Findymail API error: ${response.status} ${response.statusText}`,
output: { contact: null },
}
@@ -55,6 +55,7 @@ export const findEmailsByDomainTool: ToolConfig<
success: false,
error:
(errorData as Record<string, string>).message ||
(errorData as Record<string, string>).error ||
`Findymail API error: ${response.status} ${response.statusText}`,
output: { contacts: [] },
}
@@ -68,6 +68,7 @@ export const findEmployeesTool: ToolConfig<
success: false,
error:
(errorData as Record<string, string>).message ||
(errorData as Record<string, string>).error ||
`Findymail API error: ${response.status} ${response.statusText}`,
output: { employees: [] },
}
+1
View File
@@ -42,6 +42,7 @@ export const findPhoneTool: ToolConfig<FindymailFindPhoneParams, FindymailFindPh
success: false,
error:
(errorData as Record<string, string>).message ||
(errorData as Record<string, string>).error ||
`Findymail API error: ${response.status} ${response.statusText}`,
output: { phone: null, line_type: null },
}
+1
View File
@@ -62,6 +62,7 @@ export const getCompanyTool: ToolConfig<FindymailGetCompanyParams, FindymailGetC
success: false,
error:
(errorData as Record<string, string>).message ||
(errorData as Record<string, string>).error ||
`Findymail API error: ${response.status} ${response.statusText}`,
output: {
name: null,
+1
View File
@@ -35,6 +35,7 @@ export const getCreditsTool: ToolConfig<FindymailGetCreditsParams, FindymailGetC
success: false,
error:
(errorData as Record<string, string>).message ||
(errorData as Record<string, string>).error ||
`Findymail API error: ${response.status} ${response.statusText}`,
output: { credits: 0, verifier_credits: 0 },
}
@@ -61,6 +61,7 @@ export const lookupTechnologiesTool: ToolConfig<
success: false,
error:
(errorData as Record<string, string>).message ||
(errorData as Record<string, string>).error ||
`Findymail API error: ${response.status} ${response.statusText}`,
output: { domain: '', technologies: [] },
}
@@ -56,6 +56,7 @@ export const reverseEmailLookupTool: ToolConfig<
success: false,
error:
(errorData as Record<string, string>).message ||
(errorData as Record<string, string>).error ||
`Findymail API error: ${response.status} ${response.statusText}`,
output: {
email: null,
@@ -50,6 +50,7 @@ export const searchTechnologiesTool: ToolConfig<
success: false,
error:
(errorData as Record<string, string>).message ||
(errorData as Record<string, string>).error ||
`Findymail API error: ${response.status} ${response.statusText}`,
output: { technologies: [] },
}
+1
View File
@@ -44,6 +44,7 @@ export const verifyEmailTool: ToolConfig<FindymailVerifyEmailParams, FindymailVe
success: false,
error:
(errorData as Record<string, string>).message ||
(errorData as Record<string, string>).error ||
`Findymail API error: ${response.status} ${response.statusText}`,
output: { email: '', verified: false, provider: null },
}
+20 -6
View File
@@ -3176,6 +3176,14 @@ import {
zoomListRecordingsTool,
zoomUpdateMeetingTool,
} from '@/tools/zoom'
import {
zoominfoEnrichCompaniesTool,
zoominfoEnrichContactsTool,
zoominfoSearchCompaniesTool,
zoominfoSearchContactsTool,
zoominfoSearchIntentTool,
zoominfoSearchNewsTool,
} from '@/tools/zoominfo'
// Registry of all available tools
export const tools: Record<string, ToolConfig> = {
@@ -4999,17 +5007,17 @@ export const tools: Record<string, ToolConfig> = {
fathom_get_transcript: fathomGetTranscriptTool,
fathom_list_team_members: fathomListTeamMembersTool,
fathom_list_teams: fathomListTeamsTool,
findymail_verify_email: findymailVerifyEmailTool,
findymail_find_email_from_linkedin: findymailFindEmailFromLinkedInTool,
findymail_find_email_from_name: findymailFindEmailFromNameTool,
findymail_find_emails_by_domain: findymailFindEmailsByDomainTool,
findymail_find_email_from_linkedin: findymailFindEmailFromLinkedInTool,
findymail_reverse_email_lookup: findymailReverseEmailLookupTool,
findymail_get_company: findymailGetCompanyTool,
findymail_find_employees: findymailFindEmployeesTool,
findymail_find_phone: findymailFindPhoneTool,
findymail_search_technologies: findymailSearchTechnologiesTool,
findymail_lookup_technologies: findymailLookupTechnologiesTool,
findymail_get_company: findymailGetCompanyTool,
findymail_get_credits: findymailGetCreditsTool,
findymail_lookup_technologies: findymailLookupTechnologiesTool,
findymail_reverse_email_lookup: findymailReverseEmailLookupTool,
findymail_search_technologies: findymailSearchTechnologiesTool,
findymail_verify_email: findymailVerifyEmailTool,
stt_whisper: whisperSttTool,
stt_whisper_v2: whisperSttV2Tool,
stt_deepgram: deepgramSttTool,
@@ -6006,6 +6014,12 @@ export const tools: Record<string, ToolConfig> = {
zoom_get_meeting_recordings: zoomGetMeetingRecordingsTool,
zoom_delete_recording: zoomDeleteRecordingTool,
zoom_list_past_participants: zoomListPastParticipantsTool,
zoominfo_search_companies: zoominfoSearchCompaniesTool,
zoominfo_search_contacts: zoominfoSearchContactsTool,
zoominfo_enrich_companies: zoominfoEnrichCompaniesTool,
zoominfo_enrich_contacts: zoominfoEnrichContactsTool,
zoominfo_search_intent: zoominfoSearchIntentTool,
zoominfo_search_news: zoominfoSearchNewsTool,
// Spotify
spotify_search: spotifySearchTool,
spotify_get_track: spotifyGetTrackTool,
+5 -1
View File
@@ -137,6 +137,10 @@ export const wizaCompanyEnrichmentTool: ToolConfig<
company_region: { type: 'string', description: 'State/region', optional: true },
company_postal_code: { type: 'string', description: 'Postal code', optional: true },
company_country: { type: 'string', description: 'Country', optional: true },
credits: { type: 'json', description: 'Remaining API credits', optional: true },
credits: {
type: 'json',
description: 'Credits deducted for this enrichment (api_credits: { total, company_credits })',
optional: true,
},
},
}
+17 -4
View File
@@ -100,9 +100,17 @@ export const wizaGetIndividualRevealTool: ToolConfig<
},
outputs: {
id: { type: 'number', description: 'Reveal ID' },
status: { type: 'string', description: 'queued | resolving | finished | failed' },
is_complete: { type: 'boolean', description: 'Whether the reveal has completed' },
id: { type: 'number', description: 'Reveal ID', optional: true },
status: {
type: 'string',
description: 'queued | resolving | finished | failed',
optional: true,
},
is_complete: {
type: 'boolean',
description: 'Whether the reveal has completed',
optional: true,
},
name: { type: 'string', description: 'Full name', optional: true },
company: { type: 'string', description: 'Company name', optional: true },
enrichment_level: { type: 'string', description: 'Enrichment level used', optional: true },
@@ -158,6 +166,11 @@ export const wizaGetIndividualRevealTool: ToolConfig<
company_linkedin: { type: 'string', description: 'Company LinkedIn URL', optional: true },
company_location: { type: 'string', description: 'Full company location', optional: true },
company_description: { type: 'string', description: 'Company description', optional: true },
credits: { type: 'json', description: 'Remaining credits balance', optional: true },
credits: {
type: 'json',
description:
'Credits deducted for this reveal (api_credits: { total, email_credits, phone_credits, scrape_credits })',
optional: true,
},
},
}
+11 -2
View File
@@ -132,11 +132,20 @@ export const wizaStartIndividualRevealTool: ToolConfig<
},
outputs: {
id: { type: 'number', description: 'Individual reveal ID (use with Get Individual Reveal)' },
id: {
type: 'number',
description: 'Individual reveal ID (use with Get Individual Reveal)',
optional: true,
},
status: {
type: 'string',
description: 'Reveal status: queued, resolving, finished, or failed',
optional: true,
},
is_complete: {
type: 'boolean',
description: 'Whether the reveal has completed',
optional: true,
},
is_complete: { type: 'boolean', description: 'Whether the reveal has completed' },
},
}
+17 -6
View File
@@ -13,25 +13,36 @@ export interface WizaGetCreditsResponse extends ToolResponse {
}
}
export interface WizaIncludeExcludeFilter {
v: string
s: 'i' | 'e'
}
export interface WizaLocationFilter {
v: string | { country?: string; state?: string; city?: string }
b: 'country' | 'state' | 'city'
s: 'i' | 'e'
}
export interface WizaProspectSearchParams {
apiKey: string
size?: number
filters?: Record<string, unknown>
first_name?: string[]
last_name?: string[]
job_title?: unknown[]
job_title?: WizaIncludeExcludeFilter[]
job_title_level?: string[]
job_role?: string[]
job_sub_role?: string[]
location?: unknown[]
location?: WizaLocationFilter[]
skill?: string[]
school?: string[]
major?: string[]
linkedin_slug?: string[]
job_company?: unknown[]
past_company?: unknown[]
company_location?: unknown[]
company_industry?: unknown[]
job_company?: WizaIncludeExcludeFilter[]
past_company?: WizaIncludeExcludeFilter[]
company_location?: WizaLocationFilter[]
company_industry?: WizaIncludeExcludeFilter[]
company_size?: string[]
company_type?: string[]
}
+136
View File
@@ -0,0 +1,136 @@
import type { ToolConfig } from '@/tools/types'
import type {
ZoomInfoEnrichCompaniesParams,
ZoomInfoEnrichCompaniesResponse,
} from '@/tools/zoominfo/types'
import {
buildProxyBody,
extractDataArray,
parseCsvOrJson,
parseJsonField,
transformZoomInfoEnvelope,
ZOOMINFO_PROXY_URL,
} from '@/tools/zoominfo/utils'
/**
* Default output fields used when the caller does not specify any. ZoomInfo's
* CompanyEnrich schema requires `outputFields`, so we send a useful firmographic
* set rather than letting the request fail. All values are valid CompanyEnrich fields.
*/
const DEFAULT_COMPANY_OUTPUT_FIELDS = [
'id',
'name',
'website',
'domainList',
'ticker',
'revenue',
'revenueRange',
'employeeCount',
'employeeRange',
'primaryIndustry',
'industries',
'street',
'city',
'state',
'zipCode',
'country',
'phone',
'foundedYear',
'companyStatus',
'socialMediaUrls',
'logo',
'description',
]
export const zoominfoEnrichCompaniesTool: ToolConfig<
ZoomInfoEnrichCompaniesParams,
ZoomInfoEnrichCompaniesResponse
> = {
id: 'zoominfo_enrich_companies',
name: 'ZoomInfo Enrich Companies',
description:
'Enrich up to 25 companies in one request with detailed firmographics, industry, financials, and more.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ZoomInfo OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ZoomInfo OAuth client secret',
},
matchCompanyInput: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'JSON array (1-25 items) of company matching criteria, e.g. [{"companyName":"Acme","companyWebsite":"acme.com"}]',
},
outputFields: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON array or comma-separated list of fields to return (e.g. ["id","name","website","revenue","employeeCount"]). Defaults to a standard firmographic set if omitted.',
},
},
request: {
url: ZOOMINFO_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const matchCompanyInput = parseJsonField<unknown>(
params.matchCompanyInput,
'matchCompanyInput'
)
if (!Array.isArray(matchCompanyInput) || matchCompanyInput.length === 0) {
throw new Error('matchCompanyInput must be a non-empty JSON array')
}
if (matchCompanyInput.length > 25) {
throw new Error('matchCompanyInput supports a maximum of 25 entries per request')
}
const outputFields = parseCsvOrJson(params.outputFields, 'outputFields')
const attributes: Record<string, unknown> = {
matchCompanyInput,
outputFields: outputFields ?? DEFAULT_COMPANY_OUTPUT_FIELDS,
}
return {
...buildProxyBody(params),
path: '/data/v1/companies/enrich',
method: 'POST',
body: {
data: {
type: 'CompanyEnrich',
attributes,
},
},
}
},
},
transformResponse: async (response: Response) => {
const { data } = await transformZoomInfoEnvelope(response)
const results = extractDataArray(data)
return {
success: true,
output: { results },
}
},
outputs: {
results: {
type: 'array',
description: 'Enrichment results, one per input with match status and attributes',
items: { type: 'json' },
},
},
}
+139
View File
@@ -0,0 +1,139 @@
import type { ToolConfig } from '@/tools/types'
import type {
ZoomInfoEnrichContactsParams,
ZoomInfoEnrichContactsResponse,
} from '@/tools/zoominfo/types'
import {
buildProxyBody,
extractDataArray,
parseCsvOrJson,
parseJsonField,
transformZoomInfoEnvelope,
ZOOMINFO_PROXY_URL,
} from '@/tools/zoominfo/utils'
/**
* Default output fields used when the caller does not specify any. ZoomInfo's
* ContactEnrich schema requires `outputFields`, so we send a useful contact set
* rather than letting the request fail. All values are valid ContactEnrich fields.
*/
const DEFAULT_CONTACT_OUTPUT_FIELDS = [
'id',
'firstName',
'lastName',
'email',
'phone',
'mobilePhone',
'jobTitle',
'jobFunction',
'managementLevel',
'city',
'state',
'country',
'contactAccuracyScore',
'validDate',
'lastUpdatedDate',
'companyId',
'companyName',
'companyWebsite',
'companyPhone',
]
export const zoominfoEnrichContactsTool: ToolConfig<
ZoomInfoEnrichContactsParams,
ZoomInfoEnrichContactsResponse
> = {
id: 'zoominfo_enrich_contacts',
name: 'ZoomInfo Enrich Contacts',
description:
'Enrich up to 25 contacts in one request with verified emails, phone numbers, job details, and more.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ZoomInfo OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ZoomInfo OAuth client secret',
},
matchPersonInput: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'JSON array (1-25 items) of contact matching criteria, e.g. [{"firstName":"Jane","lastName":"Doe","companyName":"Acme"}]',
},
outputFields: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON array or comma-separated list of fields to return (e.g. ["id","firstName","email","phone","jobTitle"]). Defaults to a standard contact set if omitted.',
},
requiredFields: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'JSON array or comma-separated list of fields that must exist in results (e.g. ["email"])',
},
},
request: {
url: ZOOMINFO_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const matchPersonInput = parseJsonField<unknown>(params.matchPersonInput, 'matchPersonInput')
if (!Array.isArray(matchPersonInput) || matchPersonInput.length === 0) {
throw new Error('matchPersonInput must be a non-empty JSON array')
}
if (matchPersonInput.length > 25) {
throw new Error('matchPersonInput supports a maximum of 25 entries per request')
}
const outputFields = parseCsvOrJson(params.outputFields, 'outputFields')
const attributes: Record<string, unknown> = {
matchPersonInput,
outputFields: outputFields ?? DEFAULT_CONTACT_OUTPUT_FIELDS,
}
const requiredFields = parseCsvOrJson(params.requiredFields, 'requiredFields')
if (requiredFields) attributes.requiredFields = requiredFields
return {
...buildProxyBody(params),
path: '/data/v1/contacts/enrich',
method: 'POST',
body: {
data: {
type: 'ContactEnrich',
attributes,
},
},
}
},
},
transformResponse: async (response: Response) => {
const { data } = await transformZoomInfoEnvelope(response)
const results = extractDataArray(data)
return {
success: true,
output: { results },
}
},
outputs: {
results: {
type: 'array',
description: 'Enrichment results, one per input with match status and attributes',
items: { type: 'json' },
},
},
}
+6
View File
@@ -0,0 +1,6 @@
export { zoominfoEnrichCompaniesTool } from '@/tools/zoominfo/enrich_companies'
export { zoominfoEnrichContactsTool } from '@/tools/zoominfo/enrich_contacts'
export { zoominfoSearchCompaniesTool } from '@/tools/zoominfo/search_companies'
export { zoominfoSearchContactsTool } from '@/tools/zoominfo/search_contacts'
export { zoominfoSearchIntentTool } from '@/tools/zoominfo/search_intent'
export { zoominfoSearchNewsTool } from '@/tools/zoominfo/search_news'
+213
View File
@@ -0,0 +1,213 @@
import type { ToolConfig } from '@/tools/types'
import type {
ZoomInfoSearchCompaniesParams,
ZoomInfoSearchCompaniesResponse,
} from '@/tools/zoominfo/types'
import {
buildProxyBody,
extractDataArray,
extractPagination,
paginationOutputProperties,
parseCsvOrJson,
toCsvStringOrUndefined,
toNumberOrUndefined,
transformZoomInfoEnvelope,
ZOOMINFO_PROXY_URL,
} from '@/tools/zoominfo/utils'
export const zoominfoSearchCompaniesTool: ToolConfig<
ZoomInfoSearchCompaniesParams,
ZoomInfoSearchCompaniesResponse
> = {
id: 'zoominfo_search_companies',
name: 'ZoomInfo Search Companies',
description: 'Search the ZoomInfo company database by name, industry, location, and size.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ZoomInfo OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ZoomInfo OAuth client secret',
},
companyName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Company name to search for',
},
companyWebsite: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Company website (comma-separated for multiple)',
},
companyTicker: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Stock ticker symbols — JSON array, comma-separated list, or single ticker. Sent to the API as an array.',
},
industryCodes: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Industry codes — JSON array or comma-separated list. Sent to the API as a comma-separated string.',
},
country: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Country name',
},
state: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'State or province',
},
metroRegion: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'US/Canada metro region',
},
revenueMin: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Minimum annual revenue in thousands USD',
},
revenueMax: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum annual revenue in thousands USD',
},
employeeRangeMin: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Minimum employee count',
},
employeeRangeMax: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum employee count',
},
excludeDefunctCompanies: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Exclude inactive companies',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number (1-based)',
},
rpp: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Results per page (1-100, default 25)',
},
sortBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Field to sort by',
},
sortOrder: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort order (asc or desc)',
},
},
request: {
url: ZOOMINFO_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const attributes: Record<string, unknown> = {}
if (params.companyName) attributes.companyName = params.companyName
if (params.companyWebsite) attributes.companyWebsite = params.companyWebsite
const companyTicker = parseCsvOrJson(params.companyTicker, 'companyTicker')
if (companyTicker) attributes.companyTicker = companyTicker
const industryCodes = toCsvStringOrUndefined(params.industryCodes, 'industryCodes')
if (industryCodes) attributes.industryCodes = industryCodes
if (params.country) attributes.country = params.country
if (params.state) attributes.state = params.state
if (params.metroRegion) attributes.metroRegion = params.metroRegion
const revenueMin = toNumberOrUndefined(params.revenueMin)
if (revenueMin !== undefined) attributes.revenueMin = revenueMin
const revenueMax = toNumberOrUndefined(params.revenueMax)
if (revenueMax !== undefined) attributes.revenueMax = revenueMax
const employeeRangeMin = toNumberOrUndefined(params.employeeRangeMin)
if (employeeRangeMin !== undefined) attributes.employeeRangeMin = String(employeeRangeMin)
const employeeRangeMax = toNumberOrUndefined(params.employeeRangeMax)
if (employeeRangeMax !== undefined) attributes.employeeRangeMax = String(employeeRangeMax)
if (params.excludeDefunctCompanies !== undefined) {
attributes.excludeDefunctCompanies = params.excludeDefunctCompanies
}
const query: Record<string, string | number> = {}
const page = toNumberOrUndefined(params.page)
const rpp = toNumberOrUndefined(params.rpp)
if (page !== undefined) query['page[number]'] = page
if (rpp !== undefined) query['page[size]'] = rpp
if (params.sortBy) {
const order = params.sortOrder === 'desc' ? '-' : ''
query.sort = `${order}${params.sortBy}`
}
return {
...buildProxyBody(params),
path: '/data/v1/companies/search',
method: 'POST',
query: Object.keys(query).length > 0 ? query : undefined,
body: {
data: {
type: 'CompanySearch',
attributes,
},
},
}
},
},
transformResponse: async (response: Response) => {
const { data } = await transformZoomInfoEnvelope(response)
const companies = extractDataArray(data)
const pagination = extractPagination(data)
return {
success: true,
output: {
companies,
...pagination,
},
}
},
outputs: {
companies: {
type: 'array',
description: 'Matching companies',
items: { type: 'json' },
},
...paginationOutputProperties,
},
}
+212
View File
@@ -0,0 +1,212 @@
import type { ToolConfig } from '@/tools/types'
import type {
ZoomInfoSearchContactsParams,
ZoomInfoSearchContactsResponse,
} from '@/tools/zoominfo/types'
import {
buildProxyBody,
extractDataArray,
extractPagination,
paginationOutputProperties,
toCsvStringOrUndefined,
toNumberOrUndefined,
transformZoomInfoEnvelope,
ZOOMINFO_PROXY_URL,
} from '@/tools/zoominfo/utils'
export const zoominfoSearchContactsTool: ToolConfig<
ZoomInfoSearchContactsParams,
ZoomInfoSearchContactsResponse
> = {
id: 'zoominfo_search_contacts',
name: 'ZoomInfo Search Contacts',
description:
'Search ZoomInfo for contacts (people) by name, job title, company, and other filters. Does not return emails or phone numbers — use Enrich Contacts for engagement data.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ZoomInfo OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ZoomInfo OAuth client secret',
},
firstName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'First name',
},
lastName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Last name',
},
fullName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Full name',
},
emailAddress: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Email address',
},
jobTitle: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Job title',
},
managementLevel: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Management level — JSON array or comma-separated list. Sent to the API as a comma-separated string.',
},
department: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Department — JSON array or comma-separated list. Sent to the API as a comma-separated string.',
},
companyId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'ZoomInfo company ID',
},
companyName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Company name',
},
contactAccuracyScoreMin: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Minimum accuracy score (70-99)',
},
requiredFields: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Fields that must exist in results — JSON array or comma-separated list. Sent to the API as a comma-separated string.',
},
excludePartialProfiles: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Exclude partial profiles',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number (1-based)',
},
rpp: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Results per page (1-100, default 25)',
},
sortBy: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Field to sort by',
},
sortOrder: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Sort order (asc or desc)',
},
},
request: {
url: ZOOMINFO_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const attributes: Record<string, unknown> = {}
if (params.firstName) attributes.firstName = params.firstName
if (params.lastName) attributes.lastName = params.lastName
if (params.fullName) attributes.fullName = params.fullName
if (params.emailAddress) attributes.emailAddress = params.emailAddress
if (params.jobTitle) attributes.jobTitle = params.jobTitle
const managementLevel = toCsvStringOrUndefined(params.managementLevel, 'managementLevel')
if (managementLevel) attributes.managementLevel = managementLevel
const department = toCsvStringOrUndefined(params.department, 'department')
if (department) attributes.department = department
if (params.companyId) attributes.companyId = params.companyId
if (params.companyName) attributes.companyName = params.companyName
const minScore = toNumberOrUndefined(params.contactAccuracyScoreMin)
if (minScore !== undefined) attributes.contactAccuracyScoreMin = String(minScore)
const required = toCsvStringOrUndefined(params.requiredFields, 'requiredFields')
if (required) attributes.requiredFields = required
if (params.excludePartialProfiles !== undefined) {
attributes.excludePartialProfiles = params.excludePartialProfiles
}
const query: Record<string, string | number> = {}
const page = toNumberOrUndefined(params.page)
const rpp = toNumberOrUndefined(params.rpp)
if (page !== undefined) query['page[number]'] = page
if (rpp !== undefined) query['page[size]'] = rpp
if (params.sortBy) {
const order = params.sortOrder === 'desc' ? '-' : ''
query.sort = `${order}${params.sortBy}`
}
return {
...buildProxyBody(params),
path: '/data/v1/contacts/search',
method: 'POST',
query: Object.keys(query).length > 0 ? query : undefined,
body: {
data: {
type: 'ContactSearch',
attributes,
},
},
}
},
},
transformResponse: async (response: Response) => {
const { data } = await transformZoomInfoEnvelope(response)
const contacts = extractDataArray(data)
const pagination = extractPagination(data)
return {
success: true,
output: {
contacts,
...pagination,
},
}
},
outputs: {
contacts: {
type: 'array',
description: 'Matching contacts (without emails or phone numbers)',
items: { type: 'json' },
},
...paginationOutputProperties,
},
}
+190
View File
@@ -0,0 +1,190 @@
import type { ToolConfig } from '@/tools/types'
import type {
ZoomInfoSearchIntentParams,
ZoomInfoSearchIntentResponse,
} from '@/tools/zoominfo/types'
import {
buildProxyBody,
extractDataArray,
extractPagination,
paginationOutputProperties,
parseCsvOrJson,
toCsvStringOrUndefined,
toNumberOrUndefined,
transformZoomInfoEnvelope,
ZOOMINFO_PROXY_URL,
} from '@/tools/zoominfo/utils'
export const zoominfoSearchIntentTool: ToolConfig<
ZoomInfoSearchIntentParams,
ZoomInfoSearchIntentResponse
> = {
id: 'zoominfo_search_intent',
name: 'ZoomInfo Search Intent',
description: 'Search for companies showing intent signals on specific topics.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ZoomInfo OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ZoomInfo OAuth client secret',
},
topics: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Up to 50 intent topics as JSON array or comma-separated list (e.g. ["CRM Software","Marketing Automation"])',
},
signalStartDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Earliest signal date (YYYY-MM-DD)',
},
signalEndDate: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Latest signal date (YYYY-MM-DD)',
},
signalScoreMin: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Minimum signal score (60-100)',
},
signalScoreMax: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum signal score (60-100)',
},
audienceStrengthMin: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Minimum audience strength (A-E, A is largest)',
},
audienceStrengthMax: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Maximum audience strength (A-E, A is largest)',
},
findRecommendedContacts: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Include recommended contacts (default true)',
},
country: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Country filter',
},
state: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'State filter',
},
industryCodes: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Industry codes — JSON array or comma-separated list. Sent to the API as a comma-separated string.',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number (1-based)',
},
rpp: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Results per page (1-100, default 25)',
},
},
request: {
url: ZOOMINFO_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const topics = parseCsvOrJson(params.topics, 'topics')
if (!topics || topics.length === 0) {
throw new Error('topics is required')
}
const attributes: Record<string, unknown> = { topics }
if (params.signalStartDate) attributes.signalStartDate = params.signalStartDate
if (params.signalEndDate) attributes.signalEndDate = params.signalEndDate
const scoreMin = toNumberOrUndefined(params.signalScoreMin)
if (scoreMin !== undefined) attributes.signalScoreMin = scoreMin
const scoreMax = toNumberOrUndefined(params.signalScoreMax)
if (scoreMax !== undefined) attributes.signalScoreMax = scoreMax
if (params.audienceStrengthMin) attributes.audienceStrengthMin = params.audienceStrengthMin
if (params.audienceStrengthMax) attributes.audienceStrengthMax = params.audienceStrengthMax
if (params.findRecommendedContacts !== undefined) {
attributes.findRecommendedContacts = params.findRecommendedContacts
}
if (params.country) attributes.country = params.country
if (params.state) attributes.state = params.state
const industryCodes = toCsvStringOrUndefined(params.industryCodes, 'industryCodes')
if (industryCodes) attributes.industryCodes = industryCodes
const query: Record<string, string | number> = {}
const page = toNumberOrUndefined(params.page)
const rpp = toNumberOrUndefined(params.rpp)
if (page !== undefined) query['page[number]'] = page
if (rpp !== undefined) query['page[size]'] = rpp
return {
...buildProxyBody(params),
path: '/data/v1/intent/search',
method: 'POST',
query: Object.keys(query).length > 0 ? query : undefined,
body: {
data: {
type: 'IntentSearch',
attributes,
},
},
}
},
},
transformResponse: async (response: Response) => {
const { data } = await transformZoomInfoEnvelope(response)
const signals = extractDataArray(data)
const pagination = extractPagination(data)
return {
success: true,
output: {
signals,
...pagination,
},
}
},
outputs: {
signals: {
type: 'array',
description: 'Intent signals with topic, score, audience strength, and company',
items: { type: 'json' },
},
...paginationOutputProperties,
},
}
+133
View File
@@ -0,0 +1,133 @@
import type { ToolConfig } from '@/tools/types'
import type { ZoomInfoSearchNewsParams, ZoomInfoSearchNewsResponse } from '@/tools/zoominfo/types'
import {
buildProxyBody,
extractDataArray,
extractPagination,
paginationOutputProperties,
parseCsvOrJson,
toNumberOrUndefined,
transformZoomInfoEnvelope,
ZOOMINFO_PROXY_URL,
} from '@/tools/zoominfo/utils'
export const zoominfoSearchNewsTool: ToolConfig<
ZoomInfoSearchNewsParams,
ZoomInfoSearchNewsResponse
> = {
id: 'zoominfo_search_news',
name: 'ZoomInfo Search News',
description: 'Search ZoomInfo news articles by category, URL, or date range.',
version: '1.0.0',
params: {
clientId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ZoomInfo OAuth client ID',
},
clientSecret: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ZoomInfo OAuth client secret',
},
categories: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'News categories as JSON array or comma-separated list',
},
url: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'News source URLs as JSON array or comma-separated list',
},
pageDateMin: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Earliest publish date (YYYY-MM-DD)',
},
pageDateMax: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Latest publish date (YYYY-MM-DD)',
},
page: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Page number (1-based)',
},
rpp: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Results per page (1-100, default 25)',
},
},
request: {
url: ZOOMINFO_PROXY_URL,
method: 'POST',
headers: () => ({ 'Content-Type': 'application/json' }),
body: (params) => {
const attributes: Record<string, unknown> = {}
const categories = parseCsvOrJson(params.categories, 'categories')
if (categories) attributes.categories = categories
const urls = parseCsvOrJson(params.url, 'url')
if (urls) attributes.url = urls
if (params.pageDateMin) attributes.pageDateMin = params.pageDateMin
if (params.pageDateMax) attributes.pageDateMax = params.pageDateMax
if (Object.keys(attributes).length === 0) {
throw new Error('Provide at least one of: categories, url, pageDateMin, pageDateMax')
}
const query: Record<string, string | number> = {}
const page = toNumberOrUndefined(params.page)
const rpp = toNumberOrUndefined(params.rpp)
if (page !== undefined) query['page[number]'] = page
if (rpp !== undefined) query['page[size]'] = rpp
return {
...buildProxyBody(params),
path: '/data/v1/news/search',
method: 'POST',
query: Object.keys(query).length > 0 ? query : undefined,
body: {
data: {
type: 'NewsSearch',
attributes,
},
},
}
},
},
transformResponse: async (response: Response) => {
const { data } = await transformZoomInfoEnvelope(response)
const articles = extractDataArray(data)
const pagination = extractPagination(data)
return {
success: true,
output: {
articles,
...pagination,
},
}
},
outputs: {
articles: {
type: 'array',
description: 'News articles matching the filters',
items: { type: 'json' },
},
...paginationOutputProperties,
},
}
+136
View File
@@ -0,0 +1,136 @@
import type { ToolResponse } from '@/tools/types'
export interface ZoomInfoBaseParams {
clientId: string
clientSecret: string
}
export interface ZoomInfoSearchCompaniesParams extends ZoomInfoBaseParams {
companyName?: string
companyWebsite?: string
companyTicker?: string
industryCodes?: string
country?: string
state?: string
metroRegion?: string
revenueMin?: number
revenueMax?: number
employeeRangeMin?: number
employeeRangeMax?: number
excludeDefunctCompanies?: boolean
page?: number
rpp?: number
sortBy?: string
sortOrder?: string
}
export interface ZoomInfoSearchContactsParams extends ZoomInfoBaseParams {
firstName?: string
lastName?: string
fullName?: string
emailAddress?: string
jobTitle?: string
managementLevel?: string
department?: string
companyId?: string
companyName?: string
contactAccuracyScoreMin?: number
requiredFields?: string
excludePartialProfiles?: boolean
page?: number
rpp?: number
sortBy?: string
sortOrder?: string
}
export interface ZoomInfoEnrichCompaniesParams extends ZoomInfoBaseParams {
matchCompanyInput: string
outputFields?: string
}
export interface ZoomInfoEnrichContactsParams extends ZoomInfoBaseParams {
matchPersonInput: string
outputFields?: string
requiredFields?: string
}
export interface ZoomInfoSearchIntentParams extends ZoomInfoBaseParams {
topics: string
signalStartDate?: string
signalEndDate?: string
signalScoreMin?: number
signalScoreMax?: number
audienceStrengthMin?: string
audienceStrengthMax?: string
findRecommendedContacts?: boolean
country?: string
state?: string
industryCodes?: string
page?: number
rpp?: number
}
export interface ZoomInfoSearchNewsParams extends ZoomInfoBaseParams {
categories?: string
url?: string
pageDateMin?: string
pageDateMax?: string
page?: number
rpp?: number
}
export interface ZoomInfoSearchCompaniesResponse extends ToolResponse {
output: {
companies: Array<Record<string, unknown>>
totalResults: number | null
currentPage: number | null
totalPages: number | null
}
}
export interface ZoomInfoSearchContactsResponse extends ToolResponse {
output: {
contacts: Array<Record<string, unknown>>
totalResults: number | null
currentPage: number | null
totalPages: number | null
}
}
export interface ZoomInfoEnrichCompaniesResponse extends ToolResponse {
output: {
results: Array<Record<string, unknown>>
}
}
export interface ZoomInfoEnrichContactsResponse extends ToolResponse {
output: {
results: Array<Record<string, unknown>>
}
}
export interface ZoomInfoSearchIntentResponse extends ToolResponse {
output: {
signals: Array<Record<string, unknown>>
totalResults: number | null
currentPage: number | null
totalPages: number | null
}
}
export interface ZoomInfoSearchNewsResponse extends ToolResponse {
output: {
articles: Array<Record<string, unknown>>
totalResults: number | null
currentPage: number | null
totalPages: number | null
}
}
export type ZoomInfoResponse =
| ZoomInfoSearchCompaniesResponse
| ZoomInfoSearchContactsResponse
| ZoomInfoEnrichCompaniesResponse
| ZoomInfoEnrichContactsResponse
| ZoomInfoSearchIntentResponse
| ZoomInfoSearchNewsResponse
+136
View File
@@ -0,0 +1,136 @@
import { getErrorMessage } from '@sim/utils/errors'
import type { OutputProperty } from '@/tools/types'
import type { ZoomInfoBaseParams } from '@/tools/zoominfo/types'
export const ZOOMINFO_PROXY_URL = '/api/tools/zoominfo/proxy'
export interface ZoomInfoProxyEnvelope {
clientId: string
clientSecret: string
path: string
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
query?: Record<string, string | number | boolean>
body?: unknown
}
export function buildProxyBody(
params: ZoomInfoBaseParams
): Pick<ZoomInfoProxyEnvelope, 'clientId' | 'clientSecret'> {
return {
clientId: params.clientId,
clientSecret: params.clientSecret,
}
}
export function parseJsonField<T>(value: unknown, fieldName: string): T {
if (typeof value !== 'string') return value as T
const trimmed = value.trim()
if (!trimmed) {
throw new Error(`${fieldName} is required`)
}
try {
return JSON.parse(trimmed) as T
} catch (error) {
throw new Error(`${fieldName} must be valid JSON: ${getErrorMessage(error)}`)
}
}
/**
* Normalize a JSON-array string, real array, or comma-separated string into a
* single comma-separated string. Use for ZoomInfo attributes that the docs
* describe as a scalar string accepting a comma-separated list
* (e.g. industryCodes, managementLevel, department).
*/
export function toCsvStringOrUndefined(value: unknown, fieldName: string): string | undefined {
const arr = parseCsvOrJson(value, fieldName)
if (!arr || arr.length === 0) return undefined
return arr.join(',')
}
export function parseCsvOrJson(value: unknown, fieldName: string): string[] | undefined {
if (value === undefined || value === null) return undefined
if (Array.isArray(value)) return value.map(String)
if (typeof value !== 'string') return undefined
const trimmed = value.trim()
if (!trimmed) return undefined
if (trimmed.startsWith('[')) {
try {
const parsed = JSON.parse(trimmed)
if (!Array.isArray(parsed)) {
throw new Error(`${fieldName} JSON must be an array of strings`)
}
return parsed.map(String)
} catch (error) {
throw new Error(`${fieldName} must be valid JSON: ${getErrorMessage(error)}`)
}
}
return trimmed
.split(',')
.map((s) => s.trim())
.filter(Boolean)
}
export function toNumberOrUndefined(value: unknown): number | undefined {
if (value === undefined || value === null || value === '') return undefined
const n = Number(value)
return Number.isFinite(n) ? n : undefined
}
export async function transformZoomInfoEnvelope(
response: Response
): Promise<{ status: number; data: unknown }> {
const data = (await response.json()) as
| { success: true; output: { status: number; data: unknown } }
| { success: false; error?: string; status?: number }
if (!('success' in data) || data.success === false) {
const errMessage = 'error' in data && data.error ? data.error : 'ZoomInfo request failed'
throw new Error(errMessage)
}
return { status: data.output.status, data: data.output.data }
}
export const paginationOutputProperties: Record<string, OutputProperty> = {
totalResults: {
type: 'number',
description: 'Total number of matching results across all pages',
optional: true,
},
currentPage: {
type: 'number',
description: 'Current page number',
optional: true,
},
totalPages: {
type: 'number',
description: 'Total number of pages available',
optional: true,
},
}
export function extractPagination(payload: unknown): {
totalResults: number | null
currentPage: number | null
totalPages: number | null
} {
if (payload && typeof payload === 'object') {
const meta = (payload as Record<string, unknown>).meta as
| { totalResults?: unknown; page?: { number?: unknown; total?: unknown } }
| undefined
if (meta) {
const totalResults = typeof meta.totalResults === 'number' ? meta.totalResults : null
const currentPage =
meta.page && typeof meta.page.number === 'number' ? meta.page.number : null
const totalPages = meta.page && typeof meta.page.total === 'number' ? meta.page.total : null
return { totalResults, currentPage, totalPages }
}
}
return { totalResults: null, currentPage: null, totalPages: null }
}
export function extractDataArray(payload: unknown): Array<Record<string, unknown>> {
if (payload && typeof payload === 'object') {
const data = (payload as Record<string, unknown>).data
if (Array.isArray(data)) return data as Array<Record<string, unknown>>
}
return []
}