@agnocon/piece-bookedin 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.MIT-AP +24 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/actions/bulk-delete-leads.d.ts +4 -0
- package/dist/lib/actions/bulk-delete-leads.d.ts.map +1 -0
- package/dist/lib/actions/bulk-delete-leads.js +41 -0
- package/dist/lib/actions/bulk-delete-leads.js.map +1 -0
- package/dist/lib/actions/create-lead.d.ts +7 -0
- package/dist/lib/actions/create-lead.d.ts.map +1 -0
- package/dist/lib/actions/create-lead.js +59 -0
- package/dist/lib/actions/create-lead.js.map +1 -0
- package/dist/lib/actions/delete-lead.d.ts +4 -0
- package/dist/lib/actions/delete-lead.d.ts.map +1 -0
- package/dist/lib/actions/delete-lead.js +33 -0
- package/dist/lib/actions/delete-lead.js.map +1 -0
- package/dist/lib/actions/get-lead-stats.d.ts +2 -0
- package/dist/lib/actions/get-lead-stats.d.ts.map +1 -0
- package/dist/lib/actions/get-lead-stats.js +31 -0
- package/dist/lib/actions/get-lead-stats.js.map +1 -0
- package/dist/lib/actions/get-lead.d.ts +4 -0
- package/dist/lib/actions/get-lead.d.ts.map +1 -0
- package/dist/lib/actions/get-lead.js +33 -0
- package/dist/lib/actions/get-lead.js.map +1 -0
- package/dist/lib/actions/get-leads.d.ts +9 -0
- package/dist/lib/actions/get-leads.d.ts.map +1 -0
- package/dist/lib/actions/get-leads.js +78 -0
- package/dist/lib/actions/get-leads.js.map +1 -0
- package/dist/lib/actions/update-lead.d.ts +10 -0
- package/dist/lib/actions/update-lead.d.ts.map +1 -0
- package/dist/lib/actions/update-lead.js +86 -0
- package/dist/lib/actions/update-lead.js.map +1 -0
- package/dist/lib/auth.d.ts +2 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/auth.js +54 -0
- package/dist/lib/auth.js.map +1 -0
- package/dist/lib/common/props.d.ts +9 -0
- package/dist/lib/common/props.d.ts.map +1 -0
- package/dist/lib/common/props.js +125 -0
- package/dist/lib/common/props.js.map +1 -0
- package/package.json +46 -0
- package/src/index.ts +43 -0
- package/src/lib/actions/bulk-delete-leads.ts +40 -0
- package/src/lib/actions/create-lead.ts +57 -0
- package/src/lib/actions/delete-lead.ts +27 -0
- package/src/lib/actions/get-lead-stats.ts +25 -0
- package/src/lib/actions/get-lead.ts +27 -0
- package/src/lib/actions/get-leads.ts +69 -0
- package/src/lib/actions/update-lead.ts +92 -0
- package/src/lib/auth.ts +48 -0
- package/src/lib/common/props.ts +138 -0
package/dist/lib/auth.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.bookedinAuth = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
// Vendored from Activepieces packages/pieces/community/bookedin/src/lib/auth.ts (MIT).
|
|
6
|
+
// Generated by packages/scripts/vendor-ap.mjs - do not edit by hand.
|
|
7
|
+
const pieces_framework_1 = require("@agnocon/pieces-framework");
|
|
8
|
+
const pieces_common_1 = require("@agnocon/pieces-common");
|
|
9
|
+
const props_1 = require("./common/props");
|
|
10
|
+
exports.bookedinAuth = pieces_framework_1.PieceAuth.SecretText({
|
|
11
|
+
displayName: 'API Key',
|
|
12
|
+
required: true,
|
|
13
|
+
description: `To connect your Bookedin account, please follow these steps to retrieve your API Key:
|
|
14
|
+
|
|
15
|
+
1. Log in to your Bookedin Dashboard: dashboard.bookedin.ai
|
|
16
|
+
2. From the left menu, go to **Business**
|
|
17
|
+
3. Open **Settings**
|
|
18
|
+
4. Click on **API Key**
|
|
19
|
+
5. Copy your API Key (starts with **sk_…**)
|
|
20
|
+
6. Paste the key below to authorize the integration
|
|
21
|
+
|
|
22
|
+
Your API Key allows Activepieces to securely access your Bookedin leads, agents, and booking data.
|
|
23
|
+
`,
|
|
24
|
+
validate: (_a) => tslib_1.__awaiter(void 0, [_a], void 0, function* ({ auth }) {
|
|
25
|
+
var _b, _c;
|
|
26
|
+
try {
|
|
27
|
+
const apiKey = (0, props_1.extractApiKey)(auth);
|
|
28
|
+
if (!apiKey) {
|
|
29
|
+
return { valid: false, error: 'API Key is empty' };
|
|
30
|
+
}
|
|
31
|
+
yield pieces_common_1.httpClient.sendRequest({
|
|
32
|
+
method: pieces_common_1.HttpMethod.GET,
|
|
33
|
+
url: `${props_1.BASE_URL}/agents/`,
|
|
34
|
+
headers: {
|
|
35
|
+
'X-API-Key': apiKey,
|
|
36
|
+
'accept': 'application/json'
|
|
37
|
+
},
|
|
38
|
+
queryParams: {
|
|
39
|
+
skip: '0',
|
|
40
|
+
limit: '1'
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return { valid: true };
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
const errorMessage = ((_c = (_b = e === null || e === void 0 ? void 0 : e.response) === null || _b === void 0 ? void 0 : _b.body) === null || _c === void 0 ? void 0 : _c.detail) || (e === null || e === void 0 ? void 0 : e.message) || 'Connection failed';
|
|
47
|
+
return {
|
|
48
|
+
valid: false,
|
|
49
|
+
error: errorMessage
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}),
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/lib/auth.ts"],"names":[],"mappings":";;;;AAAA,uFAAuF;AACvF,qEAAqE;AACrE,gEAAsD;AACtD,0DAAgE;AAChE,0CAAyD;AAE5C,QAAA,YAAY,GAAG,4BAAS,CAAC,UAAU,CAAC;IAC/C,WAAW,EAAE,SAAS;IACtB,QAAQ,EAAE,IAAI;IACd,WAAW,EAAE;;;;;;;;;;KAUV;IACH,QAAQ,EAAE,KAAiB,EAAE,oDAAZ,EAAE,IAAI,EAAE;;QACvB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAA,qBAAa,EAAC,IAAI,CAAC,CAAC;YAEnC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC;YACrD,CAAC;YAED,MAAM,0BAAU,CAAC,WAAW,CAAC;gBAC3B,MAAM,EAAE,0BAAU,CAAC,GAAG;gBACtB,GAAG,EAAE,GAAG,gBAAQ,UAAU;gBAC1B,OAAO,EAAE;oBACP,WAAW,EAAE,MAAgB;oBAC7B,QAAQ,EAAE,kBAAkB;iBAC7B;gBACD,WAAW,EAAE;oBACT,IAAI,EAAE,GAAG;oBACT,KAAK,EAAE,GAAG;iBACb;aACF,CAAC,CAAC;YACH,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACzB,CAAC;QAAC,OAAO,CAAM,EAAE,CAAC;YAChB,MAAM,YAAY,GAAG,CAAA,MAAA,MAAA,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,QAAQ,0CAAE,IAAI,0CAAE,MAAM,MAAI,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,OAAO,CAAA,IAAI,mBAAmB,CAAC;YACpF,OAAO;gBACL,KAAK,EAAE,KAAK;gBACZ,KAAK,EAAE,YAAY;aACpB,CAAC;QACJ,CAAC;IACH,CAAC,CAAA;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const BASE_URL = "https://api.bookedin.ai/api/v1";
|
|
2
|
+
export declare const getBookedinHeaders: (apiKey: string) => {
|
|
3
|
+
'X-API-Key': string;
|
|
4
|
+
accept: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const extractApiKey: (auth: unknown) => string;
|
|
7
|
+
export declare const leadIdDropdown: import("@agnocon/pieces-framework").DropdownProperty<string, true, import("@agnocon/pieces-framework").SecretTextProperty<true>>;
|
|
8
|
+
export declare const leadIdsMultiSelectDropdown: import("@agnocon/pieces-framework").MultiSelectDropdownProperty<string, true, import("@agnocon/pieces-framework").SecretTextProperty<true>>;
|
|
9
|
+
//# sourceMappingURL=props.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"props.d.ts","sourceRoot":"","sources":["../../../src/lib/common/props.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,QAAQ,mCAAmC,CAAC;AAEzD,eAAO,MAAM,kBAAkB,WAAY,MAAM;;;CAKhD,CAAC;AAGF,eAAO,MAAM,aAAa,SAAU,OAAO,KAAG,MAM7C,CAAC;AAuCF,eAAO,MAAM,cAAc,kIAsCzB,CAAC;AAEH,eAAO,MAAM,0BAA0B,6IAsCrC,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.leadIdsMultiSelectDropdown = exports.leadIdDropdown = exports.extractApiKey = exports.getBookedinHeaders = exports.BASE_URL = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
// Vendored from Activepieces packages/pieces/community/bookedin/src/lib/common/props.ts (MIT).
|
|
6
|
+
// Generated by packages/scripts/vendor-ap.mjs - do not edit by hand.
|
|
7
|
+
const pieces_framework_1 = require("@agnocon/pieces-framework");
|
|
8
|
+
const pieces_common_1 = require("@agnocon/pieces-common");
|
|
9
|
+
const auth_1 = require("../auth");
|
|
10
|
+
exports.BASE_URL = 'https://api.bookedin.ai/api/v1';
|
|
11
|
+
const getBookedinHeaders = (apiKey) => {
|
|
12
|
+
return {
|
|
13
|
+
'X-API-Key': apiKey,
|
|
14
|
+
'accept': 'application/json',
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
exports.getBookedinHeaders = getBookedinHeaders;
|
|
18
|
+
const extractApiKey = (auth) => {
|
|
19
|
+
if (typeof auth === 'string') {
|
|
20
|
+
return auth;
|
|
21
|
+
}
|
|
22
|
+
const authObj = auth;
|
|
23
|
+
return (authObj === null || authObj === void 0 ? void 0 : authObj.secret_text) || (authObj === null || authObj === void 0 ? void 0 : authObj.auth) || '';
|
|
24
|
+
};
|
|
25
|
+
exports.extractApiKey = extractApiKey;
|
|
26
|
+
const fetchLeadOptions = (apiKey) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
27
|
+
const response = yield pieces_common_1.httpClient.sendRequest({
|
|
28
|
+
method: pieces_common_1.HttpMethod.GET,
|
|
29
|
+
url: `${exports.BASE_URL}/leads/`,
|
|
30
|
+
headers: (0, exports.getBookedinHeaders)(apiKey),
|
|
31
|
+
queryParams: {
|
|
32
|
+
limit: '100',
|
|
33
|
+
skip: '0',
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
return response.body.items.map((lead) => {
|
|
37
|
+
var _a, _b, _c, _d, _e;
|
|
38
|
+
const firstName = ((_b = (_a = lead.contact) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.first) || '';
|
|
39
|
+
const lastName = ((_d = (_c = lead.contact) === null || _c === void 0 ? void 0 : _c.name) === null || _d === void 0 ? void 0 : _d.last) || '';
|
|
40
|
+
const email = ((_e = lead.contact) === null || _e === void 0 ? void 0 : _e.email) || '';
|
|
41
|
+
const name = [firstName, lastName].filter(Boolean).join(' ').trim();
|
|
42
|
+
const label = name ? `${name} (${email})` : email || lead.id;
|
|
43
|
+
return {
|
|
44
|
+
label,
|
|
45
|
+
value: lead.id,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
exports.leadIdDropdown = pieces_framework_1.Property.Dropdown({
|
|
50
|
+
auth: auth_1.bookedinAuth,
|
|
51
|
+
displayName: 'Lead',
|
|
52
|
+
description: 'Select a lead',
|
|
53
|
+
required: true,
|
|
54
|
+
refreshers: [],
|
|
55
|
+
options: (_a) => tslib_1.__awaiter(void 0, [_a], void 0, function* ({ auth }) {
|
|
56
|
+
if (!auth) {
|
|
57
|
+
return {
|
|
58
|
+
disabled: true,
|
|
59
|
+
placeholder: 'Please connect your account first',
|
|
60
|
+
options: [],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const apiKey = (0, exports.extractApiKey)(auth);
|
|
65
|
+
if (!apiKey) {
|
|
66
|
+
return {
|
|
67
|
+
disabled: true,
|
|
68
|
+
placeholder: 'API key is missing',
|
|
69
|
+
options: [],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const options = yield fetchLeadOptions(apiKey);
|
|
73
|
+
return {
|
|
74
|
+
disabled: false,
|
|
75
|
+
options,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
disabled: true,
|
|
81
|
+
placeholder: 'Failed to load leads. Please check your connection.',
|
|
82
|
+
options: [],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
}),
|
|
86
|
+
});
|
|
87
|
+
exports.leadIdsMultiSelectDropdown = pieces_framework_1.Property.MultiSelectDropdown({
|
|
88
|
+
auth: auth_1.bookedinAuth,
|
|
89
|
+
displayName: 'Leads',
|
|
90
|
+
description: 'Select leads to delete (max 500)',
|
|
91
|
+
required: true,
|
|
92
|
+
refreshers: [],
|
|
93
|
+
options: (_a) => tslib_1.__awaiter(void 0, [_a], void 0, function* ({ auth }) {
|
|
94
|
+
if (!auth) {
|
|
95
|
+
return {
|
|
96
|
+
disabled: true,
|
|
97
|
+
placeholder: 'Please connect your account first',
|
|
98
|
+
options: [],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const apiKey = (0, exports.extractApiKey)(auth);
|
|
103
|
+
if (!apiKey) {
|
|
104
|
+
return {
|
|
105
|
+
disabled: true,
|
|
106
|
+
placeholder: 'API key is missing',
|
|
107
|
+
options: [],
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const options = yield fetchLeadOptions(apiKey);
|
|
111
|
+
return {
|
|
112
|
+
disabled: false,
|
|
113
|
+
options,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
return {
|
|
118
|
+
disabled: true,
|
|
119
|
+
placeholder: 'Failed to load leads. Please check your connection.',
|
|
120
|
+
options: [],
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}),
|
|
124
|
+
});
|
|
125
|
+
//# sourceMappingURL=props.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"props.js","sourceRoot":"","sources":["../../../src/lib/common/props.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agnocon/piece-bookedin",
|
|
3
|
+
"version": "0.1.7",
|
|
4
|
+
"description": "AgnoCon bookedin connector",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"src",
|
|
17
|
+
"LICENSE.MIT-AP"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc -p tsconfig.json",
|
|
21
|
+
"vendor": "node ../scripts/vendor-ap.mjs piece-bookedin"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@agnocon/core-utils": "0.2.0",
|
|
25
|
+
"@agnocon/piece-types": "0.3.1",
|
|
26
|
+
"@agnocon/pieces-common": "0.12.8",
|
|
27
|
+
"@agnocon/pieces-framework": "0.36.0",
|
|
28
|
+
"tslib": "2.6.2"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "20.17.6",
|
|
32
|
+
"typescript": "5.5.4"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"agnocon": {
|
|
36
|
+
"originVersion": "0.1.7"
|
|
37
|
+
},
|
|
38
|
+
"publishConfig": {
|
|
39
|
+
"access": "restricted",
|
|
40
|
+
"tag": "latest"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [
|
|
43
|
+
"agnocon",
|
|
44
|
+
"connector"
|
|
45
|
+
]
|
|
46
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createPiece, PieceAuth } from "@agnocon/pieces-framework";
|
|
2
|
+
import { httpClient, HttpMethod, createCustomApiCallAction } from '@agnocon/pieces-common';
|
|
3
|
+
import { BASE_URL, extractApiKey } from './lib/common/props';
|
|
4
|
+
import { getLeads } from "./lib/actions/get-leads";
|
|
5
|
+
import { createLead } from "./lib/actions/create-lead";
|
|
6
|
+
import { getLead } from "./lib/actions/get-lead";
|
|
7
|
+
import { deleteLead } from "./lib/actions/delete-lead";
|
|
8
|
+
import { getLeadStats } from "./lib/actions/get-lead-stats";
|
|
9
|
+
import { updateLead } from "./lib/actions/update-lead";
|
|
10
|
+
import { PieceCategory } from '@agnocon/pieces-framework';
|
|
11
|
+
import { bookedinAuth } from './lib/auth';
|
|
12
|
+
|
|
13
|
+
// --- Authentication ---
|
|
14
|
+
// --- Piece Definition ---
|
|
15
|
+
export const bookedin = createPiece({
|
|
16
|
+
displayName: 'Bookedin',
|
|
17
|
+
description: 'AI agents for lead conversion and appointment booking.',
|
|
18
|
+
logoUrl: 'https://cdn.activepieces.com/pieces/bookedin.png',
|
|
19
|
+
categories: [PieceCategory.SALES_AND_CRM],
|
|
20
|
+
auth: bookedinAuth,
|
|
21
|
+
minimumSupportedRelease: '0.36.1',
|
|
22
|
+
authors: ["drona2938", "onyedikachi-david"],
|
|
23
|
+
actions: [
|
|
24
|
+
getLeads,
|
|
25
|
+
createLead,
|
|
26
|
+
getLead,
|
|
27
|
+
deleteLead,
|
|
28
|
+
getLeadStats,
|
|
29
|
+
updateLead,
|
|
30
|
+
createCustomApiCallAction({
|
|
31
|
+
baseUrl: () => BASE_URL,
|
|
32
|
+
auth: bookedinAuth,
|
|
33
|
+
authMapping: async (auth) => {
|
|
34
|
+
const apiKey = extractApiKey(auth);
|
|
35
|
+
return {
|
|
36
|
+
'X-API-Key': apiKey,
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
}),
|
|
40
|
+
],
|
|
41
|
+
triggers: [],
|
|
42
|
+
});
|
|
43
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createAction } from '@agnocon/pieces-framework';
|
|
2
|
+
import { httpClient, HttpMethod } from '@agnocon/pieces-common';
|
|
3
|
+
import { bookedinAuth } from '../auth';
|
|
4
|
+
import { BASE_URL, getBookedinHeaders, leadIdsMultiSelectDropdown, extractApiKey } from '../common/props';
|
|
5
|
+
|
|
6
|
+
export const bulkDeleteLeads = createAction({
|
|
7
|
+
name: 'bulkDeleteLeads',
|
|
8
|
+
displayName: 'Bulk Delete Leads',
|
|
9
|
+
description: 'Delete multiple leads (max 500 per request)',
|
|
10
|
+
auth: bookedinAuth,
|
|
11
|
+
props: {
|
|
12
|
+
lead_ids: leadIdsMultiSelectDropdown,
|
|
13
|
+
},
|
|
14
|
+
async run({ auth, propsValue }) {
|
|
15
|
+
const apiKey = extractApiKey(auth);
|
|
16
|
+
const leadIds = Array.isArray(propsValue.lead_ids) ? propsValue.lead_ids : [propsValue.lead_ids];
|
|
17
|
+
|
|
18
|
+
if (leadIds.length === 0) {
|
|
19
|
+
throw new Error('At least one lead must be selected');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (leadIds.length > 500) {
|
|
23
|
+
throw new Error('Maximum 500 leads can be deleted per request');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const response = await httpClient.sendRequest({
|
|
27
|
+
method: HttpMethod.POST,
|
|
28
|
+
url: `${BASE_URL}/leads/bulk-delete`,
|
|
29
|
+
headers: {
|
|
30
|
+
...getBookedinHeaders(apiKey),
|
|
31
|
+
'Content-Type': 'application/json',
|
|
32
|
+
},
|
|
33
|
+
body: {
|
|
34
|
+
lead_ids: leadIds,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
return response.body;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createAction, Property } from '@agnocon/pieces-framework';
|
|
2
|
+
import { httpClient, HttpMethod } from '@agnocon/pieces-common';
|
|
3
|
+
import { bookedinAuth } from '../auth';
|
|
4
|
+
import { BASE_URL, getBookedinHeaders, extractApiKey } from '../common/props';
|
|
5
|
+
|
|
6
|
+
export const createLead = createAction({
|
|
7
|
+
name: 'createLead',
|
|
8
|
+
displayName: 'Create Lead',
|
|
9
|
+
description: 'Creates a new lead in Bookedin AI',
|
|
10
|
+
audience: 'both',
|
|
11
|
+
aiMetadata: { description: 'Create a new lead in Bookedin for the authenticated business from a contact first name, last name, email, and phone (all required). Use it to add a prospect into the lead pipeline. Not idempotent — each call creates a separate lead, with no de-duplication on email or phone.', idempotent: false },
|
|
12
|
+
auth: bookedinAuth,
|
|
13
|
+
props: {
|
|
14
|
+
firstName: Property.ShortText({
|
|
15
|
+
displayName: 'First Name',
|
|
16
|
+
required: true,
|
|
17
|
+
}),
|
|
18
|
+
lastName: Property.ShortText({
|
|
19
|
+
displayName: 'Last Name',
|
|
20
|
+
required: true,
|
|
21
|
+
}),
|
|
22
|
+
email: Property.ShortText({
|
|
23
|
+
displayName: 'Email',
|
|
24
|
+
required: true,
|
|
25
|
+
}),
|
|
26
|
+
phone: Property.ShortText({
|
|
27
|
+
displayName: 'Phone Number',
|
|
28
|
+
required: true,
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
async run({ auth, propsValue }) {
|
|
32
|
+
const apiKey = extractApiKey(auth);
|
|
33
|
+
|
|
34
|
+
const payload = {
|
|
35
|
+
contact: {
|
|
36
|
+
name: {
|
|
37
|
+
last: propsValue.lastName,
|
|
38
|
+
first: propsValue.firstName,
|
|
39
|
+
},
|
|
40
|
+
email: propsValue.email,
|
|
41
|
+
number: propsValue.phone,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const response = await httpClient.sendRequest({
|
|
46
|
+
method: HttpMethod.POST,
|
|
47
|
+
url: `${BASE_URL}/leads/`,
|
|
48
|
+
headers: {
|
|
49
|
+
...getBookedinHeaders(apiKey),
|
|
50
|
+
'Content-Type': 'application/json',
|
|
51
|
+
},
|
|
52
|
+
body: payload,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return response.body;
|
|
56
|
+
},
|
|
57
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createAction } from '@agnocon/pieces-framework';
|
|
2
|
+
import { httpClient, HttpMethod } from '@agnocon/pieces-common';
|
|
3
|
+
import { bookedinAuth } from '../auth';
|
|
4
|
+
import { BASE_URL, getBookedinHeaders, leadIdDropdown, extractApiKey } from '../common/props';
|
|
5
|
+
|
|
6
|
+
export const deleteLead = createAction({
|
|
7
|
+
name: 'deleteLead',
|
|
8
|
+
displayName: 'Delete Lead',
|
|
9
|
+
description: 'Delete a lead.',
|
|
10
|
+
audience: 'both',
|
|
11
|
+
aiMetadata: { description: 'Permanently delete a lead by its Bookedin lead ID. Use it to remove a known lead from the pipeline; this is destructive and cannot be undone. Idempotent on the end state — once the lead is gone, re-running targets an already-removed record.', idempotent: true },
|
|
12
|
+
auth: bookedinAuth,
|
|
13
|
+
props: {
|
|
14
|
+
lead_id: leadIdDropdown,
|
|
15
|
+
},
|
|
16
|
+
async run({ auth, propsValue }) {
|
|
17
|
+
const apiKey = extractApiKey(auth);
|
|
18
|
+
|
|
19
|
+
const response = await httpClient.sendRequest({
|
|
20
|
+
method: HttpMethod.DELETE,
|
|
21
|
+
url: `${BASE_URL}/leads/${propsValue.lead_id}`,
|
|
22
|
+
headers: getBookedinHeaders(apiKey),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return response.body;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { createAction } from '@agnocon/pieces-framework';
|
|
2
|
+
import { httpClient, HttpMethod } from '@agnocon/pieces-common';
|
|
3
|
+
import { bookedinAuth } from '../auth';
|
|
4
|
+
import { BASE_URL, getBookedinHeaders, extractApiKey } from '../common/props';
|
|
5
|
+
|
|
6
|
+
export const getLeadStats = createAction({
|
|
7
|
+
name: 'getLeadStats',
|
|
8
|
+
displayName: 'Get Lead Stats',
|
|
9
|
+
description: 'Get lead statistics (Hot, Warm, Cold, Objectives Met, Total).',
|
|
10
|
+
audience: 'both',
|
|
11
|
+
aiMetadata: { description: 'Get aggregate lead statistics for the authenticated Bookedin business, such as counts of Hot, Warm, and Cold leads, objectives met, and the total. Use it for a quick pipeline summary rather than fetching individual leads. Takes no input; read-only and idempotent.', idempotent: true },
|
|
12
|
+
auth: bookedinAuth,
|
|
13
|
+
props: {},
|
|
14
|
+
async run({ auth }) {
|
|
15
|
+
const apiKey = extractApiKey(auth);
|
|
16
|
+
|
|
17
|
+
const response = await httpClient.sendRequest({
|
|
18
|
+
method: HttpMethod.GET,
|
|
19
|
+
url: `${BASE_URL}/leads/stats`,
|
|
20
|
+
headers: getBookedinHeaders(apiKey),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
return response.body;
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createAction } from '@agnocon/pieces-framework';
|
|
2
|
+
import { httpClient, HttpMethod } from '@agnocon/pieces-common';
|
|
3
|
+
import { bookedinAuth } from '../auth';
|
|
4
|
+
import { BASE_URL, getBookedinHeaders, leadIdDropdown, extractApiKey } from '../common/props';
|
|
5
|
+
|
|
6
|
+
export const getLead = createAction({
|
|
7
|
+
name: 'getLead',
|
|
8
|
+
displayName: 'Get Lead',
|
|
9
|
+
description: 'Get a specific lead by ID.',
|
|
10
|
+
audience: 'both',
|
|
11
|
+
aiMetadata: { description: 'Fetch a single lead by its Bookedin lead ID. Use it to read the full record for one known lead; if you only have a name, email, or phone, find the ID first with Get Leads. Read-only and idempotent.', idempotent: true },
|
|
12
|
+
auth: bookedinAuth,
|
|
13
|
+
props: {
|
|
14
|
+
lead_id: leadIdDropdown,
|
|
15
|
+
},
|
|
16
|
+
async run({ auth, propsValue }) {
|
|
17
|
+
const apiKey = extractApiKey(auth);
|
|
18
|
+
|
|
19
|
+
const response = await httpClient.sendRequest({
|
|
20
|
+
method: HttpMethod.GET,
|
|
21
|
+
url: `${BASE_URL}/leads/${propsValue.lead_id}`,
|
|
22
|
+
headers: getBookedinHeaders(apiKey),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return response.body;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createAction, Property } from '@agnocon/pieces-framework';
|
|
2
|
+
import { httpClient, HttpMethod } from '@agnocon/pieces-common';
|
|
3
|
+
import { bookedinAuth } from '../auth';
|
|
4
|
+
import { BASE_URL, getBookedinHeaders, extractApiKey } from '../common/props';
|
|
5
|
+
|
|
6
|
+
export const getLeads = createAction({
|
|
7
|
+
name: 'getLeads',
|
|
8
|
+
displayName: 'Get Leads',
|
|
9
|
+
description: 'Get all leads for the current business with pagination metadata.',
|
|
10
|
+
audience: 'both',
|
|
11
|
+
aiMetadata: { description: 'List leads for the authenticated Bookedin business, returning a page with pagination metadata. Use it to browse or find leads; leave the filter fields empty to fetch all, or narrow by free-text search (name/email/phone), exact email, phone, or source, and page through results with limit/skip. Read-only and idempotent.', idempotent: true },
|
|
12
|
+
auth: bookedinAuth,
|
|
13
|
+
props: {
|
|
14
|
+
search: Property.ShortText({
|
|
15
|
+
displayName: 'Search',
|
|
16
|
+
description: 'Search text in name, email, or phone number',
|
|
17
|
+
required: false,
|
|
18
|
+
}),
|
|
19
|
+
source: Property.ShortText({
|
|
20
|
+
displayName: 'Source',
|
|
21
|
+
description: 'Filter by lead source (e.g., "API", "Import")',
|
|
22
|
+
required: false,
|
|
23
|
+
}),
|
|
24
|
+
email: Property.ShortText({
|
|
25
|
+
displayName: 'Email',
|
|
26
|
+
description: 'Filter by exact email address',
|
|
27
|
+
required: false,
|
|
28
|
+
}),
|
|
29
|
+
phone: Property.ShortText({
|
|
30
|
+
displayName: 'Phone',
|
|
31
|
+
description: 'Filter by phone number',
|
|
32
|
+
required: false,
|
|
33
|
+
}),
|
|
34
|
+
limit: Property.Number({
|
|
35
|
+
displayName: 'Limit',
|
|
36
|
+
description: 'Number of leads to return',
|
|
37
|
+
required: false,
|
|
38
|
+
defaultValue: 100,
|
|
39
|
+
}),
|
|
40
|
+
skip: Property.Number({
|
|
41
|
+
displayName: 'Skip',
|
|
42
|
+
description: 'Number of leads to skip (pagination)',
|
|
43
|
+
required: false,
|
|
44
|
+
defaultValue: 0,
|
|
45
|
+
}),
|
|
46
|
+
},
|
|
47
|
+
async run({ auth, propsValue }) {
|
|
48
|
+
const apiKey = extractApiKey(auth);
|
|
49
|
+
|
|
50
|
+
const queryParams: Record<string, string> = {
|
|
51
|
+
limit: (propsValue.limit ?? 100).toString(),
|
|
52
|
+
skip: (propsValue.skip ?? 0).toString(),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (propsValue.search) queryParams['search'] = propsValue.search;
|
|
56
|
+
if (propsValue.source) queryParams['source'] = propsValue.source;
|
|
57
|
+
if (propsValue.email) queryParams['email'] = propsValue.email;
|
|
58
|
+
if (propsValue.phone) queryParams['phone'] = propsValue.phone;
|
|
59
|
+
|
|
60
|
+
const response = await httpClient.sendRequest({
|
|
61
|
+
method: HttpMethod.GET,
|
|
62
|
+
url: `${BASE_URL}/leads/`,
|
|
63
|
+
headers: getBookedinHeaders(apiKey),
|
|
64
|
+
queryParams,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return response.body;
|
|
68
|
+
},
|
|
69
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { createAction, Property } from '@agnocon/pieces-framework';
|
|
2
|
+
import { httpClient, HttpMethod } from '@agnocon/pieces-common';
|
|
3
|
+
import { bookedinAuth } from '../auth';
|
|
4
|
+
import { BASE_URL, getBookedinHeaders, leadIdDropdown, extractApiKey } from '../common/props';
|
|
5
|
+
import { isNil } from '@agnocon/pieces-framework';
|
|
6
|
+
|
|
7
|
+
export const updateLead = createAction({
|
|
8
|
+
name: 'updateLead',
|
|
9
|
+
displayName: 'Update Lead',
|
|
10
|
+
description: 'Update a lead.',
|
|
11
|
+
audience: 'both',
|
|
12
|
+
aiMetadata: { description: 'Update an existing lead identified by its Bookedin lead ID. Supply any of the individual contact fields (first/last name, email, phone) and handling status, and/or pass a raw JSON payload for complex updates that merges over those fields. Use it to edit a known lead; only the fields you provide are changed. Idempotent — re-applying the same values yields the same lead state.', idempotent: true },
|
|
13
|
+
auth: bookedinAuth,
|
|
14
|
+
props: {
|
|
15
|
+
lead_id: leadIdDropdown,
|
|
16
|
+
firstName: Property.ShortText({
|
|
17
|
+
displayName: 'First Name',
|
|
18
|
+
required: false,
|
|
19
|
+
}),
|
|
20
|
+
lastName: Property.ShortText({
|
|
21
|
+
displayName: 'Last Name',
|
|
22
|
+
required: false,
|
|
23
|
+
}),
|
|
24
|
+
email: Property.ShortText({
|
|
25
|
+
displayName: 'Email',
|
|
26
|
+
required: false,
|
|
27
|
+
}),
|
|
28
|
+
phone: Property.ShortText({
|
|
29
|
+
displayName: 'Phone Number',
|
|
30
|
+
required: false,
|
|
31
|
+
}),
|
|
32
|
+
handling_status: Property.ShortText({
|
|
33
|
+
displayName: 'Handling Status',
|
|
34
|
+
required: false,
|
|
35
|
+
}),
|
|
36
|
+
update_json: Property.Json({
|
|
37
|
+
displayName: 'Update Payload (JSON)',
|
|
38
|
+
description: 'Optional JSON body for complex updates. Merges with individual fields above.',
|
|
39
|
+
required: false,
|
|
40
|
+
defaultValue: {},
|
|
41
|
+
}),
|
|
42
|
+
},
|
|
43
|
+
async run({ auth, propsValue }) {
|
|
44
|
+
const apiKey = extractApiKey(auth);
|
|
45
|
+
const basePayload: Record<string, unknown> = {};
|
|
46
|
+
|
|
47
|
+
if (!isNil(propsValue.firstName) || !isNil(propsValue.lastName) || !isNil(propsValue.email) || !isNil(propsValue.phone)) {
|
|
48
|
+
const contact: Record<string, unknown> = {};
|
|
49
|
+
|
|
50
|
+
const nameParts: Record<string, string> = {};
|
|
51
|
+
if (!isNil(propsValue.firstName) && propsValue.firstName !== '') {
|
|
52
|
+
nameParts['first'] = propsValue.firstName;
|
|
53
|
+
}
|
|
54
|
+
if (!isNil(propsValue.lastName) && propsValue.lastName !== '') {
|
|
55
|
+
nameParts['last'] = propsValue.lastName;
|
|
56
|
+
}
|
|
57
|
+
if (Object.keys(nameParts).length > 0) {
|
|
58
|
+
contact['name'] = nameParts;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!isNil(propsValue.email) && propsValue.email !== '') {
|
|
62
|
+
contact['email'] = propsValue.email;
|
|
63
|
+
}
|
|
64
|
+
if (!isNil(propsValue.phone) && propsValue.phone !== '') {
|
|
65
|
+
contact['number'] = propsValue.phone;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
basePayload['contact'] = contact;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!isNil(propsValue.handling_status) && propsValue.handling_status !== '') {
|
|
72
|
+
basePayload['handling_status'] = propsValue.handling_status;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const finalPayload = {
|
|
76
|
+
...basePayload,
|
|
77
|
+
...(propsValue.update_json || {}),
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const response = await httpClient.sendRequest({
|
|
81
|
+
method: HttpMethod.PUT,
|
|
82
|
+
url: `${BASE_URL}/leads/${propsValue.lead_id}`,
|
|
83
|
+
headers: {
|
|
84
|
+
...getBookedinHeaders(apiKey),
|
|
85
|
+
'Content-Type': 'application/json',
|
|
86
|
+
},
|
|
87
|
+
body: finalPayload,
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return response.body;
|
|
91
|
+
},
|
|
92
|
+
});
|