socialscrape-sdk 0.1.0__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 (27) hide show
  1. socialscrape_sdk-0.1.0/LICENSE +21 -0
  2. socialscrape_sdk-0.1.0/MANIFEST.in +2 -0
  3. socialscrape_sdk-0.1.0/PKG-INFO +246 -0
  4. socialscrape_sdk-0.1.0/README.md +230 -0
  5. socialscrape_sdk-0.1.0/pyproject.toml +38 -0
  6. socialscrape_sdk-0.1.0/setup.cfg +4 -0
  7. socialscrape_sdk-0.1.0/src/socialscrape/__init__.py +32 -0
  8. socialscrape_sdk-0.1.0/src/socialscrape/_base_client.py +488 -0
  9. socialscrape_sdk-0.1.0/src/socialscrape/_batch.py +80 -0
  10. socialscrape_sdk-0.1.0/src/socialscrape/_errors_base.py +89 -0
  11. socialscrape_sdk-0.1.0/src/socialscrape/_generated/__init__.py +3 -0
  12. socialscrape_sdk-0.1.0/src/socialscrape/_generated/endpoints.py +219 -0
  13. socialscrape_sdk-0.1.0/src/socialscrape/_generated/errors.py +182 -0
  14. socialscrape_sdk-0.1.0/src/socialscrape/_generated/models.py +617 -0
  15. socialscrape_sdk-0.1.0/src/socialscrape/_generated/resources.py +14103 -0
  16. socialscrape_sdk-0.1.0/src/socialscrape/_pagination.py +87 -0
  17. socialscrape_sdk-0.1.0/src/socialscrape/_response.py +55 -0
  18. socialscrape_sdk-0.1.0/src/socialscrape/_version.py +1 -0
  19. socialscrape_sdk-0.1.0/src/socialscrape/client.py +67 -0
  20. socialscrape_sdk-0.1.0/src/socialscrape/errors.py +23 -0
  21. socialscrape_sdk-0.1.0/src/socialscrape/models.py +3 -0
  22. socialscrape_sdk-0.1.0/src/socialscrape/py.typed +0 -0
  23. socialscrape_sdk-0.1.0/src/socialscrape_sdk.egg-info/PKG-INFO +246 -0
  24. socialscrape_sdk-0.1.0/src/socialscrape_sdk.egg-info/SOURCES.txt +25 -0
  25. socialscrape_sdk-0.1.0/src/socialscrape_sdk.egg-info/dependency_links.txt +1 -0
  26. socialscrape_sdk-0.1.0/src/socialscrape_sdk.egg-info/requires.txt +1 -0
  27. socialscrape_sdk-0.1.0/src/socialscrape_sdk.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The Casey Company LLC (SocialScrape)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ # Tests need the repository's ss_* packages and fixtures, so they cannot run from an sdist.
2
+ prune tests
@@ -0,0 +1,246 @@
1
+ Metadata-Version: 2.4
2
+ Name: socialscrape-sdk
3
+ Version: 0.1.0
4
+ Summary: SocialScrape API client: social media data (profiles, posts, comments, search, ads, transcripts) in one response shape.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://socialscrape.dev
7
+ Project-URL: Documentation, https://socialscrape.dev/docs
8
+ Keywords: socialscrape,social media,api,tiktok,instagram,youtube,transcripts
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Typing :: Typed
11
+ Requires-Python: >=3.11
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: httpx<1,>=0.27
15
+ Dynamic: license-file
16
+
17
+ # socialscrape (Python)
18
+
19
+ Social media data as an API: profiles, posts, comments, search, ads and
20
+ transcripts across platforms, in one response shape. This is the official Python
21
+ client for the [SocialScrape API](https://socialscrape.dev/docs).
22
+
23
+ - Sync (`SocialScrape`) and asyncio (`AsyncSocialScrape`) clients on `httpx`.
24
+ - Typed methods per endpoint, grouped by platform; responses typed with `TypedDict`s.
25
+ - Typed exceptions per error code, retries with backoff and `Retry-After`, timeouts.
26
+ - `credits_charged` and `credits_remaining` on every response.
27
+ - Cursor auto-pagination, long-job polling, batch calls.
28
+
29
+ ```sh
30
+ pip install socialscrape-sdk
31
+ ```
32
+
33
+ Python 3.11 or newer.
34
+
35
+ Your API key is a secret: keep it on your server, in an environment variable or a secrets manager, and never in client-side code, a public repository or a log.
36
+
37
+ ## Quickstart
38
+
39
+ ```sh
40
+ export SOCIALSCRAPE_API_KEY=ss_live_...
41
+ ```
42
+
43
+ ```python
44
+ from socialscrape import SocialScrape
45
+
46
+ client = SocialScrape() # reads SOCIALSCRAPE_API_KEY
47
+
48
+ res = client.tiktok.profile(handle="khaby.lame")
49
+
50
+ print(res.data["followers"]) # the normalised profile (a plain dict)
51
+ print(res.credits_charged) # 1
52
+ print(res.credits_remaining) # what is left
53
+ ```
54
+
55
+ Methods are grouped by platform and named after the endpoint, with the API's own
56
+ parameter names as keyword arguments: `client.tiktok.profile_videos(handle=...)`,
57
+ `client.instagram.post_comments(url=...)`, `client.youtube.video_transcript(url=...)`,
58
+ `client.apple_music.artist(...)`. `client.transcript(url=...)` takes a link from
59
+ any supported platform.
60
+
61
+ Async is the same, awaited:
62
+
63
+ ```python
64
+ import asyncio
65
+ from socialscrape import AsyncSocialScrape
66
+
67
+ async def main():
68
+ async with AsyncSocialScrape() as client:
69
+ res = await client.tiktok.profile(handle="khaby.lame")
70
+ print(res.data["followers"])
71
+
72
+ asyncio.run(main())
73
+ ```
74
+
75
+ ## The response
76
+
77
+ Every method returns a `Response` with the envelope fields, typed:
78
+
79
+ ```python
80
+ res.data # the endpoint's model (Profile, Post, ListPage[Post], ...)
81
+ res.credits_charged # 0 on a cache hit
82
+ res.credits_remaining # the team's balance after this call, or None
83
+ res.cached, res.cached_at
84
+ res.request_id # quote it to support
85
+ res.cursor, res.has_more # lists: copies of data["cursor"] / data["has_more"]
86
+ res.raw # only with raw=True: the platform's original object
87
+ res.http.status_code, res.http.headers, res.http.attempts
88
+ res.http.rate_limit_limit, res.http.rate_limit_remaining # both None: the api sends no x-ratelimit-* headers
89
+ res.http.ignored_params
90
+ ```
91
+
92
+ The client keeps count too: `client.credits_remaining` (last balance seen) and
93
+ `client.credits_charged_total`.
94
+
95
+ Every endpoint also accepts `trim=True` (smaller response), `raw=True` (adds the
96
+ platform's original object) and `max_age_days=7` (accept a cached result up to 7
97
+ days old for 0 credits). Models live in `socialscrape.models`.
98
+
99
+ ## Errors
100
+
101
+ Failures are free (`credits_charged` is 0) and raise a class per error code. Branch
102
+ on the class or `code`, never on the message.
103
+
104
+ ```python
105
+ from socialscrape import errors
106
+
107
+ try:
108
+ client.tiktok.profile(handle="no-such-account-123")
109
+ except errors.NotFoundError as e:
110
+ print(e.code, e.http_status, e.request_id, e.retryable) # not_found 404 req_... False
111
+ except errors.InsufficientCreditsError:
112
+ ... # top up
113
+ except errors.APIError as e:
114
+ print(e.code, e.retryable)
115
+ ```
116
+
117
+ | Class | `code` | HTTP | Retryable |
118
+ |---|---|---|---|
119
+ | `InvalidParamsError` | `invalid_params` | 400 | no |
120
+ | `UnsupportedUrlError` | `unsupported_url` | 400 | no |
121
+ | `UnauthorizedError` | `unauthorized` | 401 | no |
122
+ | `InsufficientCreditsError` | `insufficient_credits` | 402 | no |
123
+ | `ForbiddenError` | `forbidden` | 403 | no |
124
+ | `PrivateError` | `private` | 403 | no |
125
+ | `AgeRestrictedError` | `age_restricted` | 403 | no |
126
+ | `NotFoundError` | `not_found` | 404 | no |
127
+ | `TranscriptUnavailableError` | `transcript_unavailable` | 404 | no |
128
+ | `ConflictError` | `conflict` | 409 | no |
129
+ | `RemovedError` | `removed` | 410 | no |
130
+ | `UnavailableInRegionError` | `unavailable_in_region` | 451 | no |
131
+ | `RateLimitedError` | `rate_limited` | 429 | yes |
132
+ | `UpstreamRateLimitedError` | `upstream_rate_limited` | 503 | yes |
133
+ | `UpstreamBlockedError` | `upstream_blocked` | 503 | yes |
134
+ | `OverloadedError` | `overloaded` | 503 | yes |
135
+ | `UpstreamErrorError` | `upstream_error` | 502 | yes |
136
+ | `UpstreamChangedError` | `upstream_changed` | 502 | no |
137
+ | `UpstreamTimeoutError` | `upstream_timeout` | 504 | yes |
138
+ | `InternalError` | `internal` | 500 | yes |
139
+ | `NotImplementedYetError` | `not_implemented` | 501 | no |
140
+
141
+ All extend `errors.APIError` (attributes `code`, `message`, `retryable`,
142
+ `http_status`, `request_id`, `retry_after`, `headers`, `body`, `credits_charged`).
143
+ A code this version does not know yet still arrives as a plain `APIError`. Beyond
144
+ the API: `SocialScrapeConnectionError`, `SocialScrapeTimeoutError` (and its
145
+ subclass `JobTimeoutError`), `UnexpectedResponseError` and `MissingApiKeyError`,
146
+ all extending `SocialScrapeError`.
147
+
148
+ ## Retries, timeouts, idempotency
149
+
150
+ Retryable errors (the table above, network failures and client timeouts) are
151
+ retried up to `max_retries` times (default 2) with exponential backoff and jitter.
152
+ `Retry-After` is honoured; one longer than `max_retry_after` (60 s) is not waited
153
+ for and the error is raised. POST and DELETE calls carry an `Idempotency-Key`
154
+ (generated, and reused across retries) so a retried write never charges twice.
155
+
156
+ ```python
157
+ client = SocialScrape(
158
+ api_key="ss_live_...", # default: SOCIALSCRAPE_API_KEY
159
+ base_url="https://api.socialscrape.dev", # default: SOCIALSCRAPE_BASE_URL, else this
160
+ timeout=60.0, # seconds, per attempt
161
+ max_retries=2,
162
+ retry_initial_delay=0.5,
163
+ retry_max_delay=8.0,
164
+ max_retry_after=60.0,
165
+ wait_for_jobs=True,
166
+ job_timeout=600.0,
167
+ http_client=my_httpx_client, # optional: an httpx.Client you own
168
+ )
169
+
170
+ # Per call:
171
+ client.tiktok.profile(handle="khaby.lame", request_options={"timeout": 10, "max_retries": 0})
172
+ ```
173
+
174
+ `request_options` takes `timeout`, `max_retries`, `idempotency_key`, `headers`,
175
+ `wait_for_job` and `job_timeout`.
176
+
177
+ ## Pagination
178
+
179
+ List endpoints return `cursor` and `has_more`. Pass the cursor back, or let the
180
+ client walk it. **Every page is a request and costs what one call costs**, so cap
181
+ the walk with `max_items` or `max_pages`.
182
+
183
+ ```python
184
+ for video in client.paginate(client.tiktok.profile_videos, handle="khaby.lame", max_items=100):
185
+ print(video["id"], video["stats"]["views"])
186
+
187
+ # whole pages, with each page's credits and cursor
188
+ for page in client.paginate_pages(client.tiktok.profile_videos, handle="khaby.lame", max_pages=3):
189
+ print(len(page.data["items"]), page.credits_charged, page.cursor)
190
+ ```
191
+
192
+ Async: `async for video in client.paginate(client.tiktok.profile_videos, handle=...)`.
193
+ Iteration stops at `has_more` false, at a repeated cursor and at your caps, and
194
+ never fetches a page it does not need to reach `max_items`.
195
+
196
+ ## Long jobs (AI transcription, deep listings)
197
+
198
+ When an answer is not ready within a few seconds the API replies `202` with a job
199
+ id. The client follows it for you (polling is free; the finished job is charged
200
+ once) and returns the final response. Switch it off per call or globally:
201
+
202
+ ```python
203
+ pending = client.youtube.video_transcript(url=url, request_options={"wait_for_job": False})
204
+ # pending.http.status_code == 202, pending.data["job_id"]
205
+ done = client.wait_for_job(pending.data["job_id"])
206
+ ```
207
+
208
+ A job that outlives `job_timeout` raises `JobTimeoutError` with its `job_id`; it
209
+ keeps running and can be fetched later with `client.jobs.get(job_id=...)`.
210
+
211
+ ## Batch and monitors
212
+
213
+ ```python
214
+ res = client.batch.run([
215
+ {"endpoint": "tiktok.profile", "params": {"handle": "khaby.lame"}},
216
+ {"endpoint": "tiktok.profile", "params": {"handle": "charlidamelio"}},
217
+ ])
218
+ res.credits_charged # the sum of the items
219
+ res.data["results"][0]["response"] # each item is a full envelope
220
+
221
+ monitor = client.monitors.create(
222
+ endpoint="/v1/tiktok/profile/videos",
223
+ params={"handle": "khaby.lame"},
224
+ webhook_url="https://example.com/hooks/socialscrape",
225
+ )
226
+ ```
227
+
228
+ ## Account
229
+
230
+ ```python
231
+ client.account.credits().data["credits_remaining"] # free
232
+ for row in client.paginate(client.account.usage): # request history
233
+ ...
234
+ ```
235
+
236
+ ## Newer endpoints
237
+
238
+ Methods are generated from the API's endpoint registry, and only endpoints that
239
+ are built appear (coming-soon ones are hidden). To call a path this client version
240
+ does not know yet:
241
+
242
+ ```python
243
+ res = client.request("GET", "/v1/some/new/endpoint", query={"handle": "x"})
244
+ ```
245
+
246
+ `socialscrape.ENDPOINTS` lists every endpoint the installed version knows.
@@ -0,0 +1,230 @@
1
+ # socialscrape (Python)
2
+
3
+ Social media data as an API: profiles, posts, comments, search, ads and
4
+ transcripts across platforms, in one response shape. This is the official Python
5
+ client for the [SocialScrape API](https://socialscrape.dev/docs).
6
+
7
+ - Sync (`SocialScrape`) and asyncio (`AsyncSocialScrape`) clients on `httpx`.
8
+ - Typed methods per endpoint, grouped by platform; responses typed with `TypedDict`s.
9
+ - Typed exceptions per error code, retries with backoff and `Retry-After`, timeouts.
10
+ - `credits_charged` and `credits_remaining` on every response.
11
+ - Cursor auto-pagination, long-job polling, batch calls.
12
+
13
+ ```sh
14
+ pip install socialscrape-sdk
15
+ ```
16
+
17
+ Python 3.11 or newer.
18
+
19
+ Your API key is a secret: keep it on your server, in an environment variable or a secrets manager, and never in client-side code, a public repository or a log.
20
+
21
+ ## Quickstart
22
+
23
+ ```sh
24
+ export SOCIALSCRAPE_API_KEY=ss_live_...
25
+ ```
26
+
27
+ ```python
28
+ from socialscrape import SocialScrape
29
+
30
+ client = SocialScrape() # reads SOCIALSCRAPE_API_KEY
31
+
32
+ res = client.tiktok.profile(handle="khaby.lame")
33
+
34
+ print(res.data["followers"]) # the normalised profile (a plain dict)
35
+ print(res.credits_charged) # 1
36
+ print(res.credits_remaining) # what is left
37
+ ```
38
+
39
+ Methods are grouped by platform and named after the endpoint, with the API's own
40
+ parameter names as keyword arguments: `client.tiktok.profile_videos(handle=...)`,
41
+ `client.instagram.post_comments(url=...)`, `client.youtube.video_transcript(url=...)`,
42
+ `client.apple_music.artist(...)`. `client.transcript(url=...)` takes a link from
43
+ any supported platform.
44
+
45
+ Async is the same, awaited:
46
+
47
+ ```python
48
+ import asyncio
49
+ from socialscrape import AsyncSocialScrape
50
+
51
+ async def main():
52
+ async with AsyncSocialScrape() as client:
53
+ res = await client.tiktok.profile(handle="khaby.lame")
54
+ print(res.data["followers"])
55
+
56
+ asyncio.run(main())
57
+ ```
58
+
59
+ ## The response
60
+
61
+ Every method returns a `Response` with the envelope fields, typed:
62
+
63
+ ```python
64
+ res.data # the endpoint's model (Profile, Post, ListPage[Post], ...)
65
+ res.credits_charged # 0 on a cache hit
66
+ res.credits_remaining # the team's balance after this call, or None
67
+ res.cached, res.cached_at
68
+ res.request_id # quote it to support
69
+ res.cursor, res.has_more # lists: copies of data["cursor"] / data["has_more"]
70
+ res.raw # only with raw=True: the platform's original object
71
+ res.http.status_code, res.http.headers, res.http.attempts
72
+ res.http.rate_limit_limit, res.http.rate_limit_remaining # both None: the api sends no x-ratelimit-* headers
73
+ res.http.ignored_params
74
+ ```
75
+
76
+ The client keeps count too: `client.credits_remaining` (last balance seen) and
77
+ `client.credits_charged_total`.
78
+
79
+ Every endpoint also accepts `trim=True` (smaller response), `raw=True` (adds the
80
+ platform's original object) and `max_age_days=7` (accept a cached result up to 7
81
+ days old for 0 credits). Models live in `socialscrape.models`.
82
+
83
+ ## Errors
84
+
85
+ Failures are free (`credits_charged` is 0) and raise a class per error code. Branch
86
+ on the class or `code`, never on the message.
87
+
88
+ ```python
89
+ from socialscrape import errors
90
+
91
+ try:
92
+ client.tiktok.profile(handle="no-such-account-123")
93
+ except errors.NotFoundError as e:
94
+ print(e.code, e.http_status, e.request_id, e.retryable) # not_found 404 req_... False
95
+ except errors.InsufficientCreditsError:
96
+ ... # top up
97
+ except errors.APIError as e:
98
+ print(e.code, e.retryable)
99
+ ```
100
+
101
+ | Class | `code` | HTTP | Retryable |
102
+ |---|---|---|---|
103
+ | `InvalidParamsError` | `invalid_params` | 400 | no |
104
+ | `UnsupportedUrlError` | `unsupported_url` | 400 | no |
105
+ | `UnauthorizedError` | `unauthorized` | 401 | no |
106
+ | `InsufficientCreditsError` | `insufficient_credits` | 402 | no |
107
+ | `ForbiddenError` | `forbidden` | 403 | no |
108
+ | `PrivateError` | `private` | 403 | no |
109
+ | `AgeRestrictedError` | `age_restricted` | 403 | no |
110
+ | `NotFoundError` | `not_found` | 404 | no |
111
+ | `TranscriptUnavailableError` | `transcript_unavailable` | 404 | no |
112
+ | `ConflictError` | `conflict` | 409 | no |
113
+ | `RemovedError` | `removed` | 410 | no |
114
+ | `UnavailableInRegionError` | `unavailable_in_region` | 451 | no |
115
+ | `RateLimitedError` | `rate_limited` | 429 | yes |
116
+ | `UpstreamRateLimitedError` | `upstream_rate_limited` | 503 | yes |
117
+ | `UpstreamBlockedError` | `upstream_blocked` | 503 | yes |
118
+ | `OverloadedError` | `overloaded` | 503 | yes |
119
+ | `UpstreamErrorError` | `upstream_error` | 502 | yes |
120
+ | `UpstreamChangedError` | `upstream_changed` | 502 | no |
121
+ | `UpstreamTimeoutError` | `upstream_timeout` | 504 | yes |
122
+ | `InternalError` | `internal` | 500 | yes |
123
+ | `NotImplementedYetError` | `not_implemented` | 501 | no |
124
+
125
+ All extend `errors.APIError` (attributes `code`, `message`, `retryable`,
126
+ `http_status`, `request_id`, `retry_after`, `headers`, `body`, `credits_charged`).
127
+ A code this version does not know yet still arrives as a plain `APIError`. Beyond
128
+ the API: `SocialScrapeConnectionError`, `SocialScrapeTimeoutError` (and its
129
+ subclass `JobTimeoutError`), `UnexpectedResponseError` and `MissingApiKeyError`,
130
+ all extending `SocialScrapeError`.
131
+
132
+ ## Retries, timeouts, idempotency
133
+
134
+ Retryable errors (the table above, network failures and client timeouts) are
135
+ retried up to `max_retries` times (default 2) with exponential backoff and jitter.
136
+ `Retry-After` is honoured; one longer than `max_retry_after` (60 s) is not waited
137
+ for and the error is raised. POST and DELETE calls carry an `Idempotency-Key`
138
+ (generated, and reused across retries) so a retried write never charges twice.
139
+
140
+ ```python
141
+ client = SocialScrape(
142
+ api_key="ss_live_...", # default: SOCIALSCRAPE_API_KEY
143
+ base_url="https://api.socialscrape.dev", # default: SOCIALSCRAPE_BASE_URL, else this
144
+ timeout=60.0, # seconds, per attempt
145
+ max_retries=2,
146
+ retry_initial_delay=0.5,
147
+ retry_max_delay=8.0,
148
+ max_retry_after=60.0,
149
+ wait_for_jobs=True,
150
+ job_timeout=600.0,
151
+ http_client=my_httpx_client, # optional: an httpx.Client you own
152
+ )
153
+
154
+ # Per call:
155
+ client.tiktok.profile(handle="khaby.lame", request_options={"timeout": 10, "max_retries": 0})
156
+ ```
157
+
158
+ `request_options` takes `timeout`, `max_retries`, `idempotency_key`, `headers`,
159
+ `wait_for_job` and `job_timeout`.
160
+
161
+ ## Pagination
162
+
163
+ List endpoints return `cursor` and `has_more`. Pass the cursor back, or let the
164
+ client walk it. **Every page is a request and costs what one call costs**, so cap
165
+ the walk with `max_items` or `max_pages`.
166
+
167
+ ```python
168
+ for video in client.paginate(client.tiktok.profile_videos, handle="khaby.lame", max_items=100):
169
+ print(video["id"], video["stats"]["views"])
170
+
171
+ # whole pages, with each page's credits and cursor
172
+ for page in client.paginate_pages(client.tiktok.profile_videos, handle="khaby.lame", max_pages=3):
173
+ print(len(page.data["items"]), page.credits_charged, page.cursor)
174
+ ```
175
+
176
+ Async: `async for video in client.paginate(client.tiktok.profile_videos, handle=...)`.
177
+ Iteration stops at `has_more` false, at a repeated cursor and at your caps, and
178
+ never fetches a page it does not need to reach `max_items`.
179
+
180
+ ## Long jobs (AI transcription, deep listings)
181
+
182
+ When an answer is not ready within a few seconds the API replies `202` with a job
183
+ id. The client follows it for you (polling is free; the finished job is charged
184
+ once) and returns the final response. Switch it off per call or globally:
185
+
186
+ ```python
187
+ pending = client.youtube.video_transcript(url=url, request_options={"wait_for_job": False})
188
+ # pending.http.status_code == 202, pending.data["job_id"]
189
+ done = client.wait_for_job(pending.data["job_id"])
190
+ ```
191
+
192
+ A job that outlives `job_timeout` raises `JobTimeoutError` with its `job_id`; it
193
+ keeps running and can be fetched later with `client.jobs.get(job_id=...)`.
194
+
195
+ ## Batch and monitors
196
+
197
+ ```python
198
+ res = client.batch.run([
199
+ {"endpoint": "tiktok.profile", "params": {"handle": "khaby.lame"}},
200
+ {"endpoint": "tiktok.profile", "params": {"handle": "charlidamelio"}},
201
+ ])
202
+ res.credits_charged # the sum of the items
203
+ res.data["results"][0]["response"] # each item is a full envelope
204
+
205
+ monitor = client.monitors.create(
206
+ endpoint="/v1/tiktok/profile/videos",
207
+ params={"handle": "khaby.lame"},
208
+ webhook_url="https://example.com/hooks/socialscrape",
209
+ )
210
+ ```
211
+
212
+ ## Account
213
+
214
+ ```python
215
+ client.account.credits().data["credits_remaining"] # free
216
+ for row in client.paginate(client.account.usage): # request history
217
+ ...
218
+ ```
219
+
220
+ ## Newer endpoints
221
+
222
+ Methods are generated from the API's endpoint registry, and only endpoints that
223
+ are built appear (coming-soon ones are hidden). To call a path this client version
224
+ does not know yet:
225
+
226
+ ```python
227
+ res = client.request("GET", "/v1/some/new/endpoint", query={"handle": "x"})
228
+ ```
229
+
230
+ `socialscrape.ENDPOINTS` lists every endpoint the installed version knows.
@@ -0,0 +1,38 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "socialscrape-sdk"
7
+ version = "0.1.0"
8
+ description = "SocialScrape API client: social media data (profiles, posts, comments, search, ads, transcripts) in one response shape."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ keywords = ["socialscrape", "social media", "api", "tiktok", "instagram", "youtube", "transcripts"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Typing :: Typed",
17
+ ]
18
+ dependencies = ["httpx>=0.27,<1"]
19
+
20
+ [project.urls]
21
+ Homepage = "https://socialscrape.dev"
22
+ Documentation = "https://socialscrape.dev/docs"
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["src"]
26
+
27
+ [tool.setuptools.package-data]
28
+ socialscrape = ["py.typed"]
29
+
30
+ # Tests run against the real SocialScrape app (memory stores), so the server
31
+ # packages from this repo are on the path too:
32
+ # .venv/bin/python -m pytest sdks/python/tests
33
+ [tool.pytest.ini_options]
34
+ addopts = "--import-mode=importlib -q"
35
+ testpaths = ["tests"]
36
+ asyncio_mode = "auto"
37
+ asyncio_default_fixture_loop_scope = "function"
38
+ pythonpath = ["src", "tests", "../../packages", "../../services"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,32 @@
1
+ """SocialScrape: public social media data as an API.
2
+
3
+ from socialscrape import SocialScrape
4
+
5
+ client = SocialScrape() # reads SOCIALSCRAPE_API_KEY
6
+ profile = client.tiktok.profile(handle="khaby.lame")
7
+ print(profile.data["followers"], profile.credits_charged, profile.credits_remaining)
8
+
9
+ See https://socialscrape.dev/docs.
10
+ """
11
+
12
+ from . import errors, models
13
+ from ._batch import BatchRequest
14
+ from ._generated.endpoints import ENDPOINTS
15
+ from ._response import HttpInfo, RequestOptions, Response
16
+ from ._version import __version__
17
+ from .client import AsyncSocialScrape, SocialScrape
18
+ from .errors import (
19
+ APIError,
20
+ JobTimeoutError,
21
+ MissingApiKeyError,
22
+ SocialScrapeConnectionError,
23
+ SocialScrapeError,
24
+ SocialScrapeTimeoutError,
25
+ UnexpectedResponseError,
26
+ )
27
+
28
+ __all__ = [
29
+ "SocialScrape", "AsyncSocialScrape", "Response", "HttpInfo", "RequestOptions", "BatchRequest", "ENDPOINTS",
30
+ "APIError", "SocialScrapeError", "MissingApiKeyError", "UnexpectedResponseError", "SocialScrapeConnectionError",
31
+ "SocialScrapeTimeoutError", "JobTimeoutError", "errors", "models", "__version__",
32
+ ]