universal-mcp-applications 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (268) hide show
  1. universal_mcp/applications/ahrefs/README.md +51 -0
  2. universal_mcp/applications/ahrefs/__init__.py +1 -0
  3. universal_mcp/applications/ahrefs/app.py +2291 -0
  4. universal_mcp/applications/airtable/README.md +22 -0
  5. universal_mcp/applications/airtable/__init__.py +1 -0
  6. universal_mcp/applications/airtable/app.py +479 -0
  7. universal_mcp/applications/apollo/README.md +44 -0
  8. universal_mcp/applications/apollo/__init__.py +1 -0
  9. universal_mcp/applications/apollo/app.py +1847 -0
  10. universal_mcp/applications/asana/README.md +199 -0
  11. universal_mcp/applications/asana/__init__.py +1 -0
  12. universal_mcp/applications/asana/app.py +9509 -0
  13. universal_mcp/applications/aws-s3/README.md +0 -0
  14. universal_mcp/applications/aws-s3/__init__.py +1 -0
  15. universal_mcp/applications/aws-s3/app.py +552 -0
  16. universal_mcp/applications/bill/README.md +0 -0
  17. universal_mcp/applications/bill/__init__.py +1 -0
  18. universal_mcp/applications/bill/app.py +8705 -0
  19. universal_mcp/applications/box/README.md +307 -0
  20. universal_mcp/applications/box/__init__.py +1 -0
  21. universal_mcp/applications/box/app.py +15987 -0
  22. universal_mcp/applications/braze/README.md +106 -0
  23. universal_mcp/applications/braze/__init__.py +1 -0
  24. universal_mcp/applications/braze/app.py +4754 -0
  25. universal_mcp/applications/cal-com-v2/README.md +150 -0
  26. universal_mcp/applications/cal-com-v2/__init__.py +1 -0
  27. universal_mcp/applications/cal-com-v2/app.py +5541 -0
  28. universal_mcp/applications/calendly/README.md +53 -0
  29. universal_mcp/applications/calendly/__init__.py +1 -0
  30. universal_mcp/applications/calendly/app.py +1436 -0
  31. universal_mcp/applications/canva/README.md +43 -0
  32. universal_mcp/applications/canva/__init__.py +1 -0
  33. universal_mcp/applications/canva/app.py +941 -0
  34. universal_mcp/applications/clickup/README.md +135 -0
  35. universal_mcp/applications/clickup/__init__.py +1 -0
  36. universal_mcp/applications/clickup/app.py +5009 -0
  37. universal_mcp/applications/coda/README.md +108 -0
  38. universal_mcp/applications/coda/__init__.py +1 -0
  39. universal_mcp/applications/coda/app.py +3671 -0
  40. universal_mcp/applications/confluence/README.md +198 -0
  41. universal_mcp/applications/confluence/__init__.py +1 -0
  42. universal_mcp/applications/confluence/app.py +6273 -0
  43. universal_mcp/applications/contentful/README.md +17 -0
  44. universal_mcp/applications/contentful/__init__.py +1 -0
  45. universal_mcp/applications/contentful/app.py +364 -0
  46. universal_mcp/applications/crustdata/README.md +25 -0
  47. universal_mcp/applications/crustdata/__init__.py +1 -0
  48. universal_mcp/applications/crustdata/app.py +586 -0
  49. universal_mcp/applications/dialpad/README.md +202 -0
  50. universal_mcp/applications/dialpad/__init__.py +1 -0
  51. universal_mcp/applications/dialpad/app.py +5949 -0
  52. universal_mcp/applications/digitalocean/README.md +463 -0
  53. universal_mcp/applications/digitalocean/__init__.py +1 -0
  54. universal_mcp/applications/digitalocean/app.py +20835 -0
  55. universal_mcp/applications/domain-checker/README.md +13 -0
  56. universal_mcp/applications/domain-checker/__init__.py +1 -0
  57. universal_mcp/applications/domain-checker/app.py +265 -0
  58. universal_mcp/applications/e2b/README.md +12 -0
  59. universal_mcp/applications/e2b/__init__.py +1 -0
  60. universal_mcp/applications/e2b/app.py +187 -0
  61. universal_mcp/applications/elevenlabs/README.md +88 -0
  62. universal_mcp/applications/elevenlabs/__init__.py +1 -0
  63. universal_mcp/applications/elevenlabs/app.py +3235 -0
  64. universal_mcp/applications/exa/README.md +15 -0
  65. universal_mcp/applications/exa/__init__.py +1 -0
  66. universal_mcp/applications/exa/app.py +221 -0
  67. universal_mcp/applications/falai/README.md +17 -0
  68. universal_mcp/applications/falai/__init__.py +1 -0
  69. universal_mcp/applications/falai/app.py +331 -0
  70. universal_mcp/applications/figma/README.md +49 -0
  71. universal_mcp/applications/figma/__init__.py +1 -0
  72. universal_mcp/applications/figma/app.py +1090 -0
  73. universal_mcp/applications/firecrawl/README.md +20 -0
  74. universal_mcp/applications/firecrawl/__init__.py +1 -0
  75. universal_mcp/applications/firecrawl/app.py +514 -0
  76. universal_mcp/applications/fireflies/README.md +25 -0
  77. universal_mcp/applications/fireflies/__init__.py +1 -0
  78. universal_mcp/applications/fireflies/app.py +506 -0
  79. universal_mcp/applications/fpl/README.md +23 -0
  80. universal_mcp/applications/fpl/__init__.py +1 -0
  81. universal_mcp/applications/fpl/app.py +1327 -0
  82. universal_mcp/applications/fpl/utils/api.py +142 -0
  83. universal_mcp/applications/fpl/utils/fixtures.py +629 -0
  84. universal_mcp/applications/fpl/utils/helper.py +982 -0
  85. universal_mcp/applications/fpl/utils/league_utils.py +546 -0
  86. universal_mcp/applications/fpl/utils/position_utils.py +68 -0
  87. universal_mcp/applications/ghost-content/README.md +25 -0
  88. universal_mcp/applications/ghost-content/__init__.py +1 -0
  89. universal_mcp/applications/ghost-content/app.py +654 -0
  90. universal_mcp/applications/github/README.md +1049 -0
  91. universal_mcp/applications/github/__init__.py +1 -0
  92. universal_mcp/applications/github/app.py +50600 -0
  93. universal_mcp/applications/gong/README.md +63 -0
  94. universal_mcp/applications/gong/__init__.py +1 -0
  95. universal_mcp/applications/gong/app.py +2297 -0
  96. universal_mcp/applications/google-ads/README.md +0 -0
  97. universal_mcp/applications/google-ads/__init__.py +1 -0
  98. universal_mcp/applications/google-ads/app.py +23 -0
  99. universal_mcp/applications/google-calendar/README.md +21 -0
  100. universal_mcp/applications/google-calendar/__init__.py +1 -0
  101. universal_mcp/applications/google-calendar/app.py +574 -0
  102. universal_mcp/applications/google-docs/README.md +25 -0
  103. universal_mcp/applications/google-docs/__init__.py +1 -0
  104. universal_mcp/applications/google-docs/app.py +760 -0
  105. universal_mcp/applications/google-drive/README.md +68 -0
  106. universal_mcp/applications/google-drive/__init__.py +1 -0
  107. universal_mcp/applications/google-drive/app.py +4936 -0
  108. universal_mcp/applications/google-gemini/README.md +25 -0
  109. universal_mcp/applications/google-gemini/__init__.py +1 -0
  110. universal_mcp/applications/google-gemini/app.py +663 -0
  111. universal_mcp/applications/google-mail/README.md +31 -0
  112. universal_mcp/applications/google-mail/__init__.py +1 -0
  113. universal_mcp/applications/google-mail/app.py +1354 -0
  114. universal_mcp/applications/google-searchconsole/README.md +21 -0
  115. universal_mcp/applications/google-searchconsole/__init__.py +1 -0
  116. universal_mcp/applications/google-searchconsole/app.py +320 -0
  117. universal_mcp/applications/google-sheet/README.md +36 -0
  118. universal_mcp/applications/google-sheet/__init__.py +1 -0
  119. universal_mcp/applications/google-sheet/app.py +1941 -0
  120. universal_mcp/applications/hashnode/README.md +20 -0
  121. universal_mcp/applications/hashnode/__init__.py +1 -0
  122. universal_mcp/applications/hashnode/app.py +455 -0
  123. universal_mcp/applications/heygen/README.md +44 -0
  124. universal_mcp/applications/heygen/__init__.py +1 -0
  125. universal_mcp/applications/heygen/app.py +961 -0
  126. universal_mcp/applications/http-tools/README.md +16 -0
  127. universal_mcp/applications/http-tools/__init__.py +1 -0
  128. universal_mcp/applications/http-tools/app.py +153 -0
  129. universal_mcp/applications/hubspot/README.md +239 -0
  130. universal_mcp/applications/hubspot/__init__.py +1 -0
  131. universal_mcp/applications/hubspot/app.py +416 -0
  132. universal_mcp/applications/jira/README.md +600 -0
  133. universal_mcp/applications/jira/__init__.py +1 -0
  134. universal_mcp/applications/jira/app.py +28804 -0
  135. universal_mcp/applications/klaviyo/README.md +313 -0
  136. universal_mcp/applications/klaviyo/__init__.py +1 -0
  137. universal_mcp/applications/klaviyo/app.py +11236 -0
  138. universal_mcp/applications/linkedin/README.md +15 -0
  139. universal_mcp/applications/linkedin/__init__.py +1 -0
  140. universal_mcp/applications/linkedin/app.py +243 -0
  141. universal_mcp/applications/mailchimp/README.md +281 -0
  142. universal_mcp/applications/mailchimp/__init__.py +1 -0
  143. universal_mcp/applications/mailchimp/app.py +10937 -0
  144. universal_mcp/applications/markitdown/README.md +12 -0
  145. universal_mcp/applications/markitdown/__init__.py +1 -0
  146. universal_mcp/applications/markitdown/app.py +63 -0
  147. universal_mcp/applications/miro/README.md +151 -0
  148. universal_mcp/applications/miro/__init__.py +1 -0
  149. universal_mcp/applications/miro/app.py +5429 -0
  150. universal_mcp/applications/ms-teams/README.md +42 -0
  151. universal_mcp/applications/ms-teams/__init__.py +1 -0
  152. universal_mcp/applications/ms-teams/app.py +1823 -0
  153. universal_mcp/applications/neon/README.md +74 -0
  154. universal_mcp/applications/neon/__init__.py +1 -0
  155. universal_mcp/applications/neon/app.py +2018 -0
  156. universal_mcp/applications/notion/README.md +30 -0
  157. universal_mcp/applications/notion/__init__.py +1 -0
  158. universal_mcp/applications/notion/app.py +527 -0
  159. universal_mcp/applications/openai/README.md +22 -0
  160. universal_mcp/applications/openai/__init__.py +1 -0
  161. universal_mcp/applications/openai/app.py +759 -0
  162. universal_mcp/applications/outlook/README.md +20 -0
  163. universal_mcp/applications/outlook/__init__.py +1 -0
  164. universal_mcp/applications/outlook/app.py +444 -0
  165. universal_mcp/applications/perplexity/README.md +12 -0
  166. universal_mcp/applications/perplexity/__init__.py +1 -0
  167. universal_mcp/applications/perplexity/app.py +65 -0
  168. universal_mcp/applications/pipedrive/README.md +284 -0
  169. universal_mcp/applications/pipedrive/__init__.py +1 -0
  170. universal_mcp/applications/pipedrive/app.py +12924 -0
  171. universal_mcp/applications/posthog/README.md +132 -0
  172. universal_mcp/applications/posthog/__init__.py +1 -0
  173. universal_mcp/applications/posthog/app.py +7125 -0
  174. universal_mcp/applications/reddit/README.md +135 -0
  175. universal_mcp/applications/reddit/__init__.py +1 -0
  176. universal_mcp/applications/reddit/app.py +4652 -0
  177. universal_mcp/applications/replicate/README.md +18 -0
  178. universal_mcp/applications/replicate/__init__.py +1 -0
  179. universal_mcp/applications/replicate/app.py +495 -0
  180. universal_mcp/applications/resend/README.md +40 -0
  181. universal_mcp/applications/resend/__init__.py +1 -0
  182. universal_mcp/applications/resend/app.py +881 -0
  183. universal_mcp/applications/retell/README.md +21 -0
  184. universal_mcp/applications/retell/__init__.py +1 -0
  185. universal_mcp/applications/retell/app.py +333 -0
  186. universal_mcp/applications/rocketlane/README.md +70 -0
  187. universal_mcp/applications/rocketlane/__init__.py +1 -0
  188. universal_mcp/applications/rocketlane/app.py +4346 -0
  189. universal_mcp/applications/semanticscholar/README.md +25 -0
  190. universal_mcp/applications/semanticscholar/__init__.py +1 -0
  191. universal_mcp/applications/semanticscholar/app.py +482 -0
  192. universal_mcp/applications/semrush/README.md +44 -0
  193. universal_mcp/applications/semrush/__init__.py +1 -0
  194. universal_mcp/applications/semrush/app.py +2081 -0
  195. universal_mcp/applications/sendgrid/README.md +362 -0
  196. universal_mcp/applications/sendgrid/__init__.py +1 -0
  197. universal_mcp/applications/sendgrid/app.py +9752 -0
  198. universal_mcp/applications/sentry/README.md +186 -0
  199. universal_mcp/applications/sentry/__init__.py +1 -0
  200. universal_mcp/applications/sentry/app.py +7471 -0
  201. universal_mcp/applications/serpapi/README.md +14 -0
  202. universal_mcp/applications/serpapi/__init__.py +1 -0
  203. universal_mcp/applications/serpapi/app.py +293 -0
  204. universal_mcp/applications/sharepoint/README.md +0 -0
  205. universal_mcp/applications/sharepoint/__init__.py +1 -0
  206. universal_mcp/applications/sharepoint/app.py +215 -0
  207. universal_mcp/applications/shopify/README.md +321 -0
  208. universal_mcp/applications/shopify/__init__.py +1 -0
  209. universal_mcp/applications/shopify/app.py +15392 -0
  210. universal_mcp/applications/shortcut/README.md +128 -0
  211. universal_mcp/applications/shortcut/__init__.py +1 -0
  212. universal_mcp/applications/shortcut/app.py +4478 -0
  213. universal_mcp/applications/slack/README.md +0 -0
  214. universal_mcp/applications/slack/__init__.py +1 -0
  215. universal_mcp/applications/slack/app.py +570 -0
  216. universal_mcp/applications/spotify/README.md +91 -0
  217. universal_mcp/applications/spotify/__init__.py +1 -0
  218. universal_mcp/applications/spotify/app.py +2526 -0
  219. universal_mcp/applications/supabase/README.md +87 -0
  220. universal_mcp/applications/supabase/__init__.py +1 -0
  221. universal_mcp/applications/supabase/app.py +2970 -0
  222. universal_mcp/applications/tavily/README.md +12 -0
  223. universal_mcp/applications/tavily/__init__.py +1 -0
  224. universal_mcp/applications/tavily/app.py +51 -0
  225. universal_mcp/applications/trello/README.md +266 -0
  226. universal_mcp/applications/trello/__init__.py +1 -0
  227. universal_mcp/applications/trello/app.py +10875 -0
  228. universal_mcp/applications/twillo/README.md +0 -0
  229. universal_mcp/applications/twillo/__init__.py +1 -0
  230. universal_mcp/applications/twillo/app.py +269 -0
  231. universal_mcp/applications/twitter/README.md +100 -0
  232. universal_mcp/applications/twitter/__init__.py +1 -0
  233. universal_mcp/applications/twitter/api_segments/__init__.py +0 -0
  234. universal_mcp/applications/twitter/api_segments/api_segment_base.py +51 -0
  235. universal_mcp/applications/twitter/api_segments/compliance_api.py +122 -0
  236. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +255 -0
  237. universal_mcp/applications/twitter/api_segments/dm_events_api.py +140 -0
  238. universal_mcp/applications/twitter/api_segments/likes_api.py +159 -0
  239. universal_mcp/applications/twitter/api_segments/lists_api.py +395 -0
  240. universal_mcp/applications/twitter/api_segments/openapi_json_api.py +34 -0
  241. universal_mcp/applications/twitter/api_segments/spaces_api.py +309 -0
  242. universal_mcp/applications/twitter/api_segments/trends_api.py +40 -0
  243. universal_mcp/applications/twitter/api_segments/tweets_api.py +1403 -0
  244. universal_mcp/applications/twitter/api_segments/usage_api.py +40 -0
  245. universal_mcp/applications/twitter/api_segments/users_api.py +1498 -0
  246. universal_mcp/applications/twitter/app.py +46 -0
  247. universal_mcp/applications/unipile/README.md +28 -0
  248. universal_mcp/applications/unipile/__init__.py +1 -0
  249. universal_mcp/applications/unipile/app.py +829 -0
  250. universal_mcp/applications/whatsapp/README.md +23 -0
  251. universal_mcp/applications/whatsapp/__init__.py +1 -0
  252. universal_mcp/applications/whatsapp/app.py +595 -0
  253. universal_mcp/applications/whatsapp-business/README.md +34 -0
  254. universal_mcp/applications/whatsapp-business/__init__.py +1 -0
  255. universal_mcp/applications/whatsapp-business/app.py +1065 -0
  256. universal_mcp/applications/wrike/README.md +46 -0
  257. universal_mcp/applications/wrike/__init__.py +1 -0
  258. universal_mcp/applications/wrike/app.py +1583 -0
  259. universal_mcp/applications/youtube/README.md +57 -0
  260. universal_mcp/applications/youtube/__init__.py +1 -0
  261. universal_mcp/applications/youtube/app.py +1696 -0
  262. universal_mcp/applications/zenquotes/README.md +12 -0
  263. universal_mcp/applications/zenquotes/__init__.py +1 -0
  264. universal_mcp/applications/zenquotes/app.py +31 -0
  265. universal_mcp_applications-0.1.1.dist-info/METADATA +172 -0
  266. universal_mcp_applications-0.1.1.dist-info/RECORD +268 -0
  267. universal_mcp_applications-0.1.1.dist-info/WHEEL +4 -0
  268. universal_mcp_applications-0.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,829 @@
1
+ import json
2
+ from collections.abc import Callable
3
+ from typing import Any, Literal
4
+
5
+ from loguru import logger
6
+ from universal_mcp.applications.application import APIApplication
7
+ from universal_mcp.integrations import Integration
8
+
9
+
10
+ class UnipileApp(APIApplication):
11
+ """
12
+ Application for interacting with the LinkedIn API via Unipile.
13
+ Handles operations related to chats, messages, accounts, posts, and user profiles.
14
+ """
15
+
16
+ def __init__(self, integration: Integration) -> None:
17
+ """
18
+ Initialize the LinkedinApp.
19
+
20
+ Args:
21
+ integration: The integration configuration containing credentials and other settings.
22
+ It is expected that the integration provides the 'x-api-key'
23
+ via headers in `integration.get_credentials()`, e.g.,
24
+ `{"headers": {"x-api-key": "YOUR_API_KEY"}}`.
25
+ """
26
+ super().__init__(name="unipile", integration=integration)
27
+
28
+ self._base_url = None
29
+
30
+ @property
31
+ def base_url(self) -> str:
32
+ """
33
+ Get the base URL for the Unipile API.
34
+ This is constructed from the integration's credentials.
35
+ """
36
+ if not self._base_url:
37
+ credentials = self.integration.get_credentials()
38
+ subdomain = credentials.get("subdomain")
39
+ port = credentials.get("port")
40
+ if not subdomain or not port:
41
+ logger.error(
42
+ "UnipileApp: Missing 'subdomain' or 'port' in integration credentials."
43
+ )
44
+ raise ValueError(
45
+ "Integration credentials must include 'subdomain' and 'port'."
46
+ )
47
+ self._base_url = f"https://{subdomain}.unipile.com:{port}"
48
+ return self._base_url
49
+
50
+ @base_url.setter
51
+ def base_url(self, base_url: str) -> None:
52
+ """
53
+ Set the base URL for the Unipile API.
54
+ This is useful for testing or if the base URL changes.
55
+
56
+ Args:
57
+ base_url: The new base URL to set.
58
+ """
59
+ self._base_url = base_url
60
+ logger.info(f"UnipileApp: Base URL set to {self._base_url}")
61
+
62
+ def _get_headers(self) -> dict[str, str]:
63
+ """
64
+ Get the headers for Unipile API requests.
65
+ Overrides the base class method to use X-Api-Key.
66
+ """
67
+ if not self.integration:
68
+ logger.warning(
69
+ "UnipileApp: No integration configured, returning empty headers."
70
+ )
71
+ return {}
72
+
73
+ credentials = self.integration.get_credentials()
74
+
75
+ api_key = (
76
+ credentials.get("api_key")
77
+ or credentials.get("API_KEY")
78
+ or credentials.get("apiKey")
79
+ )
80
+
81
+ if not api_key:
82
+ logger.error(
83
+ "UnipileApp: API key not found in integration credentials for Unipile."
84
+ )
85
+ return { # Or return minimal headers if some calls might not need auth (unlikely for Unipile)
86
+ "Content-Type": "application/json",
87
+ "Cache-Control": "no-cache",
88
+ }
89
+
90
+ logger.debug("UnipileApp: Using X-Api-Key for authentication.")
91
+ return {
92
+ "x-api-key": api_key,
93
+ "Content-Type": "application/json",
94
+ "Cache-Control": "no-cache", # Often good practice for APIs
95
+ }
96
+
97
+ def list_all_chats(
98
+ self,
99
+ unread: bool | None = None,
100
+ cursor: str | None = None,
101
+ before: str | None = None, # ISO 8601 UTC datetime
102
+ after: str | None = None, # ISO 8601 UTC datetime
103
+ limit: int | None = None, # 1-250
104
+ account_type: str | None = None,
105
+ account_id: str | None = None, # Comma-separated list of ids
106
+ ) -> dict[str, Any]:
107
+ """
108
+ Lists all chats, with options to filter by unread status, pagination, date ranges, and account.
109
+
110
+ Args:
111
+ unread: Filter for unread chats only or read chats only.
112
+ cursor: Pagination cursor for the next page of entries.
113
+ before: Filter for items created before this ISO 8601 UTC datetime (exclusive).
114
+ after: Filter for items created after this ISO 8601 UTC datetime (exclusive).
115
+ limit: Number of items to return (1-250).
116
+ account_type: Filter by provider (e.g., "linkedin").
117
+ account_id: Filter by specific account IDs (comma-separated).
118
+
119
+ Returns:
120
+ A dictionary containing a list of chat objects and a pagination cursor.
121
+
122
+ Raises:
123
+ httpx.HTTPError: If the API request fails.
124
+
125
+ Tags:
126
+ linkedin, chat, list, messaging, api
127
+ """
128
+ url = f"{self.base_url}/api/v1/chats"
129
+ params: dict[str, Any] = {}
130
+ if unread is not None:
131
+ params["unread"] = unread
132
+ if cursor:
133
+ params["cursor"] = cursor
134
+ if before:
135
+ params["before"] = before
136
+ if after:
137
+ params["after"] = after
138
+ if limit:
139
+ params["limit"] = limit
140
+ if account_type:
141
+ params["account_type"] = account_type
142
+ if account_id:
143
+ params["account_id"] = account_id
144
+
145
+ response = self._get(url, params=params)
146
+ return response.json()
147
+
148
+ def list_chat_messages(
149
+ self,
150
+ chat_id: str,
151
+ cursor: str | None = None,
152
+ before: str | None = None, # ISO 8601 UTC datetime
153
+ after: str | None = None, # ISO 8601 UTC datetime
154
+ limit: int | None = None, # 1-250
155
+ sender_id: str | None = None,
156
+ ) -> dict[str, Any]:
157
+ """
158
+ Lists all messages from a specific chat, with pagination and filtering options.
159
+
160
+ Args:
161
+ chat_id: The ID of the chat to retrieve messages from.
162
+ cursor: Pagination cursor for the next page of entries.
163
+ before: Filter for items created before this ISO 8601 UTC datetime (exclusive).
164
+ after: Filter for items created after this ISO 8601 UTC datetime (exclusive).
165
+ limit: Number of items to return (1-250).
166
+ sender_id: Filter messages from a specific sender ID.
167
+
168
+ Returns:
169
+ A dictionary containing a list of message objects and a pagination cursor.
170
+
171
+ Raises:
172
+ httpx.HTTPError: If the API request fails.
173
+
174
+ Tags:
175
+ linkedin, chat, message, list, messaging, api
176
+ """
177
+ url = f"{self.base_url}/api/v1/chats/{chat_id}/messages"
178
+ params: dict[str, Any] = {}
179
+ if cursor:
180
+ params["cursor"] = cursor
181
+ if before:
182
+ params["before"] = before
183
+ if after:
184
+ params["after"] = after
185
+ if limit:
186
+ params["limit"] = limit
187
+ if sender_id:
188
+ params["sender_id"] = sender_id
189
+
190
+ response = self._get(url, params=params)
191
+ return response.json()
192
+
193
+ def send_chat_message(
194
+ self,
195
+ chat_id: str,
196
+ text: str,
197
+ ) -> dict[str, Any]:
198
+ """
199
+ Sends a message in a specific chat.
200
+
201
+ Args:
202
+ chat_id: The ID of the chat where the message will be sent.
203
+ text: The text content of the message.
204
+ attachments: Optional list of attachment objects to include with the message.
205
+
206
+ Returns:
207
+ A dictionary containing the ID of the sent message.
208
+
209
+ Raises:
210
+ httpx.HTTPError: If the API request fails.
211
+
212
+ Tags:
213
+ linkedin, chat, message, send, create, messaging, api
214
+ """
215
+ url = f"{self.base_url}/api/v1/chats/{chat_id}/messages"
216
+ payload: dict[str, Any] = {"text": text}
217
+
218
+ response = self._post(url, data=payload)
219
+ return response.json()
220
+
221
+ def retrieve_chat(
222
+ self, chat_id: str, account_id: str | None = None
223
+ ) -> dict[str, Any]:
224
+ """
225
+ Retrieves a specific chat by its Unipile or provider ID.
226
+
227
+ Args:
228
+ chat_id: The Unipile or provider ID of the chat.
229
+ account_id: Mandatory if the chat_id is a provider ID. Specifies the account context.
230
+
231
+ Returns:
232
+ A dictionary containing the chat object details.
233
+
234
+ Raises:
235
+ httpx.HTTPError: If the API request fails.
236
+
237
+ Tags:
238
+ linkedin, chat, retrieve, get, messaging, api
239
+ """
240
+ url = f"{self.base_url}/api/v1/chats/{chat_id}"
241
+ params: dict[str, Any] = {}
242
+ if account_id:
243
+ params["account_id"] = account_id
244
+
245
+ response = self._get(url, params=params)
246
+ return response.json()
247
+
248
+ def list_all_messages(
249
+ self,
250
+ cursor: str | None = None,
251
+ before: str | None = None, # ISO 8601 UTC datetime
252
+ after: str | None = None, # ISO 8601 UTC datetime
253
+ limit: int | None = None, # 1-250
254
+ sender_id: str | None = None,
255
+ account_id: str | None = None,
256
+ ) -> dict[str, Any]:
257
+ """
258
+ Lists all messages across all chats, with pagination and filtering options.
259
+
260
+ Args:
261
+ cursor: Pagination cursor.
262
+ before: Filter for items created before this ISO 8601 UTC datetime.
263
+ after: Filter for items created after this ISO 8601 UTC datetime.
264
+ limit: Number of items to return (1-250).
265
+ sender_id: Filter messages from a specific sender.
266
+ account_id: Filter messages from a specific linked account.
267
+
268
+ Returns:
269
+ A dictionary containing a list of message objects and a pagination cursor.
270
+
271
+ Raises:
272
+ httpx.HTTPError: If the API request fails.
273
+
274
+ Tags:
275
+ linkedin, message, list, all_messages, messaging, api
276
+ """
277
+ url = f"{self.base_url}/api/v1/messages"
278
+ params: dict[str, Any] = {}
279
+ if cursor:
280
+ params["cursor"] = cursor
281
+ if before:
282
+ params["before"] = before
283
+ if after:
284
+ params["after"] = after
285
+ if limit:
286
+ params["limit"] = limit
287
+ if sender_id:
288
+ params["sender_id"] = sender_id
289
+ if account_id:
290
+ params["account_id"] = account_id
291
+
292
+ response = self._get(url, params=params)
293
+ return response.json()
294
+
295
+ def list_all_accounts(
296
+ self,
297
+ cursor: str | None = None,
298
+ limit: int | None = None, # 1-259 according to spec
299
+ ) -> dict[str, Any]:
300
+ """
301
+ Lists all linked accounts.
302
+
303
+ Args:
304
+ cursor: Pagination cursor.
305
+ limit: Number of items to return (1-259).
306
+
307
+ Returns:
308
+ A dictionary containing a list of account objects and a pagination cursor.
309
+
310
+ Raises:
311
+ httpx.HTTPError: If the API request fails.
312
+
313
+ Tags:
314
+ linkedin, account, list, unipile, api, important
315
+ """
316
+ url = f"{self.base_url}/api/v1/accounts"
317
+ params: dict[str, Any] = {}
318
+ if cursor:
319
+ params["cursor"] = cursor
320
+ if limit:
321
+ params["limit"] = limit
322
+
323
+ response = self._get(url, params=params)
324
+ return response.json()
325
+
326
+ def retrieve_account(
327
+ self,
328
+ account_id: str,
329
+ ) -> dict[str, Any]:
330
+ """
331
+ Retrieves a specific linked account by its ID.
332
+
333
+ Args:
334
+ account_id: The ID of the account to retrieve.
335
+
336
+ Returns:
337
+ A dictionary containing the account object details.
338
+
339
+ Raises:
340
+ httpx.HTTPError: If the API request fails.
341
+
342
+ Tags:
343
+ linkedin, account, retrieve, get, unipile, api, important
344
+ """
345
+ url = f"{self.base_url}/api/v1/accounts/{account_id}"
346
+ response = self._get(url)
347
+ return response.json()
348
+
349
+ def list_user_posts(
350
+ self,
351
+ identifier: str, # User or Company provider internal ID
352
+ account_id: str, # Account to perform the request from (REQUIRED)
353
+ cursor: str | None = None,
354
+ limit: int | None = None, # 1-100 (spec says max 250)
355
+ is_company: bool | None = None,
356
+ ) -> dict[str, Any]:
357
+ """
358
+ Lists all posts for a given user or company identifier.
359
+
360
+ Args:
361
+ identifier: The entity's provider internal ID (LinkedIn ID).
362
+ account_id: The ID of the Unipile account to perform the request from (REQUIRED).
363
+ cursor: Pagination cursor.
364
+ limit: Number of items to return (1-100, as per Unipile example, though spec allows up to 250).
365
+ is_company: Boolean indicating if the identifier is for a company.
366
+
367
+ Returns:
368
+ A dictionary containing a list of post objects and pagination details.
369
+
370
+ Raises:
371
+ httpx.HTTPError: If the API request fails.
372
+
373
+ Tags:
374
+ linkedin, post, list, user_posts, company_posts, content, api, important
375
+ """
376
+ url = f"{self.base_url}/api/v1/users/{identifier}/posts"
377
+ params: dict[str, Any] = {"account_id": account_id}
378
+ if cursor:
379
+ params["cursor"] = cursor
380
+ if limit:
381
+ params["limit"] = limit
382
+ if is_company is not None:
383
+ params["is_company"] = is_company
384
+
385
+ response = self._get(url, params=params)
386
+ return response.json()
387
+
388
+ def retrieve_own_profile(
389
+ self,
390
+ account_id: str,
391
+ ) -> dict[str, Any]:
392
+ """
393
+ Retrieves the profile of the user associated with the given Unipile account_id.
394
+
395
+ Args:
396
+ account_id: The ID of the Unipile account to use for retrieving the profile (REQUIRED).
397
+
398
+ Returns:
399
+ A dictionary containing the user's profile details.
400
+
401
+ Raises:
402
+ httpx.HTTPError: If the API request fails.
403
+
404
+ Tags:
405
+ linkedin, user, profile, me, retrieve, get, api
406
+ """
407
+ url = f"{self.base_url}/api/v1/users/me"
408
+ params: dict[str, Any] = {"account_id": account_id}
409
+ response = self._get(url, params=params)
410
+ return response.json()
411
+
412
+ def retrieve_post(
413
+ self,
414
+ post_id: str,
415
+ account_id: str,
416
+ ) -> dict[str, Any]:
417
+ """
418
+ Retrieves a specific post by its ID.
419
+
420
+ Args:
421
+ post_id: The ID of the post to retrieve.
422
+ account_id: The ID of the Unipile account to perform the request from (REQUIRED).
423
+
424
+ Returns:
425
+ A dictionary containing the post details.
426
+
427
+ Raises:
428
+ httpx.HTTPError: If the API request fails.
429
+
430
+ Tags:
431
+ linkedin, post, retrieve, get, content, api, important
432
+ """
433
+ url = f"{self.base_url}/api/v1/posts/{post_id}"
434
+ params: dict[str, Any] = {"account_id": account_id}
435
+ response = self._get(url, params=params)
436
+ return response.json()
437
+
438
+ def list_post_comments(
439
+ self,
440
+ post_id: str,
441
+ account_id: str,
442
+ comment_id: str | None = None,
443
+ cursor: str | None = None,
444
+ limit: int | None = None,
445
+ ) -> dict[str, Any]:
446
+ """
447
+ Lists all comments from a specific post. Can also list replies to a specific comment.
448
+
449
+ Args:
450
+ post_id: The social ID of the post.
451
+ account_id: The ID of the Unipile account to perform the request from (REQUIRED).
452
+ comment_id: If provided, retrieves replies to this comment ID instead of top-level comments.
453
+ cursor: Pagination cursor.
454
+ limit: Number of comments to return. (OpenAPI spec shows type string, passed as string if provided).
455
+
456
+ Returns:
457
+ A dictionary containing a list of comment objects and pagination details.
458
+
459
+ Raises:
460
+ httpx.HTTPError: If the API request fails.
461
+
462
+ Tags:
463
+ linkedin, post, comment, list, content, api, important
464
+ """
465
+ url = f"{self.base_url}/api/v1/posts/{post_id}/comments"
466
+ params: dict[str, Any] = {"account_id": account_id}
467
+ if cursor:
468
+ params["cursor"] = cursor
469
+ if limit is not None:
470
+ params["limit"] = str(limit)
471
+ if comment_id:
472
+ params["comment_id"] = comment_id
473
+
474
+ response = self._get(url, params=params)
475
+ return response.json()
476
+
477
+ def create_post(
478
+ self,
479
+ account_id: str,
480
+ text: str,
481
+ mentions: list[dict[str, Any]] | None = None,
482
+ external_link: str | None = None,
483
+ ) -> dict[str, Any]:
484
+ """
485
+ Creates a new post on LinkedIn.
486
+
487
+ Args:
488
+ account_id: The ID of the Unipile account that will author the post (added as query parameter).
489
+ text: The main text content of the post.
490
+ mentions: Optional list of dictionaries, each representing a mention.
491
+ Example: `[{"entity_urn": "urn:li:person:...", "start_index": 0, "end_index": 5}]`
492
+ external_link: Optional string, an external URL that should be displayed within a card.
493
+
494
+ Returns:
495
+ A dictionary containing the ID of the created post.
496
+
497
+ Raises:
498
+ httpx.HTTPError: If the API request fails.
499
+
500
+ Tags:
501
+ linkedin, post, create, share, content, api, important
502
+ """
503
+ url = f"{self.base_url}/api/v1/posts"
504
+
505
+ params: dict[str, str] = {
506
+ "account_id": account_id,
507
+ "text": text,
508
+ }
509
+
510
+ if mentions:
511
+ params["mentions"] = mentions
512
+ if external_link:
513
+ params["external_link"] = external_link
514
+
515
+ response = self._post(url, data=params)
516
+ return response.json()
517
+
518
+ def list_post_reactions(
519
+ self,
520
+ post_id: str,
521
+ account_id: str,
522
+ comment_id: str | None = None,
523
+ cursor: str | None = None,
524
+ limit: int | None = None,
525
+ ) -> dict[str, Any]:
526
+ """
527
+ Lists all reactions from a specific post or comment.
528
+
529
+ Args:
530
+ post_id: The social ID of the post.
531
+ account_id: The ID of the Unipile account to perform the request from .
532
+ comment_id: If provided, retrieves reactions for this comment ID.
533
+ cursor: Pagination cursor.
534
+ limit: Number of reactions to return (1-100, spec max 250).
535
+
536
+ Returns:
537
+ A dictionary containing a list of reaction objects and pagination details.
538
+
539
+ Raises:
540
+ httpx.HTTPError: If the API request fails.
541
+
542
+ Tags:
543
+ linkedin, post, reaction, list, like, content, api
544
+ """
545
+ url = f"{self.base_url}/api/v1/posts/{post_id}/reactions"
546
+ params: dict[str, Any] = {"account_id": account_id}
547
+ if cursor:
548
+ params["cursor"] = cursor
549
+ if limit:
550
+ params["limit"] = limit
551
+ if comment_id:
552
+ params["comment_id"] = comment_id
553
+
554
+ response = self._get(url, params=params)
555
+ return response.json()
556
+
557
+ def create_post_comment(
558
+ self,
559
+ post_social_id: str,
560
+ account_id: str,
561
+ text: str,
562
+ comment_id: str | None = None, # If provided, replies to a specific comment
563
+ mentions_body: list[dict[str, Any]] | None = None,
564
+ ) -> dict[str, Any]:
565
+ """
566
+ Adds a comment to a specific post.
567
+
568
+ Args:
569
+ post_social_id: The social ID of the post to comment on.
570
+ account_id: The ID of the Unipile account performing the comment.
571
+ text: The text content of the comment (passed as a query parameter).
572
+ Supports Unipile's mention syntax like "Hey {{0}}".
573
+ comment_id: Optional ID of a specific comment to reply to instead of commenting on the post.
574
+ mentions_body: Optional list of mention objects for the request body if needed.
575
+
576
+ Returns:
577
+ A dictionary, likely confirming comment creation. (Structure depends on actual API response)
578
+
579
+ Raises:
580
+ httpx.HTTPError: If the API request fails.
581
+
582
+ Tags:
583
+ linkedin, post, comment, create, content, api, important
584
+ """
585
+ url = f"{self.base_url}/api/v1/posts/{post_social_id}/comments"
586
+ params: dict[str, Any] = {
587
+ "account_id": account_id,
588
+ "text": text,
589
+ }
590
+
591
+ if comment_id:
592
+ params["comment_id"] = comment_id
593
+
594
+ if mentions_body:
595
+ params = {"mentions": mentions_body}
596
+
597
+ response = self._post(url, data=params)
598
+
599
+ try:
600
+ return response.json()
601
+ except json.JSONDecodeError:
602
+ return {
603
+ "status": response.status_code,
604
+ "message": "Comment action processed.",
605
+ }
606
+
607
+ def add_reaction_to_post(
608
+ self,
609
+ post_social_id: str,
610
+ reaction_type: Literal[
611
+ "like", "celebrate", "love", "insightful", "funny", "support"
612
+ ],
613
+ account_id: str,
614
+ comment_id: str | None = None,
615
+ ) -> dict[str, Any]:
616
+ """
617
+ Adds a reaction to a post or comment.
618
+ The OpenAPI spec does not detail the request body. This method assumes 'post_social_id'
619
+ (as 'post_id') and 'reaction_type' (as 'value') are in the JSON body.
620
+ 'account_id' is an optional query parameter.
621
+ Verify request/response structure with official Unipile LinkedIn API documentation.
622
+
623
+ Args:
624
+ post_social_id: The social ID of the post or comment to react to.
625
+ reaction_type: The type of reaction .
626
+ account_id: Account ID of the Unipile account performing the reaction.
627
+ comment_id: Optional ID of a specific comment to react to instead of the post.
628
+
629
+ Returns:
630
+ A dictionary, likely confirming the reaction. (Structure depends on actual API response)
631
+
632
+ Raises:
633
+ httpx.HTTPError: If the API request fails.
634
+
635
+ Tags:
636
+ linkedin, post, reaction, create, like, content, api, important
637
+ """
638
+ url = f"{self.base_url}/api/v1/posts/reaction"
639
+
640
+ params: dict[str, str] = {
641
+ "account_id": account_id,
642
+ "post_id": post_social_id,
643
+ "reaction_type": reaction_type,
644
+ }
645
+
646
+ if comment_id:
647
+ params["comment_id"] = comment_id
648
+
649
+ response = self._post(url, data=params)
650
+
651
+ try:
652
+ return response.json()
653
+ except json.JSONDecodeError:
654
+ return {
655
+ "status": response.status_code,
656
+ "message": "Reaction action processed.",
657
+ }
658
+
659
+ def search(
660
+ self,
661
+ account_id: str,
662
+ category: Literal["people", "companies", "posts", "jobs"] = "posts",
663
+ api: Literal["classic", "sales_navigator"] = "classic",
664
+ cursor: str | None = None,
665
+ limit: int | None = None,
666
+ keywords: str | None = None,
667
+ sort_by: Literal["relevance", "date"] | None = None,
668
+ date_posted: Literal["past_day", "past_week", "past_month"] | None = None,
669
+ content_type: Literal["videos", "images", "live_videos", "collaborative_articles", "documents"] | None = None,
670
+ posted_by: dict[str, Any] | None = None,
671
+ mentioning: dict[str, Any] | None = None,
672
+ author: dict[str, Any] | None = None,
673
+ # People search specific parameters
674
+ location: list[str] | None = None,
675
+ industry: list[str] | None = None,
676
+ company: list[str] | None = None,
677
+ past_company: list[str] | None = None,
678
+ school: list[str] | None = None,
679
+ # Company search specific parameters
680
+ headcount: list[dict[str, int]] | None = None,
681
+ # Job search specific parameters
682
+ job_type: list[str] | None = None,
683
+ minimum_salary: dict[str, Any] | None = None,
684
+ # URL search
685
+ search_url: str | None = None,
686
+ ) -> dict[str, Any]:
687
+ """
688
+ Performs a comprehensive search on LinkedIn for people, companies, posts, or jobs.
689
+
690
+ Args:
691
+ account_id: The ID of the Unipile account to perform the search from (REQUIRED).
692
+ category: Type of search to perform - "people", "companies", "posts", or "jobs".
693
+ api: Which LinkedIn API to use - "classic" or "sales_navigator".
694
+ cursor: Pagination cursor for the next page of entries.
695
+ limit: Number of items to return (up to 50 for Classic search).
696
+ keywords: Keywords to search for.
697
+ sort_by: How to sort the results, e.g., "relevance" or "date".
698
+ date_posted: Filter posts by when they were posted (posts only).
699
+ content_type: Filter by the type of content in the post (posts only).
700
+ posted_by: Dictionary to filter by who posted (posts only).
701
+ location: Location filter for people/company search (array of strings).
702
+ industry: Industry filter for people/company search (array of strings).
703
+ company: Company filter for people search (array of strings).
704
+ past_company: Past company filter for people search (array of strings).
705
+ school: School filter for people search (array of strings).
706
+ headcount: Company size filter for company search (array of objects with min/max numbers).
707
+ job_type: Job type filter for job search (array of strings).
708
+ minimum_salary: Minimum salary filter for job search (object with currency and value). Example:
709
+ minimum_salary = {
710
+ "currency": "USD",
711
+ "value": 80
712
+ }
713
+ search_url: Direct LinkedIn search URL to use instead of building parameters.
714
+
715
+ Returns:
716
+ A dictionary containing search results and pagination details.
717
+
718
+ Raises:
719
+ httpx.HTTPError: If the API request fails.
720
+
721
+ Tags:
722
+ linkedin, search, people, companies, posts, jobs, api, important
723
+ """
724
+ url = f"{self.base_url}/api/v1/linkedin/search"
725
+
726
+ params: dict[str, Any] = {"account_id": account_id}
727
+ if cursor:
728
+ params["cursor"] = cursor
729
+ if limit is not None:
730
+ params["limit"] = limit
731
+
732
+ payload: dict[str, Any] = {"api": api, "category": category}
733
+
734
+ # Add search URL if provided (takes precedence over other parameters)
735
+ if search_url:
736
+ payload["search_url"] = search_url
737
+ else:
738
+ # Add common parameters
739
+ common_params = {
740
+ "keywords": keywords,
741
+ "sort_by": sort_by,
742
+ }
743
+ payload.update({k: v for k, v in common_params.items() if v is not None})
744
+
745
+ # Category-specific parameters
746
+ category_params = {
747
+ "posts": {
748
+ "date_posted": date_posted,
749
+ "content_type": content_type,
750
+ "posted_by": posted_by,
751
+ },
752
+ "people": {
753
+ "location": location,
754
+ "industry": industry,
755
+ "company": company,
756
+ "past_company": past_company,
757
+ "school": school,
758
+ },
759
+ "companies": {
760
+ "location": location,
761
+ "industry": industry,
762
+ "headcount": headcount,
763
+ },
764
+ "jobs": {
765
+ "location": location,
766
+ "job_type": job_type,
767
+ "minimum_salary": minimum_salary,
768
+ },
769
+ }
770
+
771
+ if category in category_params:
772
+ payload.update(
773
+ {
774
+ k: v
775
+ for k, v in category_params[category].items()
776
+ if v is not None
777
+ }
778
+ )
779
+
780
+ response = self._post(url, params=params, data=payload)
781
+ return self._handle_response(response)
782
+
783
+ def retrieve_profile(
784
+ self,
785
+ identifier: str,
786
+ account_id: str,
787
+ ) -> dict[str, Any]:
788
+ """
789
+ Retrieves a specific user profile by its identifier.
790
+
791
+ Args:
792
+ identifier: Can be the provider's internal id OR the provider's public id of the requested user.For example, for https://www.linkedin.com/in/manojbajaj95/, the identifier is "manojbajaj95".
793
+
794
+ account_id: The ID of the Unipile account to perform the request from (REQUIRED).
795
+
796
+ Returns:
797
+ A dictionary containing the user's profile details.
798
+
799
+ Raises:
800
+ httpx.HTTPError: If the API request fails.
801
+
802
+ Tags:
803
+ linkedin, user, profile, retrieve, get, api, important
804
+ """
805
+ url = f"{self.base_url}/api/v1/users/{identifier}"
806
+ params: dict[str, Any] = {"account_id": account_id}
807
+ response = self._get(url, params=params)
808
+ return self._handle_response(response)
809
+
810
+ def list_tools(self) -> list[Callable]:
811
+ return [
812
+ self.list_all_chats,
813
+ self.list_chat_messages,
814
+ self.send_chat_message,
815
+ self.retrieve_chat,
816
+ self.list_all_messages,
817
+ self.list_all_accounts,
818
+ self.retrieve_account,
819
+ self.list_user_posts,
820
+ self.retrieve_own_profile,
821
+ self.retrieve_profile,
822
+ self.retrieve_post,
823
+ self.list_post_comments,
824
+ self.create_post,
825
+ self.list_post_reactions,
826
+ self.create_post_comment,
827
+ self.add_reaction_to_post,
828
+ self.search,
829
+ ]