linkedapi 1.3.8__tar.gz → 1.3.11__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. {linkedapi-1.3.8 → linkedapi-1.3.11}/PKG-INFO +1 -1
  2. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/__init__.py +5 -1
  3. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/client.py +6 -0
  4. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/errors.py +4 -0
  5. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/__init__.py +4 -0
  6. linkedapi-1.3.11/linkedapi/operations/retrieve_profile_viewers.py +15 -0
  7. linkedapi-1.3.11/linkedapi/operations/search_posts.py +15 -0
  8. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/__init__.py +40 -0
  9. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/jobs.py +48 -1
  10. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/message.py +3 -0
  11. linkedapi-1.3.11/linkedapi/types/profile_viewers.py +82 -0
  12. linkedapi-1.3.11/linkedapi/types/search_posts.py +103 -0
  13. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/webhooks.py +3 -0
  14. {linkedapi-1.3.8 → linkedapi-1.3.11}/pyproject.toml +1 -1
  15. {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_errors.py +2 -0
  16. {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_operations.py +158 -1
  17. {linkedapi-1.3.8 → linkedapi-1.3.11}/.github/workflows/publish.yaml +0 -0
  18. {linkedapi-1.3.8 → linkedapi-1.3.11}/.gitignore +0 -0
  19. {linkedapi-1.3.8 → linkedapi-1.3.11}/LICENSE +0 -0
  20. {linkedapi-1.3.8 → linkedapi-1.3.11}/README.md +0 -0
  21. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/__init__.py +0 -0
  22. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/accounts.py +0 -0
  23. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/admin.py +0 -0
  24. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/http_client.py +0 -0
  25. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/limits.py +0 -0
  26. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/subscription.py +0 -0
  27. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/admin/webhooks.py +0 -0
  28. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/config.py +0 -0
  29. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/core/__init__.py +0 -0
  30. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/core/operation.py +0 -0
  31. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/core/polling.py +0 -0
  32. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/http/__init__.py +0 -0
  33. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/http/base.py +0 -0
  34. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/http/linked_api_http_client.py +0 -0
  35. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/__init__.py +0 -0
  36. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/array.py +0 -0
  37. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/base.py +0 -0
  38. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/send_message.py +0 -0
  39. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/simple.py +0 -0
  40. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/then.py +0 -0
  41. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/mappers/void.py +0 -0
  42. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/accept_invitation.py +0 -0
  43. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/check_connection_status.py +0 -0
  44. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/comment_on_post.py +0 -0
  45. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/create_post.py +0 -0
  46. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/custom_workflow.py +0 -0
  47. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/fetch_company.py +0 -0
  48. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/fetch_job.py +0 -0
  49. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/fetch_person.py +0 -0
  50. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/fetch_post.py +0 -0
  51. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/ignore_invitation.py +0 -0
  52. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/manage_conversation.py +0 -0
  53. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_fetch_company.py +0 -0
  54. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_fetch_person.py +0 -0
  55. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_manage_conversation.py +0 -0
  56. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_search_companies.py +0 -0
  57. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_search_people.py +0 -0
  58. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_send_message.py +0 -0
  59. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_sync_conversation.py +0 -0
  60. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/nv_sync_inbox.py +0 -0
  61. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/react_to_comment.py +0 -0
  62. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/react_to_post.py +0 -0
  63. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/remove_connection.py +0 -0
  64. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/reply_to_comment.py +0 -0
  65. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/retrieve_connections.py +0 -0
  66. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/retrieve_feed.py +0 -0
  67. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/retrieve_invitations.py +0 -0
  68. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/retrieve_pending_requests.py +0 -0
  69. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/retrieve_performance.py +0 -0
  70. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/retrieve_ssi.py +0 -0
  71. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/search_companies.py +0 -0
  72. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/search_jobs.py +0 -0
  73. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/search_people.py +0 -0
  74. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/send_connection_request.py +0 -0
  75. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/send_message.py +0 -0
  76. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/sync_conversation.py +0 -0
  77. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/sync_inbox.py +0 -0
  78. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/sync_network.py +0 -0
  79. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/operations/withdraw_connection_request.py +0 -0
  80. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/py.typed +0 -0
  81. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/account.py +0 -0
  82. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/admin/__init__.py +0 -0
  83. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/admin/accounts.py +0 -0
  84. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/admin/config.py +0 -0
  85. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/admin/limits.py +0 -0
  86. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/admin/subscription.py +0 -0
  87. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/base.py +0 -0
  88. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/company.py +0 -0
  89. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/connection.py +0 -0
  90. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/feed.py +0 -0
  91. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/network.py +0 -0
  92. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/params.py +0 -0
  93. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/person.py +0 -0
  94. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/post.py +0 -0
  95. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/responses.py +0 -0
  96. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/search_companies.py +0 -0
  97. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/search_people.py +0 -0
  98. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/statistics.py +0 -0
  99. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/types/workflow.py +0 -0
  100. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/webhooks/__init__.py +0 -0
  101. {linkedapi-1.3.8 → linkedapi-1.3.11}/linkedapi/webhooks/parse.py +0 -0
  102. {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/conftest.py +0 -0
  103. {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_admin.py +0 -0
  104. {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_direct_methods.py +0 -0
  105. {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_http_client.py +0 -0
  106. {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_mappers.py +0 -0
  107. {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_models.py +0 -0
  108. {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_polling.py +0 -0
  109. {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_webhooks.py +0 -0
  110. {linkedapi-1.3.8 → linkedapi-1.3.11}/tests/test_working_hours_contract.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: linkedapi
3
- Version: 1.3.8
3
+ Version: 1.3.11
4
4
  Summary: Official synchronous Python SDK for Linked API.
5
5
  Project-URL: Homepage, https://linkedapi.io
6
6
  Project-URL: Repository, https://github.com/Linked-API/linkedapi-python
@@ -63,10 +63,12 @@ from linkedapi.operations import (
63
63
  RetrieveInvitations,
64
64
  RetrievePendingRequests,
65
65
  RetrievePerformance,
66
+ RetrieveProfileViewers,
66
67
  RetrieveSSI,
67
68
  SearchCompanies,
68
69
  SearchJobs,
69
70
  SearchPeople,
71
+ SearchPosts,
70
72
  SendConnectionRequest,
71
73
  SendMessage,
72
74
  SyncConversation,
@@ -78,7 +80,7 @@ from linkedapi.types import * # noqa: F403
78
80
  from linkedapi.types import __all__ as _types_all
79
81
  from linkedapi.webhooks import parse_webhook_event
80
82
 
81
- __version__ = "1.3.8"
83
+ __version__ = "1.3.11"
82
84
  PredefinedOperation = Operation
83
85
 
84
86
  __all__ = [
@@ -140,10 +142,12 @@ __all__ = [
140
142
  "RetrieveFeed",
141
143
  "RetrievePendingRequests",
142
144
  "RetrievePerformance",
145
+ "RetrieveProfileViewers",
143
146
  "RetrieveSSI",
144
147
  "SearchCompanies",
145
148
  "SearchJobs",
146
149
  "SearchPeople",
150
+ "SearchPosts",
147
151
  "SendConnectionRequest",
148
152
  "SendMessage",
149
153
  "SimpleWorkflowMapper",
@@ -36,10 +36,12 @@ from linkedapi.operations import (
36
36
  RetrieveInvitations,
37
37
  RetrievePendingRequests,
38
38
  RetrievePerformance,
39
+ RetrieveProfileViewers,
39
40
  RetrieveSSI,
40
41
  SearchCompanies,
41
42
  SearchJobs,
42
43
  SearchPeople,
44
+ SearchPosts,
43
45
  SendConnectionRequest,
44
46
  SendMessage,
45
47
  SyncConversation,
@@ -86,6 +88,7 @@ class LinkedApi:
86
88
  self.search_companies = SearchCompanies(self.http_client)
87
89
  self.search_people = SearchPeople(self.http_client)
88
90
  self.search_jobs = SearchJobs(self.http_client)
91
+ self.search_posts = SearchPosts(self.http_client)
89
92
  self.fetch_company = FetchCompany(self.http_client)
90
93
  self.fetch_person = FetchPerson(self.http_client)
91
94
  self.fetch_post = FetchPost(self.http_client)
@@ -98,6 +101,7 @@ class LinkedApi:
98
101
  self.retrieve_feed = RetrieveFeed(self.http_client)
99
102
  self.retrieve_ssi = RetrieveSSI(self.http_client)
100
103
  self.retrieve_performance = RetrievePerformance(self.http_client)
104
+ self.retrieve_profile_viewers = RetrieveProfileViewers(self.http_client)
101
105
  self.nv_send_message = NvSendMessage(self.http_client)
102
106
  self.nv_sync_conversation = NvSyncConversation(self.http_client)
103
107
  self.nv_sync_inbox = NvSyncInbox(self.http_client)
@@ -126,6 +130,7 @@ class LinkedApi:
126
130
  self.search_companies,
127
131
  self.search_people,
128
132
  self.search_jobs,
133
+ self.search_posts,
129
134
  self.fetch_company,
130
135
  self.fetch_person,
131
136
  self.fetch_post,
@@ -138,6 +143,7 @@ class LinkedApi:
138
143
  self.retrieve_feed,
139
144
  self.retrieve_ssi,
140
145
  self.retrieve_performance,
146
+ self.retrieve_profile_viewers,
141
147
  self.nv_send_message,
142
148
  self.nv_sync_conversation,
143
149
  self.nv_sync_inbox,
@@ -16,6 +16,8 @@ LinkedApiActionErrorType = Literal[
16
16
  "retrievingNotAllowed",
17
17
  "connectionNotFound",
18
18
  "searchingNotAllowed",
19
+ "searchInterfaceMismatch",
20
+ "filterIdentityMismatch",
19
21
  "companyNotFound",
20
22
  "postNotFound",
21
23
  "jobNotFound",
@@ -41,6 +43,8 @@ LINKED_API_ACTION_ERROR_TYPES: tuple[str, ...] = (
41
43
  "retrievingNotAllowed",
42
44
  "connectionNotFound",
43
45
  "searchingNotAllowed",
46
+ "searchInterfaceMismatch",
47
+ "filterIdentityMismatch",
44
48
  "companyNotFound",
45
49
  "postNotFound",
46
50
  "jobNotFound",
@@ -26,10 +26,12 @@ from linkedapi.operations.retrieve_feed import RetrieveFeed
26
26
  from linkedapi.operations.retrieve_invitations import RetrieveInvitations
27
27
  from linkedapi.operations.retrieve_pending_requests import RetrievePendingRequests
28
28
  from linkedapi.operations.retrieve_performance import RetrievePerformance
29
+ from linkedapi.operations.retrieve_profile_viewers import RetrieveProfileViewers
29
30
  from linkedapi.operations.retrieve_ssi import RetrieveSSI
30
31
  from linkedapi.operations.search_companies import SearchCompanies
31
32
  from linkedapi.operations.search_jobs import SearchJobs
32
33
  from linkedapi.operations.search_people import SearchPeople
34
+ from linkedapi.operations.search_posts import SearchPosts
33
35
  from linkedapi.operations.send_connection_request import SendConnectionRequest
34
36
  from linkedapi.operations.send_message import SendMessage
35
37
  from linkedapi.operations.sync_conversation import SyncConversation
@@ -72,10 +74,12 @@ __all__ = [
72
74
  "RetrieveInvitations",
73
75
  "RetrievePendingRequests",
74
76
  "RetrievePerformance",
77
+ "RetrieveProfileViewers",
75
78
  "RetrieveSSI",
76
79
  "SearchCompanies",
77
80
  "SearchJobs",
78
81
  "SearchPeople",
82
+ "SearchPosts",
79
83
  "SendConnectionRequest",
80
84
  "SendMessage",
81
85
  "SyncConversation",
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from linkedapi.core import Operation
4
+ from linkedapi.mappers import ArrayWorkflowMapper
5
+ from linkedapi.types import ProfileViewer, RetrieveProfileViewersParams
6
+
7
+
8
+ class RetrieveProfileViewers(Operation[RetrieveProfileViewersParams, list[ProfileViewer]]):
9
+ """Retrieve the viewers visible on the current account's LinkedIn profile."""
10
+
11
+ operation_name = "retrieveProfileViewers"
12
+ mapper = ArrayWorkflowMapper[RetrieveProfileViewersParams, ProfileViewer](
13
+ "st.retrieveProfileViewers",
14
+ ProfileViewer,
15
+ )
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from linkedapi.core import Operation
4
+ from linkedapi.mappers import ArrayWorkflowMapper
5
+ from linkedapi.types import SearchPostResult, SearchPostsParams
6
+
7
+
8
+ class SearchPosts(Operation[SearchPostsParams, list[SearchPostResult]]):
9
+ """Search posts on standard LinkedIn."""
10
+
11
+ operation_name = "searchPosts"
12
+ mapper = ArrayWorkflowMapper[SearchPostsParams, SearchPostResult](
13
+ "st.searchPosts",
14
+ SearchPostResult,
15
+ )
@@ -96,12 +96,15 @@ from linkedapi.types.jobs import (
96
96
  JobDatePosted,
97
97
  JobEmploymentType,
98
98
  JobExperienceLevel,
99
+ JobPreferenceEmploymentType,
100
+ JobPreferenceExperienceLevel,
99
101
  JobSalary,
100
102
  JobWorkplaceType,
101
103
  SalaryPeriod,
102
104
  SearchJobResult,
103
105
  SearchJobsFilter,
104
106
  SearchJobsParams,
107
+ SearchJobsPreferences,
105
108
  )
106
109
  from linkedapi.types.message import (
107
110
  ConversationPollRequest,
@@ -177,6 +180,11 @@ from linkedapi.types.post import (
177
180
  ReplyToCommentParams,
178
181
  ReplyToCommentResult,
179
182
  )
183
+ from linkedapi.types.profile_viewers import (
184
+ ProfileViewer,
185
+ ProfileViewerType,
186
+ RetrieveProfileViewersParams,
187
+ )
180
188
  from linkedapi.types.responses import LinkedApiRequestError, LinkedApiResponse
181
189
  from linkedapi.types.search_companies import (
182
190
  AnnualRevenueFilter,
@@ -198,6 +206,20 @@ from linkedapi.types.search_people import (
198
206
  SearchPeopleParams,
199
207
  SearchPeopleResult,
200
208
  )
209
+ from linkedapi.types.search_posts import (
210
+ PostSearchContentType,
211
+ PostSearchDatePosted,
212
+ PostSearchPostedBy,
213
+ PostSearchSort,
214
+ SearchPostActor,
215
+ SearchPostResult,
216
+ SearchPostsCompanyFilter,
217
+ SearchPostsCompanyFilterEntry,
218
+ SearchPostsFilter,
219
+ SearchPostsParams,
220
+ SearchPostsPersonFilter,
221
+ SearchPostsPersonFilterEntry,
222
+ )
201
223
  from linkedapi.types.statistics import (
202
224
  ApiUsageAction,
203
225
  ApiUsageParams,
@@ -315,6 +337,8 @@ __all__ = [
315
337
  "JobDatePosted",
316
338
  "JobEmploymentType",
317
339
  "JobExperienceLevel",
340
+ "JobPreferenceEmploymentType",
341
+ "JobPreferenceExperienceLevel",
318
342
  "JobSalary",
319
343
  "JobWorkplaceType",
320
344
  "LanguageProficiency",
@@ -382,7 +406,13 @@ __all__ = [
382
406
  "PostReaction",
383
407
  "PostReactionsRetrievalConfig",
384
408
  "PostReposter",
409
+ "PostSearchContentType",
410
+ "PostSearchDatePosted",
411
+ "PostSearchPostedBy",
412
+ "PostSearchSort",
385
413
  "PostType",
414
+ "ProfileViewer",
415
+ "ProfileViewerType",
386
416
  "ReactToCommentParams",
387
417
  "ReactToPostParams",
388
418
  "Reaction",
@@ -402,6 +432,7 @@ __all__ = [
402
432
  "RetrieveFeedParams",
403
433
  "RetrievePendingRequestsResult",
404
434
  "RetrievePerformanceResult",
435
+ "RetrieveProfileViewersParams",
405
436
  "RetrieveSSIResult",
406
437
  "SalaryPeriod",
407
438
  "SearchCompaniesFilter",
@@ -411,9 +442,18 @@ __all__ = [
411
442
  "SearchJobResult",
412
443
  "SearchJobsFilter",
413
444
  "SearchJobsParams",
445
+ "SearchJobsPreferences",
414
446
  "SearchPeopleFilter",
415
447
  "SearchPeopleParams",
416
448
  "SearchPeopleResult",
449
+ "SearchPostActor",
450
+ "SearchPostResult",
451
+ "SearchPostsCompanyFilter",
452
+ "SearchPostsCompanyFilterEntry",
453
+ "SearchPostsFilter",
454
+ "SearchPostsParams",
455
+ "SearchPostsPersonFilter",
456
+ "SearchPostsPersonFilterEntry",
417
457
  "SeatType",
418
458
  "SendConnectionRequestParams",
419
459
  "SendMessageManageConversation",
@@ -24,6 +24,24 @@ JobEmploymentType = Literal[
24
24
  "other",
25
25
  ]
26
26
  JobWorkplaceType = Literal["onSite", "remote", "hybrid"]
27
+ # Experience levels of LinkedIn's AI-powered jobs search. Not a subset of JobExperienceLevel:
28
+ # "senior" corresponds to "midSeniorLevel", "manager" has no classic equivalent, and "internship"
29
+ # and "associate" are absent here.
30
+ JobPreferenceExperienceLevel = Literal[
31
+ "entryLevel",
32
+ "senior",
33
+ "manager",
34
+ "director",
35
+ "executive",
36
+ ]
37
+ # Same names as JobEmploymentType, minus "temporary" and "other".
38
+ JobPreferenceEmploymentType = Literal[
39
+ "fullTime",
40
+ "partTime",
41
+ "contract",
42
+ "internship",
43
+ "volunteer",
44
+ ]
27
45
  SalaryPeriod = Literal["yearly", "monthly", "hourly"]
28
46
  JobCurrency = Literal[
29
47
  "usd",
@@ -74,6 +92,12 @@ class JobSalary(LinkedApiModel):
74
92
 
75
93
 
76
94
  class SearchJobsFilter(LinkedApiModel):
95
+ """Filtering criteria for the classic LinkedIn jobs search.
96
+
97
+ Every specified field is applied, or the action fails.
98
+ """
99
+
100
+ # Deprecated: use the top-level "location" of SearchJobsParams instead.
77
101
  location: str | None = None
78
102
  date_posted: JobDatePosted | None = None
79
103
  experience_levels: list[JobExperienceLevel] | None = None
@@ -89,9 +113,32 @@ class SearchJobsFilter(LinkedApiModel):
89
113
  fair_chance_employer: bool | None = None
90
114
 
91
115
 
116
+ class SearchJobsPreferences(LinkedApiModel):
117
+ """Filtering criteria for LinkedIn's AI-powered jobs search.
118
+
119
+ LinkedIn decides which of them it offers for a given search, and the ones it does not offer are
120
+ skipped instead of failing the action.
121
+ """
122
+
123
+ date_posted: JobDatePosted | None = None
124
+ experience_levels: list[JobPreferenceExperienceLevel] | None = None
125
+ employment_types: list[JobPreferenceEmploymentType] | None = None
126
+ companies: list[str] | None = None
127
+ remote: bool | None = None
128
+ easy_apply: bool | None = None
129
+ under_10_applicants: bool | None = None
130
+ in_your_network: bool | None = None
131
+ keywords: list[str] | None = None
132
+
133
+
92
134
  class SearchJobsParams(BaseActionParams, LimitParams):
93
135
  term: str | None = None
136
+ location: str | None = None
137
+ allow_similar_results: bool | None = None
138
+ # "filter" and "preferences" are mutually exclusive: "filter" targets the classic LinkedIn jobs
139
+ # search, "preferences" the AI-powered one.
94
140
  filter: SearchJobsFilter | None = None
141
+ preferences: SearchJobsPreferences | None = None
95
142
  custom_search_url: str | None = None
96
143
 
97
144
 
@@ -106,6 +153,7 @@ class SearchJobResult(LinkedApiModel):
106
153
  salary: JobSalary | None = None
107
154
  easy_apply: bool | None = None
108
155
  is_promoted: bool | None = None
156
+ is_similar_match: bool | None = None
109
157
 
110
158
 
111
159
  class FetchJobParams(BaseActionParams):
@@ -118,7 +166,6 @@ class Job(LinkedApiModel):
118
166
  job_url: str | None = None
119
167
  title: str | None = None
120
168
  company_name: str | None = None
121
- company_urn: str | None = None
122
169
  company_url: str | None = None
123
170
  location: str | None = None
124
171
  posted_date: str | None = None
@@ -97,6 +97,9 @@ class InboxMessage(LinkedApiModel):
97
97
  type: ConversationType | None = None
98
98
  thread_id: str | None = None
99
99
  person_urn: str | None = None
100
+ person_hashed_url: str | None = None
101
+ person_public_url: str | None = None
102
+ # Deprecated: use person_hashed_url. Still returned by the API for compatibility.
100
103
  person_url: str | None = None
101
104
  sender: MessageSender | None = None
102
105
  text: str | None = None
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import Field, model_validator
6
+
7
+ from linkedapi.types.base import LinkedApiModel
8
+ from linkedapi.types.params import BaseActionParams
9
+
10
+ ProfileViewerType = Literal["identified", "anonymous"]
11
+
12
+
13
+ class RetrieveProfileViewersParams(BaseActionParams):
14
+ limit: int | None = Field(default=None, ge=1, le=300)
15
+ since: str | None = None
16
+
17
+
18
+ class ProfileViewer(LinkedApiModel):
19
+ viewer_type: ProfileViewerType
20
+ viewed_at: str | None
21
+ viewed_ago: str | None
22
+ name: str | None = None
23
+ public_url: str | None = None
24
+ urn: str | None = None
25
+ headline: str | None = None
26
+ connection_degree: Literal[1, 2, 3] | None = None
27
+ avatar_url: str | None = None
28
+ description: str | None = None
29
+ search_url: str | None = None
30
+
31
+ @model_validator(mode="after")
32
+ def validate_type_fields(self) -> ProfileViewer:
33
+ type_fields = {
34
+ "name",
35
+ "public_url",
36
+ "urn",
37
+ "headline",
38
+ "connection_degree",
39
+ "avatar_url",
40
+ "description",
41
+ "search_url",
42
+ }
43
+ required_fields = {
44
+ "identified": {
45
+ "name",
46
+ "public_url",
47
+ "urn",
48
+ "headline",
49
+ "connection_degree",
50
+ "avatar_url",
51
+ },
52
+ "anonymous": {"description", "search_url"},
53
+ }[self.viewer_type]
54
+ missing_fields = required_fields - self.model_fields_set
55
+ if missing_fields:
56
+ msg = (
57
+ f"{self.viewer_type} profile viewer is missing required fields: "
58
+ f"{', '.join(sorted(missing_fields))}"
59
+ )
60
+ raise ValueError(msg)
61
+
62
+ unexpected_fields = (self.model_fields_set & type_fields) - required_fields
63
+ if unexpected_fields:
64
+ msg = (
65
+ f"{self.viewer_type} profile viewer contains fields for another type: "
66
+ f"{', '.join(sorted(unexpected_fields))}"
67
+ )
68
+ raise ValueError(msg)
69
+
70
+ required_values = {
71
+ "identified": {"name": self.name, "public_url": self.public_url},
72
+ "anonymous": {"description": self.description, "search_url": self.search_url},
73
+ }[self.viewer_type]
74
+ null_fields = [name for name, value in required_values.items() if value is None]
75
+ if null_fields:
76
+ msg = (
77
+ f"{self.viewer_type} profile viewer requires non-null fields: "
78
+ f"{', '.join(sorted(null_fields))}"
79
+ )
80
+ raise ValueError(msg)
81
+
82
+ return self
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Literal, TypeAlias
4
+
5
+ from linkedapi.types.base import LinkedApiModel
6
+ from linkedapi.types.params import BaseActionParams, LimitParams
7
+ from linkedapi.types.post import PostActorType, PostType
8
+
9
+ PostSearchSort = Literal["topMatch", "latest"]
10
+ PostSearchDatePosted = Literal["past24Hours", "pastWeek", "pastMonth"]
11
+ PostSearchContentType = Literal["videos", "images", "jobPosts", "liveVideos", "documents"]
12
+ PostSearchPostedBy = Literal["me", "firstConnections", "peopleYouFollow"]
13
+
14
+
15
+ class SearchPostsPersonFilter(LinkedApiModel):
16
+ """One person to narrow a post search by.
17
+
18
+ "name" is always required because LinkedIn's filter panel accepts nothing but typed text. The
19
+ optional identifier decides which of the offered namesakes is taken; with "name" alone the pick
20
+ is whichever suggestion LinkedIn ranked first.
21
+ """
22
+
23
+ name: str
24
+ urn: str | None = None
25
+ person_hashed_url: str | None = None
26
+
27
+
28
+ class SearchPostsCompanyFilter(LinkedApiModel):
29
+ """One company to narrow a post search by. Same contract as SearchPostsPersonFilter."""
30
+
31
+ name: str
32
+ urn: str | None = None
33
+ company_hashed_url: str | None = None
34
+
35
+
36
+ # A plain string is shorthand for the name alone: "Bill Gates" means {"name": "Bill Gates"}.
37
+ # Both forms may be mixed in one list.
38
+ SearchPostsPersonFilterEntry: TypeAlias = SearchPostsPersonFilter | str
39
+ SearchPostsCompanyFilterEntry: TypeAlias = SearchPostsCompanyFilter | str
40
+
41
+
42
+ class SearchPostsFilter(LinkedApiModel):
43
+ """Filtering criteria for the LinkedIn content search.
44
+
45
+ Every specified field is applied, or the action fails. Ignored entirely when
46
+ "custom_search_url" is specified.
47
+ """
48
+
49
+ sort: PostSearchSort | None = None
50
+ date_posted: PostSearchDatePosted | None = None
51
+ content_type: PostSearchContentType | None = None
52
+ posted_by: list[PostSearchPostedBy] | None = None
53
+ from_members: list[SearchPostsPersonFilterEntry] | None = None
54
+ from_companies: list[SearchPostsCompanyFilterEntry] | None = None
55
+ mentioning_members: list[SearchPostsPersonFilterEntry] | None = None
56
+ mentioning_companies: list[SearchPostsCompanyFilterEntry] | None = None
57
+ author_companies: list[SearchPostsCompanyFilterEntry] | None = None
58
+ author_industries: list[str] | None = None
59
+
60
+
61
+ class SearchPostsParams(BaseActionParams, LimitParams):
62
+ # Either "term" or "custom_search_url" must be provided. "limit" defaults to 10 and may go up
63
+ # to 100, or up to 20 when child actions are attached.
64
+ term: str | None = None
65
+ filter: SearchPostsFilter | None = None
66
+ custom_search_url: str | None = None
67
+
68
+
69
+ class SearchPostActor(LinkedApiModel):
70
+ """Whoever a search result attributes content to: its author, or the actor that reshared it.
71
+
72
+ Unlike the actors of a post returned by fetch_post, these carry no "urn": the URN is read from
73
+ identity anchors on the post's own page, and a search result list has none of them.
74
+ """
75
+
76
+ type: PostActorType | None = None
77
+ name: str | None = None
78
+ profile_url: str | None = None
79
+ headline: str | None = None
80
+ company_url: str | None = None
81
+
82
+
83
+ class SearchPostResult(LinkedApiModel):
84
+ url: str | None = None
85
+ activity_urn: str | None = None
86
+ time: str | None = None
87
+ type: PostType | None = None
88
+ author: SearchPostActor | None = None
89
+ # Non-null only when "type" is "repost".
90
+ reposter: SearchPostActor | None = None
91
+ text: str | None = None
92
+ repost_text: str | None = None
93
+ hashtags: list[str] | None = None
94
+ mentions: list[str] | None = None
95
+ external_links: list[str] | None = None
96
+ images: list[str] | None = None
97
+ document_slides: list[str] | None = None
98
+ has_video: bool | None = None
99
+ video_thumbnail: str | None = None
100
+ has_poll: bool | None = None
101
+ reactions_count: int | None = None
102
+ comments_count: int | None = None
103
+ reposts_count: int | None = None
@@ -102,6 +102,9 @@ class MessageWebhookEventData(LinkedApiModel):
102
102
  type: ConversationType | None = None
103
103
  thread_id: str | None = None
104
104
  person_urn: str | None = None
105
+ person_hashed_url: str | None = None
106
+ person_public_url: str | None = None
107
+ # Deprecated: use person_hashed_url. Still returned by the API for compatibility.
105
108
  person_url: str | None = None
106
109
  message_id: str | None = None
107
110
  sender: MessageSender | None = None
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "linkedapi"
7
- version = "1.3.8"
7
+ version = "1.3.11"
8
8
  description = "Official synchronous Python SDK for Linked API."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -45,6 +45,8 @@ def test_error_type_sets_match_node_contract() -> None:
45
45
  "retrievingNotAllowed",
46
46
  "connectionNotFound",
47
47
  "searchingNotAllowed",
48
+ "searchInterfaceMismatch",
49
+ "filterIdentityMismatch",
48
50
  "companyNotFound",
49
51
  "postNotFound",
50
52
  "jobNotFound",
@@ -13,6 +13,12 @@ from linkedapi import (
13
13
  LinkedApi,
14
14
  LinkedApiConfig,
15
15
  RetrieveFeedParams,
16
+ SearchPostActor,
17
+ SearchPostResult,
18
+ SearchPostsCompanyFilter,
19
+ SearchPostsFilter,
20
+ SearchPostsParams,
21
+ SearchPostsPersonFilter,
16
22
  SimpleWorkflowMapper,
17
23
  VoidWorkflowMapper,
18
24
  )
@@ -33,6 +39,7 @@ def test_linked_api_exposes_all_predefined_operations() -> None:
33
39
  "search_companies",
34
40
  "nv_search_companies",
35
41
  "search_jobs",
42
+ "search_posts",
36
43
  "send_connection_request",
37
44
  "check_connection_status",
38
45
  "withdraw_connection_request",
@@ -59,6 +66,7 @@ def test_linked_api_exposes_all_predefined_operations() -> None:
59
66
  "create_post",
60
67
  "retrieve_ssi",
61
68
  "retrieve_performance",
69
+ "retrieve_profile_viewers",
62
70
  ]
63
71
 
64
72
  for name in operation_names:
@@ -66,7 +74,7 @@ def test_linked_api_exposes_all_predefined_operations() -> None:
66
74
  assert hasattr(operation, "execute")
67
75
  assert hasattr(operation, "result")
68
76
  assert hasattr(operation, "cancel")
69
- assert len(linkedapi.operations) == 38
77
+ assert len(linkedapi.operations) == 40
70
78
 
71
79
 
72
80
  def test_operation_mappers_match_node_contract() -> None:
@@ -78,6 +86,8 @@ def test_operation_mappers_match_node_contract() -> None:
78
86
  assert linkedapi.search_people.mapper.base_action_type == "st.searchPeople"
79
87
  assert isinstance(linkedapi.search_jobs.mapper, ArrayWorkflowMapper)
80
88
  assert linkedapi.search_jobs.mapper.base_action_type == "st.searchJobs"
89
+ assert isinstance(linkedapi.search_posts.mapper, ArrayWorkflowMapper)
90
+ assert linkedapi.search_posts.mapper.base_action_type == "st.searchPosts"
81
91
  assert linkedapi.fetch_job.mapper.base_action_type == "st.openJob"
82
92
  assert linkedapi.fetch_job.mapper.default_params == {"basicInfo": True}
83
93
  assert isinstance(linkedapi.send_connection_request.mapper, VoidWorkflowMapper)
@@ -133,6 +143,153 @@ def test_retrieve_feed_maps_params_and_feed_context() -> None:
133
143
  RetrieveFeedParams(limit=invalid_limit)
134
144
 
135
145
 
146
+ def test_search_posts_maps_filter_params_and_actors_without_urn() -> None:
147
+ linkedapi = LinkedApi(LinkedApiConfig(linked_api_token="x", identification_token="y"))
148
+
149
+ request = linkedapi.search_posts.mapper.map_request(
150
+ SearchPostsParams(
151
+ term="climate tech",
152
+ limit=20,
153
+ filter={
154
+ "sort": "latest",
155
+ "date_posted": "pastWeek",
156
+ "content_type": "images",
157
+ "posted_by": ["firstConnections", "peopleYouFollow"],
158
+ "from_members": [{"name": "Bill Gates", "urn": "urn:li:member:251749025"}],
159
+ "from_companies": ["Example Company"],
160
+ "mentioning_members": [],
161
+ "mentioning_companies": [],
162
+ "author_companies": [
163
+ {"name": "Example Company", "urn": "urn:li:organization:1234567"}
164
+ ],
165
+ "author_industries": ["Software Development"],
166
+ },
167
+ custom_search_url=(
168
+ "https://www.linkedin.com/search/results/content/?keywords=climate%20tech"
169
+ ),
170
+ )
171
+ )
172
+
173
+ assert request == {
174
+ "actionType": "st.searchPosts",
175
+ "term": "climate tech",
176
+ "limit": 20,
177
+ "filter": {
178
+ "sort": "latest",
179
+ "datePosted": "pastWeek",
180
+ "contentType": "images",
181
+ "postedBy": ["firstConnections", "peopleYouFollow"],
182
+ "fromMembers": [{"name": "Bill Gates", "urn": "urn:li:member:251749025"}],
183
+ "fromCompanies": ["Example Company"],
184
+ "mentioningMembers": [],
185
+ "mentioningCompanies": [],
186
+ "authorCompanies": [{"name": "Example Company", "urn": "urn:li:organization:1234567"}],
187
+ "authorIndustries": ["Software Development"],
188
+ },
189
+ "customSearchUrl": "https://www.linkedin.com/search/results/content/?keywords=climate%20tech",
190
+ }
191
+
192
+ response = linkedapi.search_posts.mapper.map_response(
193
+ {
194
+ "actionType": "st.searchPosts",
195
+ "success": True,
196
+ "data": [
197
+ {
198
+ "url": "https://www.linkedin.com/feed/update/urn:li:activity:1",
199
+ "activityUrn": "urn:li:activity:1",
200
+ "time": "2023-01-01T09:15:00Z",
201
+ "type": "repost",
202
+ "author": {
203
+ "type": "company",
204
+ "name": "Example Company",
205
+ "companyUrl": "https://www.linkedin.com/company/example-company",
206
+ },
207
+ "reposter": {
208
+ "type": "person",
209
+ "name": "Example Reposter",
210
+ "profileUrl": "https://www.linkedin.com/in/example-reposter",
211
+ "headline": None,
212
+ },
213
+ "text": "Original post content about the webinar.",
214
+ "repostText": "A useful summary for anyone planning a launch.",
215
+ "hashtags": [],
216
+ "mentions": [],
217
+ "externalLinks": [],
218
+ "images": [],
219
+ "documentSlides": [],
220
+ "hasVideo": True,
221
+ "videoThumbnail": "https://media.licdn.com/dms/image/video-cover.jpg",
222
+ "hasPoll": False,
223
+ "reactionsCount": 6,
224
+ "commentsCount": 0,
225
+ "repostsCount": 1,
226
+ }
227
+ ],
228
+ }
229
+ )
230
+
231
+ assert response.data is not None
232
+ post = response.data[0]
233
+ assert isinstance(post, SearchPostResult)
234
+ assert post.activity_urn == "urn:li:activity:1"
235
+ assert post.author is not None
236
+ assert post.author.company_url == "https://www.linkedin.com/company/example-company"
237
+ assert post.reposter is not None
238
+ assert post.reposter.profile_url == "https://www.linkedin.com/in/example-reposter"
239
+ assert post.video_thumbnail == "https://media.licdn.com/dms/image/video-cover.jpg"
240
+ assert post.reposts_count == 1
241
+ # A search result never resolves an actor urn, so the field must not exist on this surface.
242
+ assert "urn" not in SearchPostActor.model_fields
243
+
244
+
245
+ def test_search_posts_actor_filters_accept_bare_strings_and_objects_mixed() -> None:
246
+ params = SearchPostsParams(
247
+ term="ai",
248
+ filter=SearchPostsFilter(
249
+ from_members=[
250
+ "Bill Gates",
251
+ SearchPostsPersonFilter(
252
+ name="Satya Nadella",
253
+ person_hashed_url="https://www.linkedin.com/in/ACoAAABC",
254
+ ),
255
+ ],
256
+ from_companies=[
257
+ "Acme",
258
+ SearchPostsCompanyFilter(
259
+ name="Globex",
260
+ company_hashed_url="https://www.linkedin.com/company/1035",
261
+ ),
262
+ ],
263
+ ),
264
+ )
265
+
266
+ assert params.model_dump(by_alias=True, exclude_none=True) == {
267
+ "term": "ai",
268
+ "filter": {
269
+ "fromMembers": [
270
+ "Bill Gates",
271
+ {
272
+ "name": "Satya Nadella",
273
+ "personHashedUrl": "https://www.linkedin.com/in/ACoAAABC",
274
+ },
275
+ ],
276
+ "fromCompanies": [
277
+ "Acme",
278
+ {
279
+ "name": "Globex",
280
+ "companyHashedUrl": "https://www.linkedin.com/company/1035",
281
+ },
282
+ ],
283
+ },
284
+ }
285
+
286
+ with pytest.raises(ValidationError):
287
+ SearchPostsFilter(sort="mostRecent")
288
+
289
+ with pytest.raises(ValidationError):
290
+ SearchPostsFilter(from_members=[{"urn": "urn:li:member:1"}])
291
+
292
+
136
293
  def test_invitation_params_require_the_matching_target_url() -> None:
137
294
  params = AcceptInvitationParams(
138
295
  invitation_type="companyFollow",
File without changes
File without changes
File without changes
File without changes
File without changes