universal-mcp-applications 0.1.17__py3-none-any.whl → 0.1.33__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.

Potentially problematic release.


This version of universal-mcp-applications might be problematic. Click here for more details.

Files changed (143) hide show
  1. universal_mcp/applications/BEST_PRACTICES.md +166 -0
  2. universal_mcp/applications/ahrefs/README.md +3 -3
  3. universal_mcp/applications/airtable/README.md +3 -3
  4. universal_mcp/applications/airtable/app.py +0 -1
  5. universal_mcp/applications/apollo/app.py +0 -1
  6. universal_mcp/applications/asana/README.md +3 -3
  7. universal_mcp/applications/aws_s3/README.md +29 -0
  8. universal_mcp/applications/aws_s3/app.py +40 -39
  9. universal_mcp/applications/bill/README.md +249 -0
  10. universal_mcp/applications/browser_use/README.md +1 -0
  11. universal_mcp/applications/browser_use/__init__.py +0 -0
  12. universal_mcp/applications/browser_use/app.py +71 -0
  13. universal_mcp/applications/calendly/README.md +45 -45
  14. universal_mcp/applications/calendly/app.py +125 -125
  15. universal_mcp/applications/canva/README.md +35 -35
  16. universal_mcp/applications/canva/app.py +95 -99
  17. universal_mcp/applications/clickup/README.md +4 -4
  18. universal_mcp/applications/confluence/app.py +0 -1
  19. universal_mcp/applications/contentful/README.md +1 -2
  20. universal_mcp/applications/contentful/app.py +4 -5
  21. universal_mcp/applications/crustdata/README.md +3 -3
  22. universal_mcp/applications/domain_checker/README.md +2 -2
  23. universal_mcp/applications/domain_checker/app.py +11 -15
  24. universal_mcp/applications/e2b/README.md +4 -4
  25. universal_mcp/applications/e2b/app.py +4 -4
  26. universal_mcp/applications/elevenlabs/README.md +3 -77
  27. universal_mcp/applications/elevenlabs/app.py +18 -15
  28. universal_mcp/applications/exa/README.md +7 -7
  29. universal_mcp/applications/exa/app.py +17 -17
  30. universal_mcp/applications/falai/README.md +13 -12
  31. universal_mcp/applications/falai/app.py +34 -35
  32. universal_mcp/applications/figma/README.md +3 -3
  33. universal_mcp/applications/file_system/README.md +13 -0
  34. universal_mcp/applications/file_system/app.py +9 -9
  35. universal_mcp/applications/firecrawl/README.md +9 -9
  36. universal_mcp/applications/firecrawl/app.py +46 -46
  37. universal_mcp/applications/fireflies/README.md +14 -14
  38. universal_mcp/applications/fireflies/app.py +164 -57
  39. universal_mcp/applications/fpl/README.md +12 -12
  40. universal_mcp/applications/fpl/app.py +54 -55
  41. universal_mcp/applications/ghost_content/app.py +0 -1
  42. universal_mcp/applications/github/README.md +10 -10
  43. universal_mcp/applications/github/app.py +50 -52
  44. universal_mcp/applications/google_calendar/README.md +10 -10
  45. universal_mcp/applications/google_calendar/app.py +50 -49
  46. universal_mcp/applications/google_docs/README.md +14 -14
  47. universal_mcp/applications/google_docs/app.py +307 -233
  48. universal_mcp/applications/google_drive/README.md +54 -57
  49. universal_mcp/applications/google_drive/app.py +270 -261
  50. universal_mcp/applications/google_gemini/README.md +3 -14
  51. universal_mcp/applications/google_gemini/app.py +15 -18
  52. universal_mcp/applications/google_mail/README.md +20 -20
  53. universal_mcp/applications/google_mail/app.py +110 -109
  54. universal_mcp/applications/google_searchconsole/README.md +10 -10
  55. universal_mcp/applications/google_searchconsole/app.py +37 -37
  56. universal_mcp/applications/google_sheet/README.md +25 -25
  57. universal_mcp/applications/google_sheet/app.py +270 -266
  58. universal_mcp/applications/hashnode/README.md +6 -3
  59. universal_mcp/applications/hashnode/app.py +174 -25
  60. universal_mcp/applications/http_tools/README.md +5 -5
  61. universal_mcp/applications/http_tools/app.py +10 -11
  62. universal_mcp/applications/hubspot/api_segments/__init__.py +0 -0
  63. universal_mcp/applications/hubspot/api_segments/api_segment_base.py +54 -0
  64. universal_mcp/applications/hubspot/api_segments/crm_api.py +7337 -0
  65. universal_mcp/applications/hubspot/api_segments/marketing_api.py +1467 -0
  66. universal_mcp/applications/hubspot/app.py +2 -15
  67. universal_mcp/applications/jira/app.py +0 -1
  68. universal_mcp/applications/klaviyo/README.md +0 -36
  69. universal_mcp/applications/linkedin/README.md +18 -4
  70. universal_mcp/applications/linkedin/app.py +763 -162
  71. universal_mcp/applications/mailchimp/README.md +3 -3
  72. universal_mcp/applications/markitdown/app.py +10 -5
  73. universal_mcp/applications/ms_teams/README.md +31 -31
  74. universal_mcp/applications/ms_teams/app.py +151 -151
  75. universal_mcp/applications/neon/README.md +3 -3
  76. universal_mcp/applications/onedrive/README.md +24 -0
  77. universal_mcp/applications/onedrive/__init__.py +1 -0
  78. universal_mcp/applications/onedrive/app.py +338 -0
  79. universal_mcp/applications/openai/README.md +18 -17
  80. universal_mcp/applications/openai/app.py +40 -39
  81. universal_mcp/applications/outlook/README.md +9 -9
  82. universal_mcp/applications/outlook/app.py +307 -225
  83. universal_mcp/applications/perplexity/README.md +4 -4
  84. universal_mcp/applications/perplexity/app.py +4 -4
  85. universal_mcp/applications/posthog/README.md +128 -127
  86. universal_mcp/applications/reddit/README.md +21 -124
  87. universal_mcp/applications/reddit/app.py +51 -68
  88. universal_mcp/applications/resend/README.md +29 -29
  89. universal_mcp/applications/resend/app.py +116 -117
  90. universal_mcp/applications/rocketlane/app.py +0 -1
  91. universal_mcp/applications/scraper/README.md +7 -4
  92. universal_mcp/applications/scraper/__init__.py +1 -1
  93. universal_mcp/applications/scraper/app.py +341 -103
  94. universal_mcp/applications/semrush/README.md +3 -0
  95. universal_mcp/applications/serpapi/README.md +3 -3
  96. universal_mcp/applications/serpapi/app.py +14 -14
  97. universal_mcp/applications/sharepoint/README.md +19 -0
  98. universal_mcp/applications/sharepoint/app.py +285 -173
  99. universal_mcp/applications/shopify/app.py +0 -1
  100. universal_mcp/applications/shortcut/README.md +3 -3
  101. universal_mcp/applications/slack/README.md +23 -0
  102. universal_mcp/applications/slack/app.py +79 -48
  103. universal_mcp/applications/spotify/README.md +3 -3
  104. universal_mcp/applications/supabase/README.md +3 -3
  105. universal_mcp/applications/tavily/README.md +4 -4
  106. universal_mcp/applications/tavily/app.py +4 -4
  107. universal_mcp/applications/twilio/README.md +15 -0
  108. universal_mcp/applications/twitter/README.md +92 -89
  109. universal_mcp/applications/twitter/api_segments/compliance_api.py +13 -15
  110. universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +20 -20
  111. universal_mcp/applications/twitter/api_segments/dm_events_api.py +12 -12
  112. universal_mcp/applications/twitter/api_segments/likes_api.py +12 -12
  113. universal_mcp/applications/twitter/api_segments/lists_api.py +37 -39
  114. universal_mcp/applications/twitter/api_segments/spaces_api.py +24 -24
  115. universal_mcp/applications/twitter/api_segments/trends_api.py +4 -4
  116. universal_mcp/applications/twitter/api_segments/tweets_api.py +105 -105
  117. universal_mcp/applications/twitter/api_segments/usage_api.py +4 -4
  118. universal_mcp/applications/twitter/api_segments/users_api.py +136 -136
  119. universal_mcp/applications/twitter/app.py +15 -11
  120. universal_mcp/applications/whatsapp/README.md +12 -12
  121. universal_mcp/applications/whatsapp/app.py +66 -67
  122. universal_mcp/applications/whatsapp/audio.py +39 -35
  123. universal_mcp/applications/whatsapp/whatsapp.py +176 -154
  124. universal_mcp/applications/whatsapp_business/README.md +23 -23
  125. universal_mcp/applications/whatsapp_business/app.py +92 -92
  126. universal_mcp/applications/yahoo_finance/README.md +17 -0
  127. universal_mcp/applications/yahoo_finance/__init__.py +1 -0
  128. universal_mcp/applications/yahoo_finance/app.py +300 -0
  129. universal_mcp/applications/youtube/README.md +46 -46
  130. universal_mcp/applications/youtube/app.py +208 -195
  131. universal_mcp/applications/zenquotes/README.md +1 -1
  132. universal_mcp/applications/zenquotes/__init__.py +2 -0
  133. universal_mcp/applications/zenquotes/app.py +5 -5
  134. {universal_mcp_applications-0.1.17.dist-info → universal_mcp_applications-0.1.33.dist-info}/METADATA +5 -90
  135. {universal_mcp_applications-0.1.17.dist-info → universal_mcp_applications-0.1.33.dist-info}/RECORD +137 -128
  136. universal_mcp/applications/replicate/README.md +0 -18
  137. universal_mcp/applications/replicate/__init__.py +0 -1
  138. universal_mcp/applications/replicate/app.py +0 -493
  139. universal_mcp/applications/unipile/README.md +0 -28
  140. universal_mcp/applications/unipile/__init__.py +0 -1
  141. universal_mcp/applications/unipile/app.py +0 -827
  142. {universal_mcp_applications-0.1.17.dist-info → universal_mcp_applications-0.1.33.dist-info}/WHEEL +0 -0
  143. {universal_mcp_applications-0.1.17.dist-info → universal_mcp_applications-0.1.33.dist-info}/licenses/LICENSE +0 -0
@@ -1,6 +1,8 @@
1
- from typing import Any
2
- from urllib.parse import quote
1
+ import json
2
+ import os
3
+ from typing import Any, Callable, Literal
3
4
 
5
+ from loguru import logger
4
6
  from universal_mcp.applications.application import APIApplication
5
7
  from universal_mcp.integrations import Integration
6
8
 
@@ -10,229 +12,828 @@ class LinkedinApp(APIApplication):
10
12
  Base class for Universal MCP Applications.
11
13
  """
12
14
 
13
- def __init__(self, integration: Integration | None = None, **kwargs) -> None:
14
- super().__init__(name="linkedin", integration=integration, **kwargs)
15
- self.base_url = "https://api.linkedin.com"
15
+ def __init__(self, integration: Integration) -> None:
16
+ """
17
+ Initialize the LinkedinApp.
18
+
19
+ Args:
20
+ integration: The integration configuration containing credentials and other settings.
21
+ It is expected that the integration provides the 'x-api-key'
22
+ via headers in `integration.get_credentials()`, e.g.,
23
+ `{"headers": {"x-api-key": "YOUR_API_KEY"}}`.
24
+ """
25
+ super().__init__(name="linkedin", integration=integration)
16
26
 
17
- def _get_headers(self):
27
+ self._base_url = None
28
+ self.account_id = None
29
+ if self.integration:
30
+ credentials = self.integration.get_credentials()
31
+ if credentials:
32
+ self.account_id = credentials.get("account_id")
33
+
34
+ @property
35
+ def base_url(self) -> str:
36
+ if not self._base_url:
37
+ unipile_dsn = os.getenv("UNIPILE_DSN")
38
+ if not unipile_dsn:
39
+ logger.error(
40
+ "UnipileApp: UNIPILE_DSN environment variable is not set."
41
+ )
42
+ raise ValueError(
43
+ "UnipileApp: UNIPILE_DSN environment variable is required."
44
+ )
45
+ self._base_url = f"https://{unipile_dsn}"
46
+ return self._base_url
47
+
48
+ @base_url.setter
49
+ def base_url(self, base_url: str) -> None:
50
+ self._base_url = base_url
51
+ logger.info(f"UnipileApp: Base URL set to {self._base_url}")
52
+
53
+ def _get_headers(self) -> dict[str, str]:
54
+ """
55
+ Get the headers for Unipile API requests.
56
+ Overrides the base class method to use X-Api-Key.
57
+ """
18
58
  if not self.integration:
19
- raise ValueError("Integration not found")
20
- credentials = self.integration.get_credentials()
21
- if "headers" in credentials:
22
- return credentials["headers"]
59
+ logger.warning(
60
+ "UnipileApp: No integration configured, returning empty headers."
61
+ )
62
+ return {}
63
+
64
+ api_key = os.getenv("UNIPILE_API_KEY")
65
+ if not api_key:
66
+ logger.error(
67
+ "UnipileApp: API key not found in integration credentials for Unipile."
68
+ )
69
+ return { # Or return minimal headers if some calls might not need auth (unlikely for Unipile)
70
+ "Content-Type": "application/json",
71
+ "Cache-Control": "no-cache",
72
+ }
73
+
74
+ logger.debug("UnipileApp: Using X-Api-Key for authentication.")
23
75
  return {
24
- "Authorization": f"Bearer {credentials['access_token']}",
25
- "X-Restli-Protocol-Version": "2.0.0",
76
+ "x-api-key": api_key,
26
77
  "Content-Type": "application/json",
27
- "LinkedIn-Version": "202507",
78
+ "Cache-Control": "no-cache", # Often good practice for APIs
28
79
  }
29
80
 
30
- def create_post(
81
+ def _get_search_parameter_id(self, param_type: str, keywords: str) -> str:
82
+ """
83
+ Retrieves the ID for a given LinkedIn search parameter by its name.
84
+
85
+ Args:
86
+ param_type: The type of parameter to search for (e.g., "LOCATION", "COMPANY").
87
+ keywords: The name of the parameter to find (e.g., "United States").
88
+
89
+ Returns:
90
+ The corresponding ID for the search parameter.
91
+
92
+ Raises:
93
+ ValueError: If no exact match for the keywords is found.
94
+ httpx.HTTPError: If the API request fails.
95
+ """
96
+ url = f"{self.base_url}/api/v1/linkedin/search/parameters"
97
+ params = {
98
+ "account_id": self.account_id,
99
+ "keywords": keywords,
100
+ "type": param_type,
101
+ }
102
+
103
+ response = self._get(url, params=params)
104
+ results = self._handle_response(response)
105
+
106
+ items = results.get("items", [])
107
+ if items:
108
+ # Return the ID of the first result, assuming it's the most relevant
109
+ return items[0]["id"]
110
+
111
+ raise ValueError(f'Could not find a matching ID for {param_type}: "{keywords}"')
112
+
113
+ def list_all_chats(
31
114
  self,
32
- commentary: str,
33
- author: str,
34
- visibility: str = "PUBLIC",
35
- distribution: dict[str, Any] | None = None,
36
- lifecycle_state: str = "PUBLISHED",
37
- is_reshare_disabled: bool = False,
38
- ) -> dict[str, str]:
115
+ unread: bool | None = None,
116
+ cursor: str | None = None,
117
+ before: str | None = None, # ISO 8601 UTC datetime
118
+ after: str | None = None, # ISO 8601 UTC datetime
119
+ limit: int | None = None, # 1-250
120
+ account_type: str | None = None,
121
+ ) -> dict[str, Any]:
39
122
  """
40
- Publishes a new text post to a specified LinkedIn author's feed (person or organization). It requires commentary and allows configuring visibility, distribution, and lifecycle state. Upon success, it returns the unique URN and a direct URL for the newly created post, distinguishing it from update/delete operations.
41
-
123
+ Retrieves a paginated list of all chat conversations across linked accounts. Supports filtering by unread status, date range, and account provider, distinguishing it from functions listing messages within a single chat.
124
+
42
125
  Args:
43
- commentary (str): The user generated commentary for the post. Supports mentions using format "@[Entity Name](urn:li:organization:123456)" and hashtags using "#keyword". Text linking to annotated entities must match the name exactly (case sensitive). For member mentions, partial name matching is supported.
44
- author (str): The URN of the author creating the post. Use "urn:li:person:{id}" for individual posts or "urn:li:organization:{id}" for company page posts. Example: "urn:li:person:wGgGaX_xbB" or "urn:li:organization:2414183"
45
- visibility (str): Controls who can view the post. Use "PUBLIC" for posts viewable by anyone on LinkedIn or "CONNECTIONS" for posts viewable by 1st-degree connections only. Defaults to "PUBLIC".
46
- distribution (dict[str, Any], optional): Distribution settings for the post. If not provided, defaults to {"feedDistribution": "MAIN_FEED", "targetEntities": [], "thirdPartyDistributionChannels": []}. feedDistribution controls where the post appears in feeds, targetEntities specifies entities to target, and thirdPartyDistributionChannels defines external distribution channels.
47
- lifecycle_state (str): The state of the post. Use "PUBLISHED" for live posts accessible to all entities, "DRAFT" for posts accessible only to author, "PUBLISH_REQUESTED" for posts submitted but processing, or "PUBLISH_FAILED" for posts that failed to publish. Defaults to "PUBLISHED".
48
- is_reshare_disabled (bool): Whether resharing is disabled by the author. Set to True to prevent other users from resharing this post, or False to allow resharing. Defaults to False.
49
-
126
+ unread: Filter for unread chats only or read chats only.
127
+ cursor: Pagination cursor for the next page of entries.
128
+ before: Filter for items created before this ISO 8601 UTC datetime (exclusive).
129
+ after: Filter for items created after this ISO 8601 UTC datetime (exclusive).
130
+ limit: Number of items to return (1-250).
131
+ account_type: Filter by provider (e.g., "linkedin").
132
+
50
133
  Returns:
51
- dict[str, str]: Dictionary containing the post ID with key "post_id". Example: {"post_id": "urn:li:share:6844785523593134080"}
52
-
134
+ A dictionary containing a list of chat objects and a pagination cursor.
135
+
53
136
  Raises:
54
- ValueError: If required parameters (commentary, author) are missing or if x-restli-id header is not found
55
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body
137
+ httpx.HTTPError: If the API request fails.
138
+
139
+ Tags:
140
+ linkedin, chat, list, messaging, api
141
+ """
142
+ url = f"{self.base_url}/api/v1/chats"
143
+ params: dict[str, Any] = {}
56
144
 
57
- Notes:
58
- Requires LinkedIn API permissions: w_member_social (for individual posts) or w_organization_social (for company posts). All requests require headers: X-Restli-Protocol-Version: 2.0.0 and LinkedIn-Version: 202507. Rate limits: 150 requests per day per member, 100,000 requests per day per application. The Posts API replaces the deprecated ugcPosts API.
145
+ params["account_id"] = self.account_id
59
146
 
147
+ if unread is not None:
148
+ params["unread"] = unread
149
+ if cursor:
150
+ params["cursor"] = cursor
151
+ if before:
152
+ params["before"] = before
153
+ if after:
154
+ params["after"] = after
155
+ if limit:
156
+ params["limit"] = limit
157
+ if account_type:
158
+ params["account_type"] = account_type
159
+
160
+
161
+ response = self._get(url, params=params)
162
+ return response.json()
163
+
164
+ def list_chat_messages(
165
+ self,
166
+ chat_id: str,
167
+ cursor: str | None = None,
168
+ before: str | None = None, # ISO 8601 UTC datetime
169
+ after: str | None = None, # ISO 8601 UTC datetime
170
+ limit: int | None = None, # 1-250
171
+ sender_id: str | None = None,
172
+ ) -> dict[str, Any]:
173
+ """
174
+ Retrieves messages from a specific chat identified by `chat_id`. Supports pagination and filtering by date or sender. Unlike `list_all_messages`, which fetches from all chats, this function targets the contents of a single conversation.
175
+
176
+ Args:
177
+ chat_id: The ID of the chat to retrieve messages from.
178
+ cursor: Pagination cursor for the next page of entries.
179
+ before: Filter for items created before this ISO 8601 UTC datetime (exclusive).
180
+ after: Filter for items created after this ISO 8601 UTC datetime (exclusive).
181
+ limit: Number of items to return (1-250).
182
+ sender_id: Filter messages from a specific sender ID.
183
+
184
+ Returns:
185
+ A dictionary containing a list of message objects and a pagination cursor.
186
+
187
+ Raises:
188
+ httpx.HTTPError: If the API request fails.
189
+
60
190
  Tags:
61
- posts, important
62
- """
63
- # Set default distribution if not provided
64
- if distribution is None:
65
- distribution = {
66
- "feedDistribution": "MAIN_FEED",
67
- "targetEntities": [],
68
- "thirdPartyDistributionChannels": [],
69
- }
191
+ linkedin, chat, message, list, messaging, api
192
+ """
193
+ url = f"{self.base_url}/api/v1/chats/{chat_id}/messages"
194
+ params: dict[str, Any] = {}
195
+ if cursor:
196
+ params["cursor"] = cursor
197
+ if before:
198
+ params["before"] = before
199
+ if after:
200
+ params["after"] = after
201
+ if limit:
202
+ params["limit"] = limit
203
+ if sender_id:
204
+ params["sender_id"] = sender_id
205
+
206
+ response = self._get(url, params=params)
207
+ return response.json()
208
+
209
+ def send_chat_message(
210
+ self,
211
+ chat_id: str,
212
+ text: str,
213
+ ) -> dict[str, Any]:
214
+ """
215
+ Sends a text message to a specific chat conversation using its `chat_id`. This function creates a new message via a POST request, distinguishing it from read-only functions like `list_chat_messages`. It returns the API's response, which typically confirms the successful creation of the message.
70
216
 
71
- request_body_data = {
72
- "author": author,
73
- "commentary": commentary,
74
- "visibility": visibility,
75
- "distribution": distribution,
76
- "lifecycleState": lifecycle_state,
77
- "isReshareDisabledByAuthor": is_reshare_disabled,
217
+ Args:
218
+ chat_id: The ID of the chat where the message will be sent.
219
+ text: The text content of the message.
220
+ attachments: Optional list of attachment objects to include with the message.
221
+
222
+ Returns:
223
+ A dictionary containing the ID of the sent message.
224
+
225
+ Raises:
226
+ httpx.HTTPError: If the API request fails.
227
+
228
+ Tags:
229
+ linkedin, chat, message, send, create, messaging, api
230
+ """
231
+ url = f"{self.base_url}/api/v1/chats/{chat_id}/messages"
232
+ payload: dict[str, Any] = {"text": text}
233
+
234
+ response = self._post(url, data=payload)
235
+ return response.json()
236
+
237
+ def retrieve_chat(self, chat_id: str) -> dict[str, Any]:
238
+ """
239
+ Retrieves a single chat's details using its Unipile or provider-specific ID. This function is distinct from `list_all_chats`, which returns a collection, by targeting one specific conversation.
240
+
241
+ Args:
242
+ chat_id: The Unipile or provider ID of the chat.
243
+
244
+ Returns:
245
+ A dictionary containing the chat object details.
246
+
247
+ Raises:
248
+ httpx.HTTPError: If the API request fails.
249
+
250
+ Tags:
251
+ linkedin, chat, retrieve, get, messaging, api
252
+ """
253
+ url = f"{self.base_url}/api/v1/chats/{chat_id}"
254
+ params: dict[str, Any] = {}
255
+ if self.account_id:
256
+ params["account_id"] = self.account_id
257
+
258
+ response = self._get(url, params=params)
259
+ return response.json()
260
+
261
+ def list_all_messages(
262
+ self,
263
+ cursor: str | None = None,
264
+ before: str | None = None, # ISO 8601 UTC datetime
265
+ after: str | None = None, # ISO 8601 UTC datetime
266
+ limit: int | None = None, # 1-250
267
+ sender_id: str | None = None,
268
+ ) -> dict[str, Any]:
269
+ """
270
+ Retrieves a paginated list of messages from all chats associated with the account. Unlike `list_chat_messages` which targets a specific conversation, this function provides a global message view, filterable by sender and date range.
271
+
272
+ Args:
273
+ cursor: Pagination cursor.
274
+ before: Filter for items created before this ISO 8601 UTC datetime.
275
+ after: Filter for items created after this ISO 8601 UTC datetime.
276
+ limit: Number of items to return (1-250).
277
+ sender_id: Filter messages from a specific sender.
278
+
279
+ Returns:
280
+ A dictionary containing a list of message objects and a pagination cursor.
281
+
282
+ Raises:
283
+ httpx.HTTPError: If the API request fails.
284
+
285
+ Tags:
286
+ linkedin, message, list, all_messages, messaging, api
287
+ """
288
+ url = f"{self.base_url}/api/v1/messages"
289
+ params: dict[str, Any] = {}
290
+ if cursor:
291
+ params["cursor"] = cursor
292
+ if before:
293
+ params["before"] = before
294
+ if after:
295
+ params["after"] = after
296
+ if limit:
297
+ params["limit"] = limit
298
+ if sender_id:
299
+ params["sender_id"] = sender_id
300
+ if self.account_id:
301
+ params["account_id"] = self.account_id
302
+
303
+ response = self._get(url, params=params)
304
+ return response.json()
305
+
306
+ def list_profile_posts(
307
+ self,
308
+ identifier: str, # User or Company provider internal ID
309
+ cursor: str | None = None,
310
+ limit: int | None = None, # 1-100 (spec says max 250)
311
+ is_company: bool | None = None,
312
+ ) -> dict[str, Any]:
313
+ """
314
+ Retrieves a paginated list of posts from a specific user or company profile using their provider ID. An authorizing `account_id` is required, and the `is_company` flag must specify the entity type, distinguishing this from `retrieve_post` which fetches a single post by its own ID.
315
+
316
+ Args:
317
+ identifier: The entity's provider internal ID (LinkedIn ID).
318
+ cursor: Pagination cursor.
319
+ limit: Number of items to return (1-100, as per Unipile example, though spec allows up to 250).
320
+ is_company: Boolean indicating if the identifier is for a company.
321
+
322
+ Returns:
323
+ A dictionary containing a list of post objects and pagination details.
324
+
325
+ Raises:
326
+ httpx.HTTPError: If the API request fails.
327
+
328
+ Tags:
329
+ linkedin, post, list, user_posts, company_posts, content, api, important
330
+ """
331
+ url = f"{self.base_url}/api/v1/users/{identifier}/posts"
332
+ params: dict[str, Any] = {"account_id": self.account_id}
333
+ if cursor:
334
+ params["cursor"] = cursor
335
+ if limit:
336
+ params["limit"] = limit
337
+ if is_company is not None:
338
+ params["is_company"] = is_company
339
+
340
+ response = self._get(url, params=params)
341
+ return response.json()
342
+
343
+ def retrieve_own_profile(self) -> dict[str, Any]:
344
+ """
345
+ Retrieves the profile details for the user associated with the Unipile account. This function targets the API's 'me' endpoint to fetch the authenticated user's profile, distinct from `retrieve_user_profile` which fetches profiles of other users by their public identifier.
346
+
347
+ Returns:
348
+ A dictionary containing the user's profile details.
349
+
350
+ Raises:
351
+ httpx.HTTPError: If the API request fails.
352
+
353
+ Tags:
354
+ linkedin, user, profile, me, retrieve, get, api
355
+ """
356
+ url = f"{self.base_url}/api/v1/users/me"
357
+ params: dict[str, Any] = {"account_id": self.account_id}
358
+ response = self._get(url, params=params)
359
+ return response.json()
360
+
361
+ def retrieve_post(self, post_id: str) -> dict[str, Any]:
362
+ """
363
+ Fetches a specific post's details by its unique ID. Unlike `list_profile_posts`, which retrieves a collection of posts from a user or company profile, this function targets one specific post and returns its full object.
364
+
365
+ Args:
366
+ post_id: The ID of the post to retrieve.
367
+
368
+ Returns:
369
+ A dictionary containing the post details.
370
+
371
+ Raises:
372
+ httpx.HTTPError: If the API request fails.
373
+
374
+ Tags:
375
+ linkedin, post, retrieve, get, content, api, important
376
+ """
377
+ url = f"{self.base_url}/api/v1/posts/{post_id}"
378
+ params: dict[str, Any] = {"account_id": self.account_id}
379
+ response = self._get(url, params=params)
380
+ return response.json()
381
+
382
+ def list_post_comments(
383
+ self,
384
+ post_id: str,
385
+ comment_id: str | None = None,
386
+ cursor: str | None = None,
387
+ limit: int | None = None,
388
+ ) -> dict[str, Any]:
389
+ """
390
+ Fetches comments for a specific post. Providing an optional `comment_id` retrieves threaded replies instead of top-level comments. This read-only operation contrasts with `create_post_comment`, which publishes new comments, and `list_content_reactions`, which retrieves 'likes'.
391
+
392
+ Args:
393
+ post_id: The social ID of the post.
394
+ comment_id: If provided, retrieves replies to this comment ID instead of top-level comments.
395
+ cursor: Pagination cursor.
396
+ limit: Number of comments to return. (OpenAPI spec shows type string, passed as string if provided).
397
+
398
+ Returns:
399
+ A dictionary containing a list of comment objects and pagination details.
400
+
401
+ Raises:
402
+ httpx.HTTPError: If the API request fails.
403
+
404
+ Tags:
405
+ linkedin, post, comment, list, content, api, important
406
+ """
407
+ url = f"{self.base_url}/api/v1/posts/{post_id}/comments"
408
+ params: dict[str, Any] = {"account_id": self.account_id}
409
+ if cursor:
410
+ params["cursor"] = cursor
411
+ if limit is not None:
412
+ params["limit"] = str(limit)
413
+ if comment_id:
414
+ params["comment_id"] = comment_id
415
+
416
+ response = self._get(url, params=params)
417
+ return response.json()
418
+
419
+ def create_post(
420
+ self,
421
+ text: str,
422
+ mentions: list[dict[str, Any]] | None = None,
423
+ external_link: str | None = None,
424
+ ) -> dict[str, Any]:
425
+ """
426
+ Publishes a new top-level post from the account, including text, user mentions, and an external link. This function creates original content, distinguishing it from `create_post_comment` which adds replies to existing posts.
427
+
428
+ Args:
429
+ text: The main text content of the post.
430
+ mentions: Optional list of dictionaries, each representing a mention.
431
+ Example: `[{"entity_urn": "urn:li:person:...", "start_index": 0, "end_index": 5}]`
432
+ external_link: Optional string, an external URL that should be displayed within a card.
433
+
434
+ Returns:
435
+ A dictionary containing the ID of the created post.
436
+
437
+ Raises:
438
+ httpx.HTTPError: If the API request fails.
439
+
440
+ Tags:
441
+ linkedin, post, create, share, content, api, important
442
+ """
443
+ url = f"{self.base_url}/api/v1/posts"
444
+
445
+ params: dict[str, str] = {
446
+ "account_id": self.account_id,
447
+ "text": text,
78
448
  }
79
449
 
80
- url = f"{self.base_url}/rest/posts"
81
- query_params = {}
450
+ if mentions:
451
+ params["mentions"] = mentions
452
+ if external_link:
453
+ params["external_link"] = external_link
454
+
455
+ response = self._post(url, data=params)
456
+ return response.json()
457
+
458
+ def list_content_reactions(
459
+ self,
460
+ post_id: str,
461
+ comment_id: str | None = None,
462
+ cursor: str | None = None,
463
+ limit: int | None = None,
464
+ ) -> dict[str, Any]:
465
+ """
466
+ Retrieves a paginated list of reactions for a given post or, optionally, a specific comment. This read-only operation uses the account for the request, distinguishing it from the `create_reaction` function which adds new reactions.
467
+
468
+ Args:
469
+ post_id: The social ID of the post.
470
+ comment_id: If provided, retrieves reactions for this comment ID.
471
+ cursor: Pagination cursor.
472
+ limit: Number of reactions to return (1-100, spec max 250).
82
473
 
83
- response = self._post(
84
- url,
85
- data=request_body_data,
86
- params=query_params,
87
- )
474
+ Returns:
475
+ A dictionary containing a list of reaction objects and pagination details.
88
476
 
89
- self._handle_response(response)
477
+ Raises:
478
+ httpx.HTTPError: If the API request fails.
90
479
 
91
- post_id = response.headers.get("x-restli-id")
92
- if not post_id:
93
- raise ValueError("x-restli-id header not found in response")
480
+ Tags:
481
+ linkedin, post, reaction, list, like, content, api
482
+ """
483
+ url = f"{self.base_url}/api/v1/posts/{post_id}/reactions"
484
+ params: dict[str, Any] = {"account_id": self.account_id}
485
+ if cursor:
486
+ params["cursor"] = cursor
487
+ if limit:
488
+ params["limit"] = limit
489
+ if comment_id:
490
+ params["comment_id"] = comment_id
94
491
 
95
- return {
96
- "post_urn": post_id,
97
- "post_url": f"https://www.linkedin.com/feed/update/{post_id}",
492
+ response = self._get(url, params=params)
493
+ return response.json()
494
+
495
+ def create_post_comment(
496
+ self,
497
+ post_social_id: str,
498
+ text: str,
499
+ comment_id: str | None = None, # If provided, replies to a specific comment
500
+ mentions_body: list[dict[str, Any]] | None = None,
501
+ ) -> dict[str, Any]:
502
+ """
503
+ Publishes a comment on a specified post. By providing an optional `comment_id`, it creates a threaded reply to an existing comment instead of a new top-level one. This function's dual capability distinguishes it from `list_post_comments`, which only retrieves comments and their replies.
504
+
505
+ Args:
506
+ post_social_id: The social ID of the post to comment on.
507
+ text: The text content of the comment (passed as a query parameter).
508
+ Supports Unipile's mention syntax like "Hey {{0}}".
509
+ comment_id: Optional ID of a specific comment to reply to instead of commenting on the post.
510
+ mentions_body: Optional list of mention objects for the request body if needed.
511
+
512
+ Returns:
513
+ A dictionary, likely confirming comment creation. (Structure depends on actual API response)
514
+
515
+ Raises:
516
+ httpx.HTTPError: If the API request fails.
517
+
518
+ Tags:
519
+ linkedin, post, comment, create, content, api, important
520
+ """
521
+ url = f"{self.base_url}/api/v1/posts/{post_social_id}/comments"
522
+ params: dict[str, Any] = {
523
+ "account_id": self.account_id,
524
+ "text": text,
98
525
  }
99
526
 
100
- def get_authenticated_user_profile(self) -> dict[str, Any]:
527
+ if comment_id:
528
+ params["comment_id"] = comment_id
529
+
530
+ if mentions_body:
531
+ params = {"mentions": mentions_body}
532
+
533
+ response = self._post(url, data=params)
534
+
535
+ try:
536
+ return response.json()
537
+ except json.JSONDecodeError:
538
+ return {
539
+ "status": response.status_code,
540
+ "message": "Comment action processed.",
541
+ }
542
+
543
+ def create_reaction(
544
+ self,
545
+ post_social_id: str,
546
+ reaction_type: Literal[
547
+ "like", "celebrate", "love", "insightful", "funny", "support"
548
+ ],
549
+ comment_id: str | None = None,
550
+ ) -> dict[str, Any]:
101
551
  """
102
- Retrieves the authenticated user's profile information from the LinkedIn `/v2/userinfo` endpoint. It returns a dictionary containing the user's basic profile details, such as name and email, using the credentials from the active integration.
103
-
552
+ Adds a specified reaction (e.g., 'like', 'love') to a LinkedIn post or, optionally, to a specific comment. This function performs a POST request to create the reaction, differentiating it from `list_content_reactions` which only retrieves existing ones.
553
+
554
+ Args:
555
+ post_social_id: The social ID of the post or comment to react to.
556
+ reaction_type: The type of reaction. Valid values are "like", "celebrate", "love", "insightful", "funny", or "support".
557
+ comment_id: Optional ID of a specific comment to react to instead of the post.
558
+
104
559
  Returns:
105
- dict[str, Any]: Dictionary containing your LinkedIn profile information.
106
-
560
+ A dictionary, likely confirming the reaction. (Structure depends on actual API response)
561
+
107
562
  Raises:
108
- ValueError: If integration is not found
109
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body
110
-
563
+ httpx.HTTPError: If the API request fails.
564
+
111
565
  Tags:
112
- profile, info
566
+ linkedin, post, reaction, create, like, content, api, important
113
567
  """
114
- url = f"{self.base_url}/v2/userinfo"
115
- query_params = {}
568
+ url = f"{self.base_url}/api/v1/posts/reaction"
569
+
570
+ params: dict[str, str] = {
571
+ "account_id": self.account_id,
572
+ "post_id": post_social_id,
573
+ "reaction_type": reaction_type,
574
+ }
575
+
576
+ if comment_id:
577
+ params["comment_id"] = comment_id
578
+
579
+ response = self._post(url, data=params)
580
+
581
+ try:
582
+ return response.json()
583
+ except json.JSONDecodeError:
584
+ return {
585
+ "status": response.status_code,
586
+ "message": "Reaction action processed.",
587
+ }
588
+
589
+ def retrieve_user_profile(self, identifier: str) -> dict[str, Any]:
590
+ """
591
+ Retrieves a specific LinkedIn user's profile using their public or internal ID. Unlike `retrieve_own_profile`, which fetches the authenticated user's details, this function targets and returns data for any specified third-party user profile on the platform.
592
+
593
+ Args:
594
+ 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".
595
+
596
+ Returns:
597
+ A dictionary containing the user's profile details.
116
598
 
117
- response = self._get(
118
- url,
119
- params=query_params,
120
- )
599
+ Raises:
600
+ httpx.HTTPError: If the API request fails.
121
601
 
602
+ Tags:
603
+ linkedin, user, profile, retrieve, get, api, important
604
+ """
605
+ url = f"{self.base_url}/api/v1/users/{identifier}"
606
+ params: dict[str, Any] = {"account_id": self.account_id}
607
+ response = self._get(url, params=params)
122
608
  return self._handle_response(response)
123
609
 
124
- def delete_post(self, post_urn: str) -> dict[str, str]:
610
+ def search_people(
611
+ self,
612
+ cursor: str | None = None,
613
+ limit: int | None = None,
614
+ keywords: str | None = None,
615
+ location: str | None = None,
616
+ industry: str | None = None,
617
+ company: str | None = None,
618
+ ) -> dict[str, Any]:
125
619
  """
126
- Deletes a specific LinkedIn post identified by its unique Uniform Resource Name (URN). The function sends a DELETE request to the LinkedIn API and returns a confirmation dictionary with the deletion status upon a successful (HTTP 204) response.
620
+ Searches for LinkedIn user profiles using keywords, with optional filters for location, industry, and company. This function specifically targets the 'people' category, distinguishing it from other search methods like `search_companies` or `search_jobs` that query different entity types through the same API endpoint.
127
621
 
128
622
  Args:
129
- post_urn (str): The URN of the post to delete. Can be either a ugcPostUrn (urn:li:ugcPost:{id}) or shareUrn (urn:li:share:{id}).
623
+ cursor: Pagination cursor for the next page of entries.
624
+ limit: Number of items to return (up to 50 for Classic search).
625
+ keywords: Keywords to search for.
626
+ location: The geographical location to filter people by (e.g., "United States").
627
+ industry: The industry to filter people by.(eg., "Information Technology and Services").
628
+ company: The company to filter people by.(e.g., "Google").
130
629
 
131
630
  Returns:
132
- dict[str, str]: Dictionary containing the deletion status. Example: {"status": "deleted", "post_urn": "urn:li:share:6844785523593134080"}
631
+ A dictionary containing search results and pagination details.
133
632
 
134
633
  Raises:
135
- ValueError: If required parameter (post_urn) is missing or if integration is not found
136
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body
634
+ httpx.HTTPError: If the API request fails.
635
+ """
636
+ url = f"{self.base_url}/api/v1/linkedin/search"
637
+
638
+ params: dict[str, Any] = {"account_id": self.account_id}
639
+ if cursor:
640
+ params["cursor"] = cursor
641
+ if limit is not None:
642
+ params["limit"] = limit
643
+
644
+ payload: dict[str, Any] = {"api": "classic", "category": "people"}
645
+
646
+ if keywords:
647
+ payload["keywords"] = keywords
648
+
649
+ if location:
650
+ location_id = self._get_search_parameter_id("LOCATION", location)
651
+ payload["location"] = [location_id]
652
+
653
+ if industry:
654
+ industry_id = self._get_search_parameter_id("INDUSTRY", industry)
655
+ payload["industry"] = [industry_id]
137
656
 
657
+ if company:
658
+ company_id = self._get_search_parameter_id("COMPANY", company)
659
+ payload["company"] = [company_id]
138
660
 
139
- Tags:
140
- posts, important
661
+ response = self._post(url, params=params, data=payload)
662
+ return self._handle_response(response)
663
+
664
+ def search_companies(
665
+ self,
666
+ cursor: str | None = None,
667
+ limit: int | None = None,
668
+ keywords: str | None = None,
669
+ location: str | None = None,
670
+ industry: str | None = None,
671
+ ) -> dict[str, Any]:
672
+ """
673
+ Performs a paginated search for companies on LinkedIn using keywords, with optional location and industry filters. Its specific 'companies' search category distinguishes it from other methods like `search_people` or `search_posts`, ensuring that only company profiles are returned.
674
+
675
+ Args:
676
+ cursor: Pagination cursor for the next page of entries.
677
+ limit: Number of items to return (up to 50 for Classic search).
678
+ keywords: Keywords to search for.
679
+ location: The geographical location to filter companies by (e.g., "United States").
680
+ industry: The industry to filter companies by.(e.g., "Information Technology and Services").
681
+
682
+ Returns:
683
+ A dictionary containing search results and pagination details.
684
+
685
+ Raises:
686
+ httpx.HTTPError: If the API request fails.
141
687
  """
142
- url = f"{self.base_url}/rest/posts/{quote(post_urn, safe='')}"
143
- query_params = {}
688
+ url = f"{self.base_url}/api/v1/linkedin/search"
689
+
690
+ params: dict[str, Any] = {"account_id": self.account_id}
691
+ if cursor:
692
+ params["cursor"] = cursor
693
+ if limit is not None:
694
+ params["limit"] = limit
695
+
696
+ payload: dict[str, Any] = {"api": "classic", "category": "companies"}
144
697
 
145
- response = self._delete(
146
- url,
147
- params=query_params,
148
- )
698
+ if keywords:
699
+ payload["keywords"] = keywords
149
700
 
150
- if response.status_code == 204:
151
- return {"status": "deleted", "post_urn": post_urn}
152
- else:
153
- return self._handle_response(response)
701
+ if location:
702
+ location_id = self._get_search_parameter_id("LOCATION", location)
703
+ payload["location"] = [location_id]
704
+
705
+ if industry:
706
+ industry_id = self._get_search_parameter_id("INDUSTRY", industry)
707
+ payload["industry"] = [industry_id]
154
708
 
155
- def update_post(
709
+ response = self._post(url, params=params, data=payload)
710
+ return self._handle_response(response)
711
+
712
+ def search_posts(
156
713
  self,
157
- post_urn: str,
158
- commentary: str | None = None,
159
- content_call_to_action_label: str | None = None,
160
- content_landing_page: str | None = None,
161
- lifecycle_state: str | None = None,
162
- ad_context_name: str | None = None,
163
- ad_context_status: str | None = None,
164
- ) -> dict[str, str]:
714
+ cursor: str | None = None,
715
+ limit: int | None = None,
716
+ keywords: str | None = None,
717
+ date_posted: Literal["past_day", "past_week", "past_month"] | None = None,
718
+ sort_by: Literal["relevance", "date"] = "relevance",
719
+ ) -> dict[str, Any]:
165
720
  """
166
- Modifies an existing LinkedIn post identified by its URN. This function performs a partial update, allowing changes to specific attributes such as commentary, call-to-action details, lifecycle state, or sponsored ad context. It returns a confirmation dictionary upon successful completion of the PATCH request.
721
+ Performs a keyword-based search for LinkedIn posts, allowing filters for date and sorting by relevance. This function executes a general, platform-wide content search, distinguishing it from other search functions that target people, companies, or jobs, and from `list_profile_posts` which retrieves from a specific profile.
167
722
 
168
723
  Args:
169
- post_urn (str): The URN of the post to update. Can be either a ugcPostUrn (urn:li:ugcPost:{id}) or shareUrn (urn:li:share:{id}).
170
- commentary (str | None, optional): The user generated commentary of this post in little format.
171
- content_call_to_action_label (str | None, optional): The call to action label that a member can act on that opens a landing page.
172
- content_landing_page (str | None, optional): URL of the landing page.
173
- lifecycle_state (str | None, optional): The state of the content. Can be DRAFT, PUBLISHED, PUBLISH_REQUESTED, or PUBLISH_FAILED.
174
- ad_context_name (str | None, optional): Update the name of the sponsored content.
175
- ad_context_status (str | None, optional): Update the status of the sponsored content.
724
+ cursor: Pagination cursor for the next page of entries.
725
+ limit: Number of items to return (up to 50 for Classic search).
726
+ keywords: Keywords to search for.
727
+ date_posted: Filter by when the post was posted.
728
+ sort_by: How to sort the results.
176
729
 
177
730
  Returns:
178
- dict[str, str]: Dictionary containing the update status. Example: {"status": "updated", "post_urn": "urn:li:share:6844785523593134080"}
731
+ A dictionary containing search results and pagination details.
179
732
 
180
733
  Raises:
181
- ValueError: If required parameter (post_urn) is missing or if integration is not found
182
- HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body
183
-
734
+ httpx.HTTPError: If the API request fails.
735
+ """
736
+ url = f"{self.base_url}/api/v1/linkedin/search"
737
+
738
+ params: dict[str, Any] = {"account_id": self.account_id}
739
+ if cursor:
740
+ params["cursor"] = cursor
741
+ if limit is not None:
742
+ params["limit"] = limit
743
+
744
+ payload: dict[str, Any] = {"api": "classic", "category": "posts"}
745
+
746
+ if keywords:
747
+ payload["keywords"] = keywords
748
+ if date_posted:
749
+ payload["date_posted"] = date_posted
750
+ if sort_by:
751
+ payload["sort_by"] = sort_by
752
+
753
+ response = self._post(url, params=params, data=payload)
754
+ return self._handle_response(response)
755
+
756
+ def search_jobs(
757
+ self,
758
+ cursor: str | None = None,
759
+ limit: int | None = None,
760
+ keywords: str | None = None,
761
+ region: str | None = None,
762
+ sort_by: Literal["relevance", "date"] = "relevance",
763
+ minimum_salary_value: int = 40,
764
+ industry: str | None = None,
765
+ ) -> dict[str, Any]:
766
+ """
767
+ Performs a LinkedIn search for jobs, filtering results by keywords, region, industry, and minimum salary. Unlike other search functions (`search_people`, `search_companies`), this method is specifically configured to query the 'jobs' category, providing a paginated list of relevant employment opportunities.
184
768
 
769
+ Args:
770
+ cursor: Pagination cursor for the next page of entries.
771
+ limit: Number of items to return (up to 50 for Classic search).
772
+ keywords: Keywords to search for.
773
+ region: The geographical region to filter jobs by (e.g., "United States").
774
+ sort_by: How to sort the results.(e.g., "relevance" or "date".)
775
+ minimum_salary_value: The minimum salary to filter for.
776
+ industry: The industry to filter jobs by.(e.g., "Software Development").
185
777
 
778
+ Returns:
779
+ A dictionary containing search results and pagination details.
186
780
 
187
- Tags:
188
- posts, update, important
781
+ Raises:
782
+ httpx.HTTPError: If the API request fails.
783
+ ValueError: If the specified location is not found.
189
784
  """
190
- url = f"{self.base_url}/rest/posts/{quote(post_urn, safe='')}"
191
- query_params = {}
192
-
193
- # Build the patch data
194
- patch_data = {"$set": {}}
195
- ad_context_data = {}
785
+ url = f"{self.base_url}/api/v1/linkedin/search"
196
786
 
197
- if commentary is not None:
198
- patch_data["$set"]["commentary"] = commentary
199
- if content_call_to_action_label is not None:
200
- patch_data["$set"]["contentCallToActionLabel"] = (
201
- content_call_to_action_label
202
- )
203
- if content_landing_page is not None:
204
- patch_data["$set"]["contentLandingPage"] = content_landing_page
205
- if lifecycle_state is not None:
206
- patch_data["$set"]["lifecycleState"] = lifecycle_state
787
+ params: dict[str, Any] = {"account_id": self.account_id}
788
+ if cursor:
789
+ params["cursor"] = cursor
790
+ if limit is not None:
791
+ params["limit"] = limit
207
792
 
208
- if ad_context_name is not None or ad_context_status is not None:
209
- ad_context_data["$set"] = {}
210
- if ad_context_name is not None:
211
- ad_context_data["$set"]["dscName"] = ad_context_name
212
- if ad_context_status is not None:
213
- ad_context_data["$set"]["dscStatus"] = ad_context_status
214
- patch_data["adContext"] = ad_context_data
793
+ payload: dict[str, Any] = {
794
+ "api": "classic",
795
+ "category": "jobs",
796
+ "minimum_salary": {
797
+ "currency": "USD",
798
+ "value": minimum_salary_value,
799
+ },
800
+ }
215
801
 
216
- request_body_data = {"patch": patch_data}
802
+ if keywords:
803
+ payload["keywords"] = keywords
804
+ if sort_by:
805
+ payload["sort_by"] = sort_by
217
806
 
218
- response = self._post(
219
- url,
220
- data=request_body_data,
221
- params=query_params,
222
- )
807
+ # If location is provided, get its ID and add it to the payload
808
+ if region:
809
+ location_id = self._get_search_parameter_id("LOCATION", region)
810
+ payload["region"] = location_id
811
+
812
+ if industry:
813
+ industry_id = self._get_search_parameter_id("INDUSTRY", industry)
814
+ payload["industry"] = [industry_id]
223
815
 
224
- if response.status_code == 204:
225
- return {"status": "updated", "post_urn": post_urn}
226
- else:
227
- return self._handle_response(response)
816
+ response = self._post(url, params=params, data=payload)
817
+ return self._handle_response(response)
228
818
 
229
- def list_tools(self):
230
- """
231
- Lists the available tools (methods) for this application.
232
- """
819
+ def list_tools(self) -> list[Callable]:
233
820
  return [
821
+ self.list_all_chats,
822
+ self.list_chat_messages,
823
+ self.send_chat_message,
824
+ self.retrieve_chat,
825
+ self.list_all_messages,
826
+ self.list_profile_posts,
827
+ self.retrieve_own_profile,
828
+ self.retrieve_user_profile,
829
+ self.retrieve_post,
830
+ self.list_post_comments,
234
831
  self.create_post,
235
- self.get_authenticated_user_profile,
236
- self.delete_post,
237
- self.update_post,
832
+ self.list_content_reactions,
833
+ self.create_post_comment,
834
+ self.create_reaction,
835
+ self.search_companies,
836
+ self.search_jobs,
837
+ self.search_people,
838
+ self.search_posts,
238
839
  ]