Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,33 @@
{
"extends": [
"../../../../.eslintrc.base.json"
],
"ignorePatterns": [
"!**/*"
],
"overrides": [
{
"files": [
"*.ts",
"*.tsx",
"*.js",
"*.jsx"
],
"rules": {}
},
{
"files": [
"*.ts",
"*.tsx"
],
"rules": {}
},
{
"files": [
"*.js",
"*.jsx"
],
"rules": {}
}
]
}

View File

@@ -0,0 +1,7 @@
# pieces-apollo
This library was generated with [Nx](https://nx.dev).
## Building
Run `nx build pieces-apollo` to build the library.

View File

@@ -0,0 +1,4 @@
{
"name": "@activepieces/piece-apollo",
"version": "0.0.8"
}

View File

@@ -0,0 +1,57 @@
{
"name": "pieces-apollo",
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/pieces/community/apollo/src",
"projectType": "library",
"targets": {
"build": {
"executor": "@nx/js:tsc",
"outputs": [
"{options.outputPath}"
],
"options": {
"outputPath": "dist/packages/pieces/community/apollo",
"tsConfig": "packages/pieces/community/apollo/tsconfig.lib.json",
"packageJson": "packages/pieces/community/apollo/package.json",
"main": "packages/pieces/community/apollo/src/index.ts",
"assets": [
"packages/pieces/community/apollo/*.md",
{
"input": "packages/pieces/community/apollo/src/i18n",
"output": "./src/i18n",
"glob": "**/!(i18n.json)"
}
],
"buildableProjectDepsInPackageJsonType": "dependencies",
"updateBuildableProjectDepsInPackageJson": true
},
"dependsOn": [
"^build",
"prebuild"
]
},
"publish": {
"command": "node tools/scripts/publish.mjs pieces-apollo {args.ver} {args.tag}",
"dependsOn": [
"build"
]
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": [
"{options.outputFile}"
]
},
"prebuild": {
"executor": "nx:run-commands",
"options": {
"cwd": "packages/pieces/community/apollo",
"command": "bun install --no-save --silent"
},
"dependsOn": [
"^build"
]
}
},
"tags": []
}

View File

@@ -0,0 +1,8 @@
{
"Match Person": "Match-Person",
"Enrich Company": "Firma bereichern",
"Email": "E-Mail",
"Cache Response": "Cache-Antwort",
"Domain": "Domäne",
"Store the response in the project store for future use.": "Speichern Sie die Antwort im Projektspeicher für zukünftige Nutzung."
}

View File

@@ -0,0 +1,8 @@
{
"Match Person": "Persona de partida",
"Enrich Company": "Enriquecer empresa",
"Email": "E-mail",
"Cache Response": "Respuesta de caché",
"Domain": "Dominio",
"Store the response in the project store for future use.": "Almacena la respuesta en la tienda de proyectos para su uso futuro."
}

View File

@@ -0,0 +1,8 @@
{
"Match Person": "Personne correspondante",
"Enrich Company": "Enrichir la société",
"Email": "Courriel",
"Cache Response": "Mettre en cache la réponse",
"Domain": "Domaine",
"Store the response in the project store for future use.": "Stockez la réponse dans la boutique du projet pour une utilisation future."
}

View File

@@ -0,0 +1,8 @@
{
"Match Person": "人と一致",
"Enrich Company": "Enrich Company",
"Email": "Eメールアドレス",
"Cache Response": "キャッシュ応答",
"Domain": "ドメイン",
"Store the response in the project store for future use.": "今後使用するためにプロジェクトストアに応答を保存します。"
}

View File

@@ -0,0 +1,8 @@
{
"Match Person": "Wedstrijd persoon",
"Enrich Company": "Bedrijf verrijken",
"Email": "E-mail",
"Cache Response": "Cache antwoord",
"Domain": "Domein",
"Store the response in the project store for future use.": "Sla het antwoord op in de projectopslag op voor toekomstig gebruik."
}

View File

@@ -0,0 +1,8 @@
{
"Match Person": "Correspondência de pessoa",
"Enrich Company": "Enriquecer empresa",
"Email": "e-mail",
"Cache Response": "Resposta em cache",
"Domain": "Domínio",
"Store the response in the project store for future use.": "Armazene a resposta na loja do projeto para uso futuro."
}

View File

@@ -0,0 +1,9 @@
{
"Apollo": "Аполло",
"Match Person": "Лицо матча",
"Enrich Company": "Расширить компанию",
"Email": "Почта",
"Cache Response": "Ответ кэша",
"Domain": "Домен",
"Store the response in the project store for future use.": "Сохранять ответ в магазине проекта для последующего использования."
}

View File

@@ -0,0 +1,8 @@
{
"Match Person": "Match Person",
"Enrich Company": "Enrich Company",
"Email": "Email",
"Cache Response": "Cache Response",
"Domain": "Domain",
"Store the response in the project store for future use.": "Store the response in the project store for future use."
}

View File

@@ -0,0 +1,9 @@
{
"Apollo": "Apollo",
"Match Person": "Match Person",
"Enrich Company": "Enrich Company",
"Email": "Email",
"Cache Response": "Cache Response",
"Domain": "Domain",
"Store the response in the project store for future use.": "Store the response in the project store for future use."
}

View File

@@ -0,0 +1,8 @@
{
"Match Person": "Match Person",
"Enrich Company": "Enrich Company",
"Email": "电子邮件地址",
"Cache Response": "Cache Response",
"Domain": "Domain",
"Store the response in the project store for future use.": "Store the response in the project store for future use."
}

View File

@@ -0,0 +1,54 @@
import { createPiece, PieceAuth } from '@activepieces/pieces-framework';
import { matchPerson } from './lib/actions/match-person';
import { enrichCompany } from './lib/actions/enrich-company';
import { newsArticlesSearch } from './lib/actions/news-articles-search';
import { organizationJobPostings } from './lib/actions/organization-job-postings';
import { organizationSearch } from './lib/actions/organization-search';
import { peopleSearch } from './lib/actions/people-search';
import { createCustomApiCallAction } from '@activepieces/pieces-common';
export const apolloAuth = PieceAuth.SecretText({
displayName: 'API Key',
description: `
To create your Apollo API key:
1. Go to **Settings** > **[Integrations](https://app.apollo.io/#/settings/integrations)** in Apollo
2. Click **Connect** beside Apollo API
3. Click **API Keys** > **Create new key**
4. Enter a name and description, then select the endpoints you need (or toggle **Set as master key** for full access)
5. Click **Create API key** and copy it to a secure location
**Note:** Some endpoints require a master API key. Keep your API keys secure and be careful with whom you share access.
[Learn more about creating API keys](https://docs.apollo.io/docs/create-api-key)
`,
required: true,
});
export const apollo = createPiece({
displayName: 'Apollo',
auth: apolloAuth,
description:
'AI sales platform for prospecting, lead gen, and deal automation. Close more deals, faster, with smart data.',
minimumSupportedRelease: '0.30.0',
logoUrl: 'https://cdn.activepieces.com/pieces/apollo.png',
authors: ['abuaboud', 'sanket-a11y'],
actions: [
matchPerson,
enrichCompany,
newsArticlesSearch,
organizationJobPostings,
organizationSearch,
peopleSearch,
createCustomApiCallAction({
auth: apolloAuth,
baseUrl: () => 'https://api.apollo.io/api/v1',
authMapping: async (auth) => {
return {
'x-api-key': auth.secret_text,
};
},
}),
],
triggers: [],
});

View File

@@ -0,0 +1,57 @@
import { apolloAuth } from '../../';
import {
Property,
StoreScope,
createAction,
} from '@activepieces/pieces-framework';
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
export const enrichCompany = createAction({
name: 'enrichCompany',
description: '',
displayName: 'Enrich Company',
props: {
domain: Property.ShortText({
displayName: 'Domain',
description: '',
required: true,
}),
cacheResponse: Property.Checkbox({
displayName: 'Cache Response',
description: 'Store the response in the project store for future use.',
required: false,
defaultValue: true,
}),
},
auth: apolloAuth,
async run({ propsValue, auth, store }) {
if (propsValue.cacheResponse) {
const cachedResult = await store.get(
`_apollo_org_${propsValue.domain}`,
StoreScope.PROJECT
);
if (cachedResult) {
return cachedResult;
}
}
const result = await httpClient.sendRequest<{
organization: Record<string, unknown>;
}>({
method: HttpMethod.GET,
url: `https://api.apollo.io/v1/organizations/enrich?domain=${propsValue.domain}`,
headers: {
'Content-Type': 'application/json',
'x-api-key': `${auth.secret_text}`,
},
});
const resultOrg = result.body.organization || {};
if (propsValue.cacheResponse) {
await store.put(
`_apollo_org_${propsValue.domain}`,
resultOrg,
StoreScope.PROJECT
);
}
return resultOrg;
},
});

View File

@@ -0,0 +1,61 @@
import { apolloAuth } from '../../';
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
import {
Property,
StoreScope,
createAction,
} from '@activepieces/pieces-framework';
export const matchPerson = createAction({
name: 'matchPerson',
displayName: 'Match Person',
description: 'Enrich a persons details using their email address',
props: {
email: Property.ShortText({
displayName: 'Email',
description: ' The email address of the person to be matched',
required: true,
}),
cacheResponse: Property.Checkbox({
displayName: 'Cache Response',
description: 'Store the response in the project store for future use.',
required: false,
defaultValue: true,
}),
},
auth: apolloAuth,
async run({ propsValue, auth, store }) {
if (propsValue.cacheResponse) {
const cachedResult = await store.get(
`_apollo_person_${propsValue.email}`,
StoreScope.PROJECT
);
if (cachedResult) {
return cachedResult;
}
}
const result = await httpClient.sendRequest<{
person: Record<string, unknown>;
}>({
method: HttpMethod.POST,
url: `https://api.apollo.io/v1/people/match`,
headers: {
'Content-Type': 'application/json',
'x-api-key': `${auth.secret_text}`,
},
body: {
email: propsValue.email,
},
});
const personResult = result.body.person || {};
if (propsValue.cacheResponse) {
await store.put(
`_apollo_person_${propsValue.email}`,
personResult,
StoreScope.PROJECT
);
}
return personResult;
},
});

View File

@@ -0,0 +1,101 @@
import { apolloAuth } from '../../';
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
import {
Property,
StoreScope,
createAction,
} from '@activepieces/pieces-framework';
export const newsArticlesSearch = createAction({
auth: apolloAuth,
name: 'newsArticlesSearch',
displayName: 'News Articles Search',
description:
'Search for news articles related to companies in the Apollo database',
props: {
organization_ids: Property.Array({
displayName: 'Organization IDs',
description:
'The Apollo IDs for the companies you want to include in your search results. To find IDs, call the Organization Search endpoint.',
required: true,
}),
categories: Property.Array({
displayName: 'Categories',
description:
'Filter your search to include only certain categories or sub-categories of news (e.g., hires, investment, contract)',
required: false,
}),
published_at_min: Property.ShortText({
displayName: 'Published Date Min',
description: 'Lower bound of the date range (YYYY-MM-DD format)',
required: false,
}),
published_at_max: Property.ShortText({
displayName: 'Published Date Max',
description: 'Upper bound of the date range (YYYY-MM-DD format)',
required: false,
}),
cacheResponse: Property.Checkbox({
displayName: 'Cache Response',
description: 'Store the response in the project store for future use.',
required: false,
defaultValue: false,
}),
},
async run({ propsValue, auth, store }) {
const queryParams = new URLSearchParams();
if (propsValue.organization_ids && propsValue.organization_ids.length > 0) {
propsValue.organization_ids.forEach((id: unknown) => {
queryParams.append('organization_ids[]', id as string);
});
}
if (propsValue.categories && propsValue.categories.length > 0) {
propsValue.categories.forEach((category: unknown) => {
queryParams.append('categories[]', category as string);
});
}
if (propsValue.published_at_min) {
queryParams.append('published_at[min]', propsValue.published_at_min);
}
if (propsValue.published_at_max) {
queryParams.append('published_at[max]', propsValue.published_at_max);
}
const cacheKey = `_apollo_news_${queryParams.toString()}`;
if (propsValue.cacheResponse) {
const cachedResult = await store.get(cacheKey, StoreScope.PROJECT);
if (cachedResult) {
return cachedResult;
}
}
const result = await httpClient.sendRequest<{
news_articles: Record<string, unknown>[];
pagination: Record<string, unknown>;
}>({
method: HttpMethod.POST,
url: `https://api.apollo.io/api/v1/news_articles/search?${queryParams.toString()}`,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'x-api-key': `${auth.secret_text}`,
},
});
const response = {
news_articles: result.body.news_articles || [],
pagination: result.body.pagination || {},
};
if (propsValue.cacheResponse) {
await store.put(cacheKey, response, StoreScope.PROJECT);
}
return response;
},
});

View File

@@ -0,0 +1,58 @@
import { apolloAuth } from '../../';
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
import {
Property,
StoreScope,
createAction,
} from '@activepieces/pieces-framework';
export const organizationJobPostings = createAction({
auth: apolloAuth,
name: 'organizationJobPostings',
displayName: 'Organization Job Postings',
description: 'Retrieve current job postings for a company to identify growing headcount areas',
props: {
organization_id: Property.ShortText({
displayName: 'Organization ID',
description: 'The Apollo ID of the organization to get job postings for',
required: true,
}),
cacheResponse: Property.Checkbox({
displayName: 'Cache Response',
description: 'Store the response in the project store for future use.',
required: false,
defaultValue: false,
}),
},
async run({ propsValue, auth, store }) {
const cacheKey = `_apollo_job_postings_${propsValue.organization_id}`;
if (propsValue.cacheResponse) {
const cachedResult = await store.get(cacheKey, StoreScope.PROJECT);
if (cachedResult) {
return cachedResult;
}
}
const result = await httpClient.sendRequest<{
organization_job_postings: Record<string, unknown>[];
pagination: Record<string, unknown>;
}>({
method: HttpMethod.GET,
url: `https://api.apollo.io/api/v1/organizations/${propsValue.organization_id}/job_postings`,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'x-api-key': `${auth.secret_text}`,
},
});
if (propsValue.cacheResponse) {
await store.put(cacheKey, result.body.organization_job_postings || [], StoreScope.PROJECT);
}
return result.body.organization_job_postings || [];
},
});

View File

@@ -0,0 +1,145 @@
import { apolloAuth } from '../../';
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
import {
Property,
StoreScope,
createAction,
} from '@activepieces/pieces-framework';
export const organizationSearch = createAction({
auth: apolloAuth,
name: 'organizationSearch',
displayName: 'Organization Search',
description:
'Search for companies in the Apollo database with various filters',
props: {
q_organization_name: Property.ShortText({
displayName: 'Organization Name',
description: 'Search by organization name',
required: false,
}),
organization_locations: Property.Array({
displayName: 'Organization Locations',
description: 'Filter by locations (e.g., texas, tokyo)',
required: false,
}),
organization_not_locations: Property.Array({
displayName: 'Exclude Locations',
description: 'Exclude specific locations (e.g., minnesota)',
required: false,
}),
organization_num_employees_ranges: Property.Array({
displayName: 'Employee Count Ranges',
description: 'Filter by employee count ranges (e.g., "1,10", "250,500")',
required: false,
}),
organization_ids: Property.Array({
displayName: 'Organization IDs',
description: 'Filter by specific organization IDs',
required: false,
}),
q_organization_domains: Property.Array({
displayName: 'Domains',
description: 'Filter by organization domains',
required: false,
}),
organization_industry_tag_ids: Property.Array({
displayName: 'Industry Tag IDs',
description: 'Filter by industry tag IDs',
required: false,
}),
cacheResponse: Property.Checkbox({
displayName: 'Cache Response',
description: 'Store the response in the project store for future use.',
required: false,
defaultValue: false,
}),
},
async run({ propsValue, auth, store }) {
const queryParams = new URLSearchParams();
if (propsValue.q_organization_name) {
queryParams.append('q_organization_name', propsValue.q_organization_name);
}
if (
propsValue.organization_locations &&
propsValue.organization_locations.length > 0
) {
propsValue.organization_locations.forEach((location: unknown) => {
queryParams.append('organization_locations[]', location as string);
});
}
if (
propsValue.organization_not_locations &&
propsValue.organization_not_locations.length > 0
) {
propsValue.organization_not_locations.forEach((location: unknown) => {
queryParams.append('organization_not_locations[]', location as string);
});
}
if (
propsValue.organization_num_employees_ranges &&
propsValue.organization_num_employees_ranges.length > 0
) {
propsValue.organization_num_employees_ranges.forEach((range: unknown) => {
queryParams.append(
'organization_num_employees_ranges[]',
range as string
);
});
}
if (propsValue.organization_ids && propsValue.organization_ids.length > 0) {
propsValue.organization_ids.forEach((id: unknown) => {
queryParams.append('organization_ids[]', id as string);
});
}
if (
propsValue.q_organization_domains &&
propsValue.q_organization_domains.length > 0
) {
propsValue.q_organization_domains.forEach((domain: unknown) => {
queryParams.append('q_organization_domains[]', domain as string);
});
}
if (
propsValue.organization_industry_tag_ids &&
propsValue.organization_industry_tag_ids.length > 0
) {
propsValue.organization_industry_tag_ids.forEach((tagId: unknown) => {
queryParams.append('organization_industry_tag_ids[]', tagId as string);
});
}
const cacheKey = `_apollo_org_search_${queryParams.toString()}`;
if (propsValue.cacheResponse) {
const cachedResult = await store.get(cacheKey, StoreScope.PROJECT);
if (cachedResult) {
return cachedResult;
}
}
const result = await httpClient.sendRequest<{
organizations: Record<string, unknown>[];
pagination: Record<string, unknown>;
}>({
method: HttpMethod.POST,
url: `https://api.apollo.io/api/v1/mixed_companies/search?${queryParams.toString()}`,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'x-api-key': `${auth.secret_text}`,
},
});
if (propsValue.cacheResponse) {
await store.put(cacheKey, result.body, StoreScope.PROJECT);
}
return result.body;
},
});

View File

@@ -0,0 +1,161 @@
import { apolloAuth } from '../../';
import { HttpMethod, httpClient } from '@activepieces/pieces-common';
import {
Property,
StoreScope,
createAction,
} from '@activepieces/pieces-framework';
export const peopleSearch = createAction({
auth: apolloAuth,
name: 'peopleSearch',
displayName: 'People Search',
description:
'Search for people in the Apollo database (does not return email/phone, use enrichment for that)',
props: {
q_keywords: Property.ShortText({
displayName: 'Keywords',
description: 'Search keywords for people',
required: false,
}),
person_titles: Property.Array({
displayName: 'Person Titles',
description: 'Filter by job titles (e.g., "CEO", "Sales Manager")',
required: false,
}),
person_locations: Property.Array({
displayName: 'Person Locations',
description: 'Filter by person locations',
required: false,
}),
person_seniorities: Property.Array({
displayName: 'Person Seniorities',
description:
'Filter by seniority levels (e.g., "senior", "manager", "director")',
required: false,
}),
organization_ids: Property.Array({
displayName: 'Organization IDs',
description: 'Filter by specific organization IDs',
required: false,
}),
organization_num_employees_ranges: Property.Array({
displayName: 'Organization Employee Ranges',
description:
'Filter by organization employee count ranges (e.g., "1,10", "250,500")',
required: false,
}),
q_organization_domains: Property.Array({
displayName: 'Organization Domains',
description: 'Filter by organization domains',
required: false,
}),
organization_locations: Property.Array({
displayName: 'Organization Locations',
description: 'Filter by organization locations',
required: false,
}),
cacheResponse: Property.Checkbox({
displayName: 'Cache Response',
description: 'Store the response in the project store for future use.',
required: false,
defaultValue: false,
}),
},
async run({ propsValue, auth, store }) {
const queryParams = new URLSearchParams();
if (propsValue.q_keywords) {
queryParams.append('q_keywords', propsValue.q_keywords);
}
if (propsValue.person_titles && propsValue.person_titles.length > 0) {
propsValue.person_titles.forEach((title: unknown) => {
queryParams.append('person_titles[]', title as string);
});
}
if (propsValue.person_locations && propsValue.person_locations.length > 0) {
propsValue.person_locations.forEach((location: unknown) => {
queryParams.append('person_locations[]', location as string);
});
}
if (
propsValue.person_seniorities &&
propsValue.person_seniorities.length > 0
) {
propsValue.person_seniorities.forEach((seniority: unknown) => {
queryParams.append('person_seniorities[]', seniority as string);
});
}
if (propsValue.organization_ids && propsValue.organization_ids.length > 0) {
propsValue.organization_ids.forEach((id: unknown) => {
queryParams.append('organization_ids[]', id as string);
});
}
if (
propsValue.organization_num_employees_ranges &&
propsValue.organization_num_employees_ranges.length > 0
) {
propsValue.organization_num_employees_ranges.forEach((range: unknown) => {
queryParams.append(
'organization_num_employees_ranges[]',
range as string
);
});
}
if (
propsValue.q_organization_domains &&
propsValue.q_organization_domains.length > 0
) {
propsValue.q_organization_domains.forEach((domain: unknown) => {
queryParams.append('q_organization_domains[]', domain as string);
});
}
if (
propsValue.organization_locations &&
propsValue.organization_locations.length > 0
) {
propsValue.organization_locations.forEach((location: unknown) => {
queryParams.append('organization_locations[]', location as string);
});
}
const cacheKey = `_apollo_people_search_${queryParams.toString()}`;
if (propsValue.cacheResponse) {
const cachedResult = await store.get(cacheKey, StoreScope.PROJECT);
if (cachedResult) {
return cachedResult;
}
}
const result = await httpClient.sendRequest<{
people: Record<string, unknown>[];
pagination: Record<string, unknown>;
}>({
method: HttpMethod.POST,
url: `https://api.apollo.io/api/v1/mixed_people/api_search?${queryParams.toString()}`,
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'x-api-key': `${auth.secret_text}`,
},
});
const response = {
people: result.body.people || [],
pagination: result.body.pagination || {},
};
if (propsValue.cacheResponse) {
await store.put(cacheKey, response, StoreScope.PROJECT);
}
return response;
},
});

View File

@@ -0,0 +1,19 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
]
}

View File

@@ -0,0 +1,11 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "../../../../dist/out-tsc",
"declaration": true,
"types": ["node"]
},
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}