universal-mcp-applications 0.1.39rc8__py3-none-any.whl → 0.1.39rc16__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.
- universal_mcp/applications/BEST_PRACTICES.md +1 -1
- universal_mcp/applications/airtable/app.py +13 -13
- universal_mcp/applications/apollo/app.py +2 -2
- universal_mcp/applications/aws_s3/app.py +30 -19
- universal_mcp/applications/browser_use/app.py +10 -7
- universal_mcp/applications/contentful/app.py +4 -4
- universal_mcp/applications/crustdata/app.py +2 -2
- universal_mcp/applications/e2b/app.py +3 -4
- universal_mcp/applications/elevenlabs/README.md +27 -3
- universal_mcp/applications/elevenlabs/app.py +753 -48
- universal_mcp/applications/exa/app.py +18 -11
- universal_mcp/applications/falai/README.md +5 -7
- universal_mcp/applications/falai/app.py +160 -159
- universal_mcp/applications/firecrawl/app.py +14 -15
- universal_mcp/applications/ghost_content/app.py +4 -4
- universal_mcp/applications/github/app.py +2 -2
- universal_mcp/applications/gong/app.py +2 -2
- universal_mcp/applications/google_docs/README.md +15 -14
- universal_mcp/applications/google_docs/app.py +5 -4
- universal_mcp/applications/google_gemini/app.py +61 -17
- universal_mcp/applications/google_sheet/README.md +2 -1
- universal_mcp/applications/google_sheet/app.py +55 -0
- universal_mcp/applications/heygen/README.md +10 -32
- universal_mcp/applications/heygen/app.py +350 -744
- universal_mcp/applications/klaviyo/app.py +2 -2
- universal_mcp/applications/linkedin/README.md +14 -2
- universal_mcp/applications/linkedin/app.py +411 -38
- universal_mcp/applications/ms_teams/app.py +420 -1285
- universal_mcp/applications/notion/app.py +2 -2
- universal_mcp/applications/openai/app.py +1 -1
- universal_mcp/applications/perplexity/app.py +6 -7
- universal_mcp/applications/reddit/app.py +4 -4
- universal_mcp/applications/resend/app.py +31 -32
- universal_mcp/applications/rocketlane/app.py +2 -2
- universal_mcp/applications/scraper/app.py +51 -21
- universal_mcp/applications/semrush/app.py +1 -1
- universal_mcp/applications/serpapi/app.py +8 -7
- universal_mcp/applications/shopify/app.py +5 -7
- universal_mcp/applications/shortcut/app.py +3 -2
- universal_mcp/applications/slack/app.py +2 -2
- universal_mcp/applications/twilio/app.py +14 -13
- {universal_mcp_applications-0.1.39rc8.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/METADATA +1 -1
- {universal_mcp_applications-0.1.39rc8.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/RECORD +45 -45
- {universal_mcp_applications-0.1.39rc8.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/WHEEL +0 -0
- {universal_mcp_applications-0.1.39rc8.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/licenses/LICENSE +0 -0
|
@@ -20,16 +20,22 @@ class LinkedinApp(APIApplication):
|
|
|
20
20
|
Args:
|
|
21
21
|
integration: The integration configuration containing credentials and other settings.
|
|
22
22
|
It is expected that the integration provides the 'x-api-key'
|
|
23
|
-
via headers in `integration.
|
|
23
|
+
via headers in `integration.get_credentials_async()`, e.g.,
|
|
24
24
|
`{"headers": {"x-api-key": "YOUR_API_KEY"}}`.
|
|
25
25
|
"""
|
|
26
26
|
super().__init__(name="linkedin", integration=integration)
|
|
27
27
|
self._base_url = None
|
|
28
|
-
self.
|
|
28
|
+
self._account_id = None
|
|
29
|
+
|
|
30
|
+
async def _get_account_id(self) -> str | None:
|
|
31
|
+
if self._account_id:
|
|
32
|
+
return self._account_id
|
|
29
33
|
if self.integration:
|
|
30
|
-
credentials = self.integration.
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
credentials = await self.integration.get_credentials_async()
|
|
35
|
+
self._account_id = credentials.get("account_id")
|
|
36
|
+
else:
|
|
37
|
+
logger.warning("Integration not found")
|
|
38
|
+
return self._account_id
|
|
33
39
|
|
|
34
40
|
@property
|
|
35
41
|
def base_url(self) -> str:
|
|
@@ -84,7 +90,7 @@ class LinkedinApp(APIApplication):
|
|
|
84
90
|
httpx.HTTPError: If the API request fails.
|
|
85
91
|
"""
|
|
86
92
|
url = f"{self.base_url}/api/v1/linkedin/search/parameters"
|
|
87
|
-
params = {"account_id": self.
|
|
93
|
+
params = {"account_id": await self._get_account_id(), "keywords": keywords, "type": param_type}
|
|
88
94
|
response = await self._aget(url, params=params)
|
|
89
95
|
results = self._handle_response(response)
|
|
90
96
|
items = results.get("items", [])
|
|
@@ -115,7 +121,7 @@ class LinkedinApp(APIApplication):
|
|
|
115
121
|
linkedin, chat, create, start, new, messaging, api, important
|
|
116
122
|
"""
|
|
117
123
|
url = f"{self.base_url}/api/v1/chats"
|
|
118
|
-
form_payload = {"account_id": (None, self.
|
|
124
|
+
form_payload = {"account_id": (None, await self._get_account_id()), "text": (None, text), "attendees_ids": (None, provider_id)}
|
|
119
125
|
api_key = os.getenv("UNIPILE_API_KEY")
|
|
120
126
|
if not api_key:
|
|
121
127
|
raise ValueError("UNIPILE_API_KEY environment variable is not set.")
|
|
@@ -154,7 +160,7 @@ class LinkedinApp(APIApplication):
|
|
|
154
160
|
"""
|
|
155
161
|
url = f"{self.base_url}/api/v1/chats"
|
|
156
162
|
params: dict[str, Any] = {}
|
|
157
|
-
params["account_id"] = self.
|
|
163
|
+
params["account_id"] = await self._get_account_id()
|
|
158
164
|
if unread is not None:
|
|
159
165
|
params["unread"] = unread
|
|
160
166
|
if cursor:
|
|
@@ -255,8 +261,8 @@ class LinkedinApp(APIApplication):
|
|
|
255
261
|
"""
|
|
256
262
|
url = f"{self.base_url}/api/v1/chats/{chat_id}"
|
|
257
263
|
params: dict[str, Any] = {}
|
|
258
|
-
if self.
|
|
259
|
-
params["account_id"] = self.
|
|
264
|
+
if await self._get_account_id():
|
|
265
|
+
params["account_id"] = await self._get_account_id()
|
|
260
266
|
response = await self._aget(url, params=params)
|
|
261
267
|
return self._handle_response(response)
|
|
262
268
|
|
|
@@ -299,22 +305,22 @@ class LinkedinApp(APIApplication):
|
|
|
299
305
|
params["limit"] = limit
|
|
300
306
|
if sender_id:
|
|
301
307
|
params["sender_id"] = sender_id
|
|
302
|
-
if self.
|
|
303
|
-
params["account_id"] = self.
|
|
308
|
+
if await self._get_account_id():
|
|
309
|
+
params["account_id"] = await self._get_account_id()
|
|
304
310
|
response = await self._aget(url, params=params)
|
|
305
311
|
return self._handle_response(response)
|
|
306
312
|
|
|
307
313
|
async def list_profile_posts(
|
|
308
|
-
self,
|
|
314
|
+
self, provider_id: str, cursor: str | None = None, limit: int | None = None, is_company: bool | None = None
|
|
309
315
|
) -> dict[str, Any]:
|
|
310
316
|
"""
|
|
311
317
|
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.
|
|
312
318
|
|
|
313
319
|
Args:
|
|
314
|
-
|
|
320
|
+
provider_id: The entity's provider internal ID (LinkedIn ID).
|
|
315
321
|
cursor: Pagination cursor.
|
|
316
|
-
limit: Number of items to return (1-100
|
|
317
|
-
is_company: Boolean indicating if the
|
|
322
|
+
limit: Number of items to return (1-100).
|
|
323
|
+
is_company: Boolean indicating if the provider_id is for a company.
|
|
318
324
|
|
|
319
325
|
Returns:
|
|
320
326
|
A dictionary containing a list of post objects and pagination details.
|
|
@@ -325,8 +331,8 @@ class LinkedinApp(APIApplication):
|
|
|
325
331
|
Tags:
|
|
326
332
|
linkedin, post, list, user_posts, company_posts, content, api, important
|
|
327
333
|
"""
|
|
328
|
-
url = f"{self.base_url}/api/v1/users/{
|
|
329
|
-
params: dict[str, Any] = {"account_id": self.
|
|
334
|
+
url = f"{self.base_url}/api/v1/users/{provider_id}/posts"
|
|
335
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
330
336
|
if cursor:
|
|
331
337
|
params["cursor"] = cursor
|
|
332
338
|
if limit:
|
|
@@ -336,6 +342,33 @@ class LinkedinApp(APIApplication):
|
|
|
336
342
|
response = await self._aget(url, params=params)
|
|
337
343
|
return self._handle_response(response)
|
|
338
344
|
|
|
345
|
+
async def list_profile_comments(self, provider_id: str, limit: int | None = None, cursor: str | None = None) -> dict[str, Any]:
|
|
346
|
+
"""
|
|
347
|
+
Retrieves a list of comments made by a specific user using their provider ID.
|
|
348
|
+
|
|
349
|
+
Args:
|
|
350
|
+
provider_id: The entity's provider internal ID (LinkedIn ID).
|
|
351
|
+
limit: Number of items to return (1-100).
|
|
352
|
+
cursor: Pagination cursor.
|
|
353
|
+
|
|
354
|
+
Returns:
|
|
355
|
+
A dictionary containing the list of comments.
|
|
356
|
+
|
|
357
|
+
Raises:
|
|
358
|
+
httpx.HTTPError: If the API request fails.
|
|
359
|
+
|
|
360
|
+
Tags:
|
|
361
|
+
linkedin, user, comments, list, content, api
|
|
362
|
+
"""
|
|
363
|
+
url = f"{self.base_url}/api/v1/users/{provider_id}/comments"
|
|
364
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
365
|
+
if cursor:
|
|
366
|
+
params["cursor"] = cursor
|
|
367
|
+
if limit:
|
|
368
|
+
params["limit"] = limit
|
|
369
|
+
response = await self._aget(url, params=params)
|
|
370
|
+
return self._handle_response(response)
|
|
371
|
+
|
|
339
372
|
async def retrieve_own_profile(self) -> dict[str, Any]:
|
|
340
373
|
"""
|
|
341
374
|
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.
|
|
@@ -350,7 +383,7 @@ class LinkedinApp(APIApplication):
|
|
|
350
383
|
linkedin, user, profile, me, retrieve, get, api
|
|
351
384
|
"""
|
|
352
385
|
url = f"{self.base_url}/api/v1/users/me"
|
|
353
|
-
params: dict[str, Any] = {"account_id": self.
|
|
386
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
354
387
|
response = await self._aget(url, params=params)
|
|
355
388
|
return self._handle_response(response)
|
|
356
389
|
|
|
@@ -371,7 +404,7 @@ class LinkedinApp(APIApplication):
|
|
|
371
404
|
linkedin, post, retrieve, get, content, api, important
|
|
372
405
|
"""
|
|
373
406
|
url = f"{self.base_url}/api/v1/posts/{post_id}"
|
|
374
|
-
params: dict[str, Any] = {"account_id": self.
|
|
407
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
375
408
|
response = await self._aget(url, params=params)
|
|
376
409
|
return self._handle_response(response)
|
|
377
410
|
|
|
@@ -397,7 +430,7 @@ class LinkedinApp(APIApplication):
|
|
|
397
430
|
linkedin, post, comment, list, content, api, important
|
|
398
431
|
"""
|
|
399
432
|
url = f"{self.base_url}/api/v1/posts/{post_id}/comments"
|
|
400
|
-
params: dict[str, Any] = {"account_id": self.
|
|
433
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
401
434
|
if cursor:
|
|
402
435
|
params["cursor"] = cursor
|
|
403
436
|
if limit is not None:
|
|
@@ -429,7 +462,7 @@ class LinkedinApp(APIApplication):
|
|
|
429
462
|
linkedin, post, create, share, content, api, important
|
|
430
463
|
"""
|
|
431
464
|
url = f"{self.base_url}/api/v1/posts"
|
|
432
|
-
params: dict[str, str] = {"account_id": self.
|
|
465
|
+
params: dict[str, str] = {"account_id": await self._get_account_id(), "text": text}
|
|
433
466
|
if mentions:
|
|
434
467
|
params["mentions"] = mentions
|
|
435
468
|
if external_link:
|
|
@@ -447,7 +480,7 @@ class LinkedinApp(APIApplication):
|
|
|
447
480
|
post_id: The social ID of the post.
|
|
448
481
|
comment_id: If provided, retrieves reactions for this comment ID.
|
|
449
482
|
cursor: Pagination cursor.
|
|
450
|
-
limit: Number of reactions to return (1-100
|
|
483
|
+
limit: Number of reactions to return (1-100).
|
|
451
484
|
|
|
452
485
|
Returns:
|
|
453
486
|
A dictionary containing a list of reaction objects and pagination details.
|
|
@@ -459,7 +492,7 @@ class LinkedinApp(APIApplication):
|
|
|
459
492
|
linkedin, post, reaction, list, like, content, api
|
|
460
493
|
"""
|
|
461
494
|
url = f"{self.base_url}/api/v1/posts/{post_id}/reactions"
|
|
462
|
-
params: dict[str, Any] = {"account_id": self.
|
|
495
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
463
496
|
if cursor:
|
|
464
497
|
params["cursor"] = cursor
|
|
465
498
|
if limit:
|
|
@@ -492,7 +525,7 @@ class LinkedinApp(APIApplication):
|
|
|
492
525
|
linkedin, post, comment, create, content, api, important
|
|
493
526
|
"""
|
|
494
527
|
url = f"{self.base_url}/api/v1/posts/{post_social_id}/comments"
|
|
495
|
-
params: dict[str, Any] = {"account_id": self.
|
|
528
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id(), "text": text}
|
|
496
529
|
if comment_id:
|
|
497
530
|
params["comment_id"] = comment_id
|
|
498
531
|
if mentions_body:
|
|
@@ -524,7 +557,7 @@ class LinkedinApp(APIApplication):
|
|
|
524
557
|
linkedin, post, reaction, create, like, content, api, important
|
|
525
558
|
"""
|
|
526
559
|
url = f"{self.base_url}/api/v1/posts/reaction"
|
|
527
|
-
params: dict[str, str] = {"account_id": self.
|
|
560
|
+
params: dict[str, str] = {"account_id": await self._get_account_id(), "post_id": post_social_id, "reaction_type": reaction_type}
|
|
528
561
|
if comment_id:
|
|
529
562
|
params["comment_id"] = comment_id
|
|
530
563
|
response = await self._apost(url, data=params)
|
|
@@ -547,7 +580,7 @@ class LinkedinApp(APIApplication):
|
|
|
547
580
|
linkedin, user, profile, retrieve, get, api, important
|
|
548
581
|
"""
|
|
549
582
|
url = f"{self.base_url}/api/v1/users/{public_identifier}"
|
|
550
|
-
params: dict[str, Any] = {"account_id": self.
|
|
583
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
551
584
|
response = await self._aget(url, params=params)
|
|
552
585
|
return self._handle_response(response)
|
|
553
586
|
|
|
@@ -578,7 +611,7 @@ class LinkedinApp(APIApplication):
|
|
|
578
611
|
httpx.HTTPError: If the API request fails.
|
|
579
612
|
"""
|
|
580
613
|
url = f"{self.base_url}/api/v1/linkedin/search"
|
|
581
|
-
params: dict[str, Any] = {"account_id": self.
|
|
614
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
582
615
|
if cursor:
|
|
583
616
|
params["cursor"] = cursor
|
|
584
617
|
if limit is not None:
|
|
@@ -623,7 +656,7 @@ class LinkedinApp(APIApplication):
|
|
|
623
656
|
httpx.HTTPError: If the API request fails.
|
|
624
657
|
"""
|
|
625
658
|
url = f"{self.base_url}/api/v1/linkedin/search"
|
|
626
|
-
params: dict[str, Any] = {"account_id": self.
|
|
659
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
627
660
|
if cursor:
|
|
628
661
|
params["cursor"] = cursor
|
|
629
662
|
if limit is not None:
|
|
@@ -665,7 +698,7 @@ class LinkedinApp(APIApplication):
|
|
|
665
698
|
httpx.HTTPError: If the API request fails.
|
|
666
699
|
"""
|
|
667
700
|
url = f"{self.base_url}/api/v1/linkedin/search"
|
|
668
|
-
params: dict[str, Any] = {"account_id": self.
|
|
701
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
669
702
|
if cursor:
|
|
670
703
|
params["cursor"] = cursor
|
|
671
704
|
if limit is not None:
|
|
@@ -710,7 +743,7 @@ class LinkedinApp(APIApplication):
|
|
|
710
743
|
ValueError: If the specified location is not found.
|
|
711
744
|
"""
|
|
712
745
|
url = f"{self.base_url}/api/v1/linkedin/search"
|
|
713
|
-
params: dict[str, Any] = {"account_id": self.
|
|
746
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
714
747
|
if cursor:
|
|
715
748
|
params["cursor"] = cursor
|
|
716
749
|
if limit is not None:
|
|
@@ -753,7 +786,7 @@ class LinkedinApp(APIApplication):
|
|
|
753
786
|
linkedin, user, invite, connect, contact, api, important
|
|
754
787
|
"""
|
|
755
788
|
url = f"{self.base_url}/api/v1/users/invite"
|
|
756
|
-
payload: dict[str, Any] = {"account_id": self.
|
|
789
|
+
payload: dict[str, Any] = {"account_id": await self._get_account_id(), "provider_id": provider_id}
|
|
757
790
|
if user_email:
|
|
758
791
|
payload["user_email"] = user_email
|
|
759
792
|
if message:
|
|
@@ -781,7 +814,7 @@ class LinkedinApp(APIApplication):
|
|
|
781
814
|
linkedin, user, invite, sent, list, contacts, api
|
|
782
815
|
"""
|
|
783
816
|
url = f"{self.base_url}/api/v1/users/invite/sent"
|
|
784
|
-
params: dict[str, Any] = {"account_id": self.
|
|
817
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
785
818
|
if cursor:
|
|
786
819
|
params["cursor"] = cursor
|
|
787
820
|
if limit is not None:
|
|
@@ -807,7 +840,7 @@ class LinkedinApp(APIApplication):
|
|
|
807
840
|
linkedin, user, invite, received, list, contacts, api
|
|
808
841
|
"""
|
|
809
842
|
url = f"{self.base_url}/api/v1/users/invite/received"
|
|
810
|
-
params: dict[str, Any] = {"account_id": self.
|
|
843
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
811
844
|
if cursor:
|
|
812
845
|
params["cursor"] = cursor
|
|
813
846
|
if limit is not None:
|
|
@@ -836,7 +869,7 @@ class LinkedinApp(APIApplication):
|
|
|
836
869
|
linkedin, user, invite, received, handle, accept, decline, api
|
|
837
870
|
"""
|
|
838
871
|
url = f"{self.base_url}/api/v1/users/invite/received/{invitation_id}"
|
|
839
|
-
payload: dict[str, Any] = {"provider": "LINKEDIN", "action": action, "shared_secret": shared_secret, "account_id": self.
|
|
872
|
+
payload: dict[str, Any] = {"provider": "LINKEDIN", "action": action, "shared_secret": shared_secret, "account_id": await self._get_account_id()}
|
|
840
873
|
response = await self._apost(url, data=payload)
|
|
841
874
|
return self._handle_response(response)
|
|
842
875
|
|
|
@@ -857,7 +890,7 @@ class LinkedinApp(APIApplication):
|
|
|
857
890
|
linkedin, user, invite, sent, cancel, delete, api
|
|
858
891
|
"""
|
|
859
892
|
url = f"{self.base_url}/api/v1/users/invite/sent/{invitation_id}"
|
|
860
|
-
params = {"account_id": self.
|
|
893
|
+
params = {"account_id": await self._get_account_id()}
|
|
861
894
|
response = await self._adelete(url, params=params)
|
|
862
895
|
return self._handle_response(response)
|
|
863
896
|
|
|
@@ -879,7 +912,7 @@ class LinkedinApp(APIApplication):
|
|
|
879
912
|
linkedin, user, followers, list, contacts, api
|
|
880
913
|
"""
|
|
881
914
|
url = f"{self.base_url}/api/v1/users/followers"
|
|
882
|
-
params: dict[str, Any] = {"account_id": self.
|
|
915
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
883
916
|
if cursor:
|
|
884
917
|
params["cursor"] = cursor
|
|
885
918
|
if limit is not None:
|
|
@@ -905,7 +938,7 @@ class LinkedinApp(APIApplication):
|
|
|
905
938
|
linkedin, user, following, list, contacts, api
|
|
906
939
|
"""
|
|
907
940
|
url = f"{self.base_url}/api/v1/users/following"
|
|
908
|
-
params: dict[str, Any] = {"account_id": self.
|
|
941
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
909
942
|
if cursor:
|
|
910
943
|
params["cursor"] = cursor
|
|
911
944
|
if limit is not None:
|
|
@@ -913,6 +946,336 @@ class LinkedinApp(APIApplication):
|
|
|
913
946
|
response = self._get(url, params=params)
|
|
914
947
|
return self._handle_response(response)
|
|
915
948
|
|
|
949
|
+
async def list_job_postings(
|
|
950
|
+
self,
|
|
951
|
+
category: Literal["active", "draft", "closed"] = "active",
|
|
952
|
+
limit: int | None = None,
|
|
953
|
+
cursor: str | None = None,
|
|
954
|
+
) -> dict[str, Any]:
|
|
955
|
+
"""
|
|
956
|
+
Retrieve the job offers you have posted on LinkedIn whether they are open, closed, or still drafts.
|
|
957
|
+
|
|
958
|
+
Args:
|
|
959
|
+
category: The state of the requested job postings. Default is active.
|
|
960
|
+
limit: A limit for the number of items returned in the response. The value can be set between 1 and 250.
|
|
961
|
+
cursor: A cursor for pagination purposes.
|
|
962
|
+
|
|
963
|
+
Returns:
|
|
964
|
+
A dictionary containing a list of job postings and pagination details.
|
|
965
|
+
|
|
966
|
+
Raises:
|
|
967
|
+
httpx.HTTPError: If the API request fails.
|
|
968
|
+
|
|
969
|
+
Tags:
|
|
970
|
+
linkedin, jobs, list, postings, api
|
|
971
|
+
"""
|
|
972
|
+
url = f"{self.base_url}/api/v1/linkedin/jobs"
|
|
973
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
974
|
+
if category:
|
|
975
|
+
params["category"] = category
|
|
976
|
+
if limit:
|
|
977
|
+
params["limit"] = limit
|
|
978
|
+
if cursor:
|
|
979
|
+
params["cursor"] = cursor
|
|
980
|
+
response = await self._aget(url, params=params)
|
|
981
|
+
return self._handle_response(response)
|
|
982
|
+
|
|
983
|
+
async def create_job_posting(
|
|
984
|
+
self,
|
|
985
|
+
job_title: dict[str, str],
|
|
986
|
+
company: dict[str, str],
|
|
987
|
+
workplace: Literal["ON_SITE", "HYBRID", "REMOTE"],
|
|
988
|
+
location: str,
|
|
989
|
+
description: str,
|
|
990
|
+
employment_status: Literal[
|
|
991
|
+
"FULL_TIME", "PART_TIME", "CONTRACT", "TEMPORARY", "OTHER", "VOLUNTEER", "INTERNSHIP"
|
|
992
|
+
] = "FULL_TIME",
|
|
993
|
+
auto_rejection_template: str | None = None,
|
|
994
|
+
screening_questions: list[dict[str, Any]] | None = None,
|
|
995
|
+
recruiter: dict[str, Any] | None = None,
|
|
996
|
+
) -> dict[str, Any]:
|
|
997
|
+
"""
|
|
998
|
+
Create a new job offer draft.
|
|
999
|
+
|
|
1000
|
+
Args:
|
|
1001
|
+
job_title: Required. A dictionary containing either {"id": "..."} or {"text": "..."}.
|
|
1002
|
+
company: Required. A dictionary containing either {"id": "..."} or {"text": "..."}.
|
|
1003
|
+
workplace: Required. One of "ON_SITE", "HYBRID", "REMOTE".
|
|
1004
|
+
location: Required. The ID of the location parameter. Use type LOCATION on the List search parameters route.
|
|
1005
|
+
description: Required. HTML description of the job.
|
|
1006
|
+
employment_status: Optional. One of "FULL_TIME", "PART_TIME", "CONTRACT", "TEMPORARY", "OTHER", "VOLUNTEER", "INTERNSHIP".
|
|
1007
|
+
auto_rejection_template: Optional. A rejection message template.
|
|
1008
|
+
screening_questions: Optional. A list of screening questions.
|
|
1009
|
+
recruiter: Optional. Recruiter object containing:
|
|
1010
|
+
- project: Required. {"id": "..."} or {"name": "..."}.
|
|
1011
|
+
- functions: Required. List of strings (job function IDs).
|
|
1012
|
+
- industries: Required. List of strings (industry IDs).
|
|
1013
|
+
- seniority: Required. Enum (e.g. "INTERNSHIP", "ENTRY_LEVEL", "ASSOCIATE", "MID_SENIOR_LEVEL", "DIRECTOR", "EXECUTIVE", "NOT_APPLICABLE").
|
|
1014
|
+
- apply_method: Required. {"apply_within_linkedin": ...} or {"apply_through_external_website": ...}.
|
|
1015
|
+
- include_poster_info: Optional boolean.
|
|
1016
|
+
- tracking_pixel_url: Optional string.
|
|
1017
|
+
- company_job_id: Optional string.
|
|
1018
|
+
- auto_archive_applicants: Optional object.
|
|
1019
|
+
- send_rejection_notification: Optional boolean.
|
|
1020
|
+
|
|
1021
|
+
Returns:
|
|
1022
|
+
A dictionary containing the response from the API.
|
|
1023
|
+
|
|
1024
|
+
Raises:
|
|
1025
|
+
httpx.HTTPError: If the API request fails.
|
|
1026
|
+
"""
|
|
1027
|
+
url = f"{self.base_url}/api/v1/linkedin/jobs"
|
|
1028
|
+
payload: dict[str, Any] = {
|
|
1029
|
+
"account_id": await self._get_account_id(),
|
|
1030
|
+
"job_title": job_title,
|
|
1031
|
+
"company": company,
|
|
1032
|
+
"workplace": workplace,
|
|
1033
|
+
"location": location,
|
|
1034
|
+
"description": description,
|
|
1035
|
+
"employment_status": employment_status,
|
|
1036
|
+
}
|
|
1037
|
+
if auto_rejection_template:
|
|
1038
|
+
payload["auto_rejection_template"] = auto_rejection_template
|
|
1039
|
+
if screening_questions:
|
|
1040
|
+
payload["screening_questions"] = screening_questions
|
|
1041
|
+
if recruiter:
|
|
1042
|
+
payload["recruiter"] = recruiter
|
|
1043
|
+
|
|
1044
|
+
response = await self._apost(url, data=payload)
|
|
1045
|
+
return self._handle_response(response)
|
|
1046
|
+
|
|
1047
|
+
async def close_job_posting(
|
|
1048
|
+
self,
|
|
1049
|
+
job_id: str,
|
|
1050
|
+
service: Literal["CLASSIC", "RECRUITER"] | None = None,
|
|
1051
|
+
) -> dict[str, Any]:
|
|
1052
|
+
"""
|
|
1053
|
+
Close a job offer you have posted.
|
|
1054
|
+
|
|
1055
|
+
Args:
|
|
1056
|
+
job_id: Required. The ID of the job offer.
|
|
1057
|
+
service: Optional. The Linkedin service the job posting depends on.
|
|
1058
|
+
|
|
1059
|
+
Returns:
|
|
1060
|
+
A dictionary containing the response from the API.
|
|
1061
|
+
|
|
1062
|
+
Raises:
|
|
1063
|
+
httpx.HTTPError: If the API request fails.
|
|
1064
|
+
"""
|
|
1065
|
+
url = f"{self.base_url}/api/v1/linkedin/jobs/{job_id}/close"
|
|
1066
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
1067
|
+
if service:
|
|
1068
|
+
params["service"] = service
|
|
1069
|
+
response = await self._apost(url, params=params, data={})
|
|
1070
|
+
return self._handle_response(response)
|
|
1071
|
+
|
|
1072
|
+
async def retrieve_job_posting(
|
|
1073
|
+
self,
|
|
1074
|
+
job_id: str,
|
|
1075
|
+
service: Literal["CLASSIC", "RECRUITER"] = "CLASSIC",
|
|
1076
|
+
) -> dict[str, Any]:
|
|
1077
|
+
"""
|
|
1078
|
+
Retrieve a job offer.
|
|
1079
|
+
|
|
1080
|
+
Args:
|
|
1081
|
+
job_id: Required. The ID of the job offer.
|
|
1082
|
+
service: Required. The Linkedin service the job posting depends on. Default is CLASSIC.
|
|
1083
|
+
|
|
1084
|
+
Returns:
|
|
1085
|
+
A dictionary containing the job offer details.
|
|
1086
|
+
|
|
1087
|
+
Raises:
|
|
1088
|
+
httpx.HTTPError: If the API request fails.
|
|
1089
|
+
"""
|
|
1090
|
+
url = f"{self.base_url}/api/v1/linkedin/jobs/{job_id}"
|
|
1091
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id(), "service": service}
|
|
1092
|
+
response = await self._aget(url, params=params)
|
|
1093
|
+
return self._handle_response(response)
|
|
1094
|
+
|
|
1095
|
+
async def publish_job_posting(
|
|
1096
|
+
self,
|
|
1097
|
+
draft_id: str,
|
|
1098
|
+
mode: Literal["FREE"] = "FREE",
|
|
1099
|
+
service: Literal["CLASSIC", "RECRUITER"] = "CLASSIC",
|
|
1100
|
+
hiring_photo_frame: bool | None = None,
|
|
1101
|
+
bypass_email_verification: bool | None = None,
|
|
1102
|
+
) -> dict[str, Any]:
|
|
1103
|
+
"""
|
|
1104
|
+
Publish the job posting draft you have been working on.
|
|
1105
|
+
|
|
1106
|
+
Args:
|
|
1107
|
+
draft_id: Required. The id of the draft to publish.
|
|
1108
|
+
mode: Required. "FREE".
|
|
1109
|
+
service: Optional. The Linkedin service the job posting depends on. Default is CLASSIC.
|
|
1110
|
+
hiring_photo_frame: Optional. Whether or not to add the hiring photo frame to you profile picture.
|
|
1111
|
+
bypass_email_verification: Optional. Whether or not to verify if you're allowed to post a job on behalf on the current company.
|
|
1112
|
+
|
|
1113
|
+
Returns:
|
|
1114
|
+
A dictionary containing the response from the API.
|
|
1115
|
+
|
|
1116
|
+
Raises:
|
|
1117
|
+
httpx.HTTPError: If the API request fails.
|
|
1118
|
+
"""
|
|
1119
|
+
url = f"{self.base_url}/api/v1/linkedin/jobs/{draft_id}/publish"
|
|
1120
|
+
payload: dict[str, Any] = {
|
|
1121
|
+
"account_id": await self._get_account_id(),
|
|
1122
|
+
"mode": mode,
|
|
1123
|
+
"service": service,
|
|
1124
|
+
}
|
|
1125
|
+
if hiring_photo_frame is not None:
|
|
1126
|
+
payload["hiring_photo_frame"] = hiring_photo_frame
|
|
1127
|
+
if bypass_email_verification is not None:
|
|
1128
|
+
payload["bypass_email_verification"] = bypass_email_verification
|
|
1129
|
+
|
|
1130
|
+
response = await self._apost(url, data=payload)
|
|
1131
|
+
return self._handle_response(response)
|
|
1132
|
+
|
|
1133
|
+
async def solve_job_publishing_checkpoint(
|
|
1134
|
+
self,
|
|
1135
|
+
draft_id: str,
|
|
1136
|
+
input: str,
|
|
1137
|
+
) -> dict[str, Any]:
|
|
1138
|
+
"""
|
|
1139
|
+
Solve a checkpoint to verify your member privileges.
|
|
1140
|
+
|
|
1141
|
+
Args:
|
|
1142
|
+
draft_id: Required. The id of the draft to solve the checkpoint from.
|
|
1143
|
+
input: Required. The code or input to solve the checkpoint.
|
|
1144
|
+
|
|
1145
|
+
Returns:
|
|
1146
|
+
A dictionary containing the response from the API.
|
|
1147
|
+
|
|
1148
|
+
Raises:
|
|
1149
|
+
httpx.HTTPError: If the API request fails.
|
|
1150
|
+
"""
|
|
1151
|
+
url = f"{self.base_url}/api/v1/linkedin/jobs/{draft_id}/checkpoint"
|
|
1152
|
+
payload: dict[str, Any] = {
|
|
1153
|
+
"account_id": await self._get_account_id(),
|
|
1154
|
+
"input": input,
|
|
1155
|
+
}
|
|
1156
|
+
response = await self._apost(url, data=payload)
|
|
1157
|
+
return self._handle_response(response)
|
|
1158
|
+
|
|
1159
|
+
async def list_job_applicants(
|
|
1160
|
+
self,
|
|
1161
|
+
job_id: str,
|
|
1162
|
+
limit: int = 100,
|
|
1163
|
+
cursor: str | None = None,
|
|
1164
|
+
service: Literal["CLASSIC", "RECRUITER"] = "CLASSIC",
|
|
1165
|
+
sort_by: Literal[
|
|
1166
|
+
"relevance", "alphabetical", "newest_first", "screening_requirements"
|
|
1167
|
+
]
|
|
1168
|
+
| None = None,
|
|
1169
|
+
keywords: str | None = None,
|
|
1170
|
+
ratings: str | None = None,
|
|
1171
|
+
min_years_in_company: float | None = None,
|
|
1172
|
+
max_years_in_company: float | None = None,
|
|
1173
|
+
min_years_in_position: float | None = None,
|
|
1174
|
+
max_years_in_position: float | None = None,
|
|
1175
|
+
min_years_of_experience: float | None = None,
|
|
1176
|
+
max_years_of_experience: float | None = None,
|
|
1177
|
+
) -> dict[str, Any]:
|
|
1178
|
+
"""
|
|
1179
|
+
Retrieve all the users that have applied to a given offer.
|
|
1180
|
+
|
|
1181
|
+
Args:
|
|
1182
|
+
job_id: Required. The ID of the job offer.
|
|
1183
|
+
limit: Optional. The number of results to return. Default 100.
|
|
1184
|
+
cursor: Optional. The cursor to retrieve the next page.
|
|
1185
|
+
service: Optional. The Linkedin service the job posting depends on. Default is CLASSIC.
|
|
1186
|
+
sort_by: Optional. The sorting rule for applicants. Recruiter only.
|
|
1187
|
+
keywords: Optional. Filter results with keywords.
|
|
1188
|
+
ratings: Optional. One or more ratings (UNRATED, GOOD_FIT, MAYBE, NOT_A_FIT) separated by commas.
|
|
1189
|
+
min_years_in_company: Optional. Linkedin Recruiter native filter.
|
|
1190
|
+
max_years_in_company: Optional. Linkedin Recruiter native filter.
|
|
1191
|
+
min_years_in_position: Optional. Linkedin Recruiter native filter.
|
|
1192
|
+
max_years_in_position: Optional. Linkedin Recruiter native filter.
|
|
1193
|
+
min_years_of_experience: Optional. Linkedin Recruiter native filter.
|
|
1194
|
+
max_years_of_experience: Optional. Linkedin Recruiter native filter.
|
|
1195
|
+
|
|
1196
|
+
Returns:
|
|
1197
|
+
A dictionary containing the list of applicants.
|
|
1198
|
+
|
|
1199
|
+
Raises:
|
|
1200
|
+
httpx.HTTPError: If the API request fails.
|
|
1201
|
+
"""
|
|
1202
|
+
url = f"{self.base_url}/api/v1/linkedin/jobs/{job_id}/applicants"
|
|
1203
|
+
params: dict[str, Any] = {
|
|
1204
|
+
"account_id": await self._get_account_id(),
|
|
1205
|
+
"limit": limit,
|
|
1206
|
+
"service": service,
|
|
1207
|
+
}
|
|
1208
|
+
if cursor:
|
|
1209
|
+
params["cursor"] = cursor
|
|
1210
|
+
if sort_by:
|
|
1211
|
+
params["sort_by"] = sort_by
|
|
1212
|
+
if keywords:
|
|
1213
|
+
params["keywords"] = keywords
|
|
1214
|
+
if ratings:
|
|
1215
|
+
params["ratings"] = ratings
|
|
1216
|
+
if min_years_in_company is not None:
|
|
1217
|
+
params["min_years_in_company"] = min_years_in_company
|
|
1218
|
+
if max_years_in_company is not None:
|
|
1219
|
+
params["max_years_in_company"] = max_years_in_company
|
|
1220
|
+
if min_years_in_position is not None:
|
|
1221
|
+
params["min_years_in_position"] = min_years_in_position
|
|
1222
|
+
if max_years_in_position is not None:
|
|
1223
|
+
params["max_years_in_position"] = max_years_in_position
|
|
1224
|
+
if min_years_of_experience is not None:
|
|
1225
|
+
params["min_years_of_experience"] = min_years_of_experience
|
|
1226
|
+
if max_years_of_experience is not None:
|
|
1227
|
+
params["max_years_of_experience"] = max_years_of_experience
|
|
1228
|
+
|
|
1229
|
+
response = await self._aget(url, params=params)
|
|
1230
|
+
return self._handle_response(response)
|
|
1231
|
+
|
|
1232
|
+
async def retrieve_job_applicant(
|
|
1233
|
+
self,
|
|
1234
|
+
applicant_id: str,
|
|
1235
|
+
) -> dict[str, Any]:
|
|
1236
|
+
"""
|
|
1237
|
+
Retrieve the details of a user that has applied to a given offer. Applies to Classic job posting only.
|
|
1238
|
+
|
|
1239
|
+
Args:
|
|
1240
|
+
applicant_id: Required. The ID of the applicant.
|
|
1241
|
+
|
|
1242
|
+
Returns:
|
|
1243
|
+
A dictionary containing the applicant details.
|
|
1244
|
+
|
|
1245
|
+
Raises:
|
|
1246
|
+
httpx.HTTPError: If the API request fails.
|
|
1247
|
+
"""
|
|
1248
|
+
url = f"{self.base_url}/api/v1/linkedin/jobs/applicants/{applicant_id}"
|
|
1249
|
+
params: dict[str, Any] = {"account_id": await self._get_account_id()}
|
|
1250
|
+
response = await self._aget(url, params=params)
|
|
1251
|
+
return self._handle_response(response)
|
|
1252
|
+
|
|
1253
|
+
async def download_job_applicant_resume(
|
|
1254
|
+
self,
|
|
1255
|
+
applicant_id: str,
|
|
1256
|
+
service: Literal["CLASSIC", "RECRUITER"] = "CLASSIC",
|
|
1257
|
+
) -> dict[str, Any]:
|
|
1258
|
+
"""
|
|
1259
|
+
Download the resume of a job applicant.
|
|
1260
|
+
|
|
1261
|
+
Args:
|
|
1262
|
+
applicant_id: Required. The ID of the job applicant.
|
|
1263
|
+
service: Optional. The Linkedin service the applicant depends on. Default is classic.
|
|
1264
|
+
|
|
1265
|
+
Returns:
|
|
1266
|
+
A dictionary containing the resume details (likely a download URL or binary content, depending on API response).
|
|
1267
|
+
|
|
1268
|
+
Raises:
|
|
1269
|
+
httpx.HTTPError: If the API request fails.
|
|
1270
|
+
"""
|
|
1271
|
+
url = f"{self.base_url}/api/v1/linkedin/jobs/applicants/{applicant_id}/resume"
|
|
1272
|
+
params: dict[str, Any] = {
|
|
1273
|
+
"account_id": await self._get_account_id(),
|
|
1274
|
+
"service": service,
|
|
1275
|
+
}
|
|
1276
|
+
response = await self._aget(url, params=params)
|
|
1277
|
+
return self._handle_response(response)
|
|
1278
|
+
|
|
916
1279
|
def list_tools(self) -> list[Callable]:
|
|
917
1280
|
return [
|
|
918
1281
|
self.start_new_chat,
|
|
@@ -922,6 +1285,7 @@ class LinkedinApp(APIApplication):
|
|
|
922
1285
|
self.retrieve_chat,
|
|
923
1286
|
self.list_all_messages,
|
|
924
1287
|
self.list_profile_posts,
|
|
1288
|
+
self.list_profile_comments,
|
|
925
1289
|
self.retrieve_own_profile,
|
|
926
1290
|
self.retrieve_user_profile,
|
|
927
1291
|
self.retrieve_post,
|
|
@@ -941,4 +1305,13 @@ class LinkedinApp(APIApplication):
|
|
|
941
1305
|
self.handle_received_invitation,
|
|
942
1306
|
self.list_followers,
|
|
943
1307
|
# self.list_following this endpoint is not yet implemented by unipile
|
|
1308
|
+
self.list_job_postings,
|
|
1309
|
+
self.create_job_posting,
|
|
1310
|
+
self.close_job_posting,
|
|
1311
|
+
self.retrieve_job_posting,
|
|
1312
|
+
self.publish_job_posting,
|
|
1313
|
+
self.solve_job_publishing_checkpoint,
|
|
1314
|
+
self.list_job_applicants,
|
|
1315
|
+
self.retrieve_job_applicant,
|
|
1316
|
+
self.download_job_applicant_resume,
|
|
944
1317
|
]
|