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.

Files changed (45) hide show
  1. universal_mcp/applications/BEST_PRACTICES.md +1 -1
  2. universal_mcp/applications/airtable/app.py +13 -13
  3. universal_mcp/applications/apollo/app.py +2 -2
  4. universal_mcp/applications/aws_s3/app.py +30 -19
  5. universal_mcp/applications/browser_use/app.py +10 -7
  6. universal_mcp/applications/contentful/app.py +4 -4
  7. universal_mcp/applications/crustdata/app.py +2 -2
  8. universal_mcp/applications/e2b/app.py +3 -4
  9. universal_mcp/applications/elevenlabs/README.md +27 -3
  10. universal_mcp/applications/elevenlabs/app.py +753 -48
  11. universal_mcp/applications/exa/app.py +18 -11
  12. universal_mcp/applications/falai/README.md +5 -7
  13. universal_mcp/applications/falai/app.py +160 -159
  14. universal_mcp/applications/firecrawl/app.py +14 -15
  15. universal_mcp/applications/ghost_content/app.py +4 -4
  16. universal_mcp/applications/github/app.py +2 -2
  17. universal_mcp/applications/gong/app.py +2 -2
  18. universal_mcp/applications/google_docs/README.md +15 -14
  19. universal_mcp/applications/google_docs/app.py +5 -4
  20. universal_mcp/applications/google_gemini/app.py +61 -17
  21. universal_mcp/applications/google_sheet/README.md +2 -1
  22. universal_mcp/applications/google_sheet/app.py +55 -0
  23. universal_mcp/applications/heygen/README.md +10 -32
  24. universal_mcp/applications/heygen/app.py +350 -744
  25. universal_mcp/applications/klaviyo/app.py +2 -2
  26. universal_mcp/applications/linkedin/README.md +14 -2
  27. universal_mcp/applications/linkedin/app.py +411 -38
  28. universal_mcp/applications/ms_teams/app.py +420 -1285
  29. universal_mcp/applications/notion/app.py +2 -2
  30. universal_mcp/applications/openai/app.py +1 -1
  31. universal_mcp/applications/perplexity/app.py +6 -7
  32. universal_mcp/applications/reddit/app.py +4 -4
  33. universal_mcp/applications/resend/app.py +31 -32
  34. universal_mcp/applications/rocketlane/app.py +2 -2
  35. universal_mcp/applications/scraper/app.py +51 -21
  36. universal_mcp/applications/semrush/app.py +1 -1
  37. universal_mcp/applications/serpapi/app.py +8 -7
  38. universal_mcp/applications/shopify/app.py +5 -7
  39. universal_mcp/applications/shortcut/app.py +3 -2
  40. universal_mcp/applications/slack/app.py +2 -2
  41. universal_mcp/applications/twilio/app.py +14 -13
  42. {universal_mcp_applications-0.1.39rc8.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/METADATA +1 -1
  43. {universal_mcp_applications-0.1.39rc8.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/RECORD +45 -45
  44. {universal_mcp_applications-0.1.39rc8.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/WHEEL +0 -0
  45. {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.get_credentials()`, e.g.,
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.account_id = None
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.get_credentials()
31
- if credentials:
32
- self.account_id = credentials.get("account_id")
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.account_id, "keywords": keywords, "type": param_type}
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.account_id), "text": (None, text), "attendees_ids": (None, provider_id)}
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.account_id
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.account_id:
259
- params["account_id"] = self.account_id
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.account_id:
303
- params["account_id"] = self.account_id
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, identifier: str, cursor: str | None = None, limit: int | None = None, is_company: bool | None = None
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
- identifier: The entity's provider internal ID (LinkedIn ID).
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, as per Unipile example, though spec allows up to 250).
317
- is_company: Boolean indicating if the identifier is for a company.
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/{identifier}/posts"
329
- params: dict[str, Any] = {"account_id": self.account_id}
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.account_id}
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.account_id}
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.account_id}
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.account_id, "text": text}
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, spec max 250).
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.account_id}
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.account_id, "text": text}
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.account_id, "post_id": post_social_id, "reaction_type": reaction_type}
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.account_id}
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.account_id}
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.account_id}
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.account_id}
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.account_id}
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.account_id, "provider_id": provider_id}
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.account_id}
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.account_id}
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.account_id}
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.account_id}
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.account_id}
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.account_id}
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
  ]