omnisocials 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.
- omnisocials-0.1.0/.gitignore +6 -0
- omnisocials-0.1.0/LICENSE +21 -0
- omnisocials-0.1.0/PKG-INFO +397 -0
- omnisocials-0.1.0/README.md +371 -0
- omnisocials-0.1.0/pyproject.toml +50 -0
- omnisocials-0.1.0/src/omnisocials/__init__.py +41 -0
- omnisocials-0.1.0/src/omnisocials/_client.py +367 -0
- omnisocials-0.1.0/src/omnisocials/_errors.py +124 -0
- omnisocials-0.1.0/src/omnisocials/_utils.py +55 -0
- omnisocials-0.1.0/src/omnisocials/_webhooks.py +112 -0
- omnisocials-0.1.0/src/omnisocials/py.typed +0 -0
- omnisocials-0.1.0/src/omnisocials/resources/__init__.py +26 -0
- omnisocials-0.1.0/src/omnisocials/resources/accounts.py +40 -0
- omnisocials-0.1.0/src/omnisocials/resources/analytics.py +113 -0
- omnisocials-0.1.0/src/omnisocials/resources/folders.py +87 -0
- omnisocials-0.1.0/src/omnisocials/resources/locations.py +42 -0
- omnisocials-0.1.0/src/omnisocials/resources/media.py +350 -0
- omnisocials-0.1.0/src/omnisocials/resources/posts.py +560 -0
- omnisocials-0.1.0/src/omnisocials/resources/webhooks.py +118 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OmniSocials
|
|
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,397 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: omnisocials
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Official Python SDK for the OmniSocials Public API - schedule and publish social media posts, upload media, and read analytics across 11 platforms.
|
|
5
|
+
Project-URL: Homepage, https://omnisocials.com
|
|
6
|
+
Project-URL: Documentation, https://docs.omnisocials.com
|
|
7
|
+
Project-URL: Repository, https://github.com/OmniSocials/omnisocials-python
|
|
8
|
+
Author-email: OmniSocials <hello@omnisocials.com>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: api,facebook,instagram,linkedin,omnisocials,scheduling,sdk,social media,tiktok,twitter,youtube
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Classifier: Typing :: Typed
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Requires-Dist: httpx<1,>=0.23.0
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# OmniSocials Python SDK
|
|
28
|
+
|
|
29
|
+
The official Python client for the [OmniSocials](https://omnisocials.com) Public API. Schedule and publish posts, upload media, and read analytics across Instagram, Facebook, LinkedIn, YouTube, TikTok, X, Pinterest, Bluesky, Threads, Mastodon, and Google Business.
|
|
30
|
+
|
|
31
|
+
Full API reference: [https://docs.omnisocials.com](https://docs.omnisocials.com)
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install omnisocials
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Requires Python 3.9+. The only dependency is [httpx](https://www.python-httpx.org/).
|
|
40
|
+
|
|
41
|
+
## Quickstart
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from omnisocials import OmniSocials
|
|
45
|
+
|
|
46
|
+
client = OmniSocials() # reads OMNISOCIALS_API_KEY from the environment
|
|
47
|
+
post = client.posts.create(content="Hello from Python", channels=["instagram", "linkedin"])
|
|
48
|
+
print(post["data"]["id"])
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Authentication
|
|
52
|
+
|
|
53
|
+
Create an API key in the OmniSocials app under **Settings -> API Keys**. Keys look like `omsk_live_...` (or `omsk_test_...` for test keys) and are bound to one workspace.
|
|
54
|
+
|
|
55
|
+
Pass the key explicitly or set the `OMNISOCIALS_API_KEY` environment variable:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
from omnisocials import OmniSocials
|
|
59
|
+
|
|
60
|
+
client = OmniSocials(api_key="omsk_live_...")
|
|
61
|
+
# or: export OMNISOCIALS_API_KEY=omsk_live_... and just OmniSocials()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The client is a context manager; use `with` (or call `client.close()`) to release connections:
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
with OmniSocials() as client:
|
|
68
|
+
accounts = client.accounts.list()
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Async client
|
|
72
|
+
|
|
73
|
+
Every resource and method exists on `AsyncOmniSocials` with identical names:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
import asyncio
|
|
77
|
+
from omnisocials import AsyncOmniSocials
|
|
78
|
+
|
|
79
|
+
async def main():
|
|
80
|
+
async with AsyncOmniSocials() as client:
|
|
81
|
+
posts = await client.posts.list(status="scheduled")
|
|
82
|
+
print(len(posts["data"]))
|
|
83
|
+
|
|
84
|
+
asyncio.run(main())
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Posts
|
|
88
|
+
|
|
89
|
+
### Schedule a post
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
post = client.posts.create(
|
|
93
|
+
content="Big announcement coming Friday",
|
|
94
|
+
channels=["instagram", "facebook", "linkedin"],
|
|
95
|
+
scheduled_at="2026-08-01T09:00:00Z",
|
|
96
|
+
media_urls=["https://example.com/teaser.jpg"],
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Omit `scheduled_at` to create a draft, or use `create_and_publish` to publish immediately:
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
post = client.posts.create_and_publish(
|
|
104
|
+
content="We are live!",
|
|
105
|
+
channels=["x", "bluesky"],
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Post to specific platforms with platform options
|
|
110
|
+
|
|
111
|
+
`content` can be a per-platform mapping (with `default` as the fallback), and each platform has its own options object:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
post = client.posts.create(
|
|
115
|
+
content={
|
|
116
|
+
"default": "New feature: best times to post",
|
|
117
|
+
"linkedin": "We just shipped best times to post. Here is how it works.",
|
|
118
|
+
},
|
|
119
|
+
channels=["linkedin", "instagram", "youtube"],
|
|
120
|
+
media_urls=["https://example.com/demo.mp4"],
|
|
121
|
+
scheduled_at="2026-08-01T09:00:00Z",
|
|
122
|
+
instagram={"share_to_feed": True},
|
|
123
|
+
youtube={"title": "Best times to post, explained", "privacy_status": "public"},
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Note that `linkedin` targets a personal LinkedIn profile and `linkedin_page` targets a LinkedIn company page. Both can be connected to the same workspace and posted to independently.
|
|
128
|
+
|
|
129
|
+
### X thread example
|
|
130
|
+
|
|
131
|
+
Pass 2 to 25 `thread_parts` to publish a chained thread instead of a single tweet (each part is at most 280 characters). Bluesky and Mastodon support the same `thread_parts` shape:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
post = client.posts.create(
|
|
135
|
+
content="How we cut our API response times by 80%", # fallback text
|
|
136
|
+
channels=["x"],
|
|
137
|
+
scheduled_at="2026-08-01T15:00:00Z",
|
|
138
|
+
x={
|
|
139
|
+
"thread_parts": [
|
|
140
|
+
{"text": "How we cut our API response times by 80%. A thread:"},
|
|
141
|
+
{"text": "1/ We started by profiling every endpoint under real load."},
|
|
142
|
+
{"text": "2/ The biggest win: batching analytics reads into one query."},
|
|
143
|
+
{"text": "3/ Full write-up on the blog. Thanks for reading!"},
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
On update, passing `x={"thread_parts": None}` clears the thread and reverts the post to single-tweet mode. Only top-level `None` values are dropped from request bodies, so nested `None` values like this one are sent as JSON `null`.
|
|
150
|
+
|
|
151
|
+
### Other post operations
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
posts = client.posts.list(status="scheduled", limit=50, offset=0)
|
|
155
|
+
post = client.posts.get("123")
|
|
156
|
+
client.posts.update("123", scheduled_at="2026-08-02T10:00:00Z")
|
|
157
|
+
client.posts.publish("123") # publish a draft or scheduled post now
|
|
158
|
+
client.posts.delete("123") # returns None (204)
|
|
159
|
+
|
|
160
|
+
# Recent posts fetched live from the connected platforms (including content
|
|
161
|
+
# published outside OmniSocials). Requires the analytics:read scope.
|
|
162
|
+
recent = client.posts.recent_platform(limit=10, platforms=["instagram", "tiktok"])
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Media
|
|
166
|
+
|
|
167
|
+
### Upload from a URL (recommended, up to 1GB)
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
media = client.media.upload_from_url(
|
|
171
|
+
url="https://example.com/promo.mp4",
|
|
172
|
+
name="promo-august",
|
|
173
|
+
folder="Campaigns",
|
|
174
|
+
)
|
|
175
|
+
media_id = media["data"]["id"]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Files over 100MB are processed in the background: the response has `data["status"] == "processing"`; poll `client.media.get(media_id)` until it is `"ready"` before attaching it to a post.
|
|
179
|
+
|
|
180
|
+
### Upload a local file (multipart, max 100MB)
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
media = client.media.upload(file="/path/to/image.jpg", name="hero-shot")
|
|
184
|
+
# file can also be raw bytes or a binary file-like object
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Uploading a PDF splits it into image slides (max 20 pages) and returns `slides` plus a `media_ids` array. Pass all of `media_ids`, in order, to `posts.create` to publish the deck as a carousel (a native swipeable document on LinkedIn, an image carousel elsewhere).
|
|
188
|
+
|
|
189
|
+
Every upload response also includes a `compatibility` block listing any connected platforms that would reject the file.
|
|
190
|
+
|
|
191
|
+
### Other media operations
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
files = client.media.list(search="promo", folder_id="42")
|
|
195
|
+
item = client.media.get("1001")
|
|
196
|
+
client.media.update("1001", name="renamed", folder_id="42")
|
|
197
|
+
client.media.update("1001", folder_id=None) # move to the root ("All media")
|
|
198
|
+
client.media.delete("1001")
|
|
199
|
+
|
|
200
|
+
# Base64 upload (handy when you have bytes but no URL)
|
|
201
|
+
client.media.upload_from_base64(data=b64_string, mime_type="image/png")
|
|
202
|
+
|
|
203
|
+
# Preflight compatibility BEFORE uploading
|
|
204
|
+
result = client.media.check(url="https://example.com/huge.mp4")
|
|
205
|
+
if not result["compatible"]:
|
|
206
|
+
print(result["summary"])
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Large file uploads with a presigned URL
|
|
210
|
+
|
|
211
|
+
`create_upload_url` mints a one-time upload URL so any environment (a CI job, a sandbox, a script without the SDK) can upload a file without auth headers:
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
import httpx
|
|
215
|
+
|
|
216
|
+
grant = client.media.create_upload_url()
|
|
217
|
+
# grant contains: upload_url, upload_token, expires_in_seconds (600),
|
|
218
|
+
# method ("POST"), content_type ("multipart/form-data")
|
|
219
|
+
|
|
220
|
+
with open("/path/to/video.mp4", "rb") as f:
|
|
221
|
+
response = httpx.post(grant["upload_url"], files={"file": f})
|
|
222
|
+
media_id = response.json()["data"]["id"]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The token is single-use and expires after 10 minutes. The response JSON contains the created media item with its `id` (or a `media_ids` array for a PDF).
|
|
226
|
+
|
|
227
|
+
## Folders
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
folders = client.folders.list()
|
|
231
|
+
folder = client.folders.create(name="Campaigns")
|
|
232
|
+
sub = client.folders.create(name="August", parent_id=folder["data"]["id"])
|
|
233
|
+
client.folders.update(sub["data"]["id"], name="August 2026")
|
|
234
|
+
client.folders.update(sub["data"]["id"], parent_id=None) # move to top level
|
|
235
|
+
client.folders.delete(folder["data"]["id"]) # files move to root, subfolders move up
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Accounts
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
accounts = client.accounts.list()
|
|
242
|
+
for account in accounts["data"]:
|
|
243
|
+
print(account["platform"], account["username"])
|
|
244
|
+
|
|
245
|
+
account = client.accounts.get("instagram")
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Analytics
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
# One post
|
|
252
|
+
stats = client.analytics.post("123")
|
|
253
|
+
|
|
254
|
+
# Up to 100 posts in one request (avoids rate limit trouble when syncing)
|
|
255
|
+
bulk = client.analytics.posts(["123", "124", "125"])
|
|
256
|
+
|
|
257
|
+
# Workspace overview
|
|
258
|
+
overview = client.analytics.overview(period="30d")
|
|
259
|
+
|
|
260
|
+
# Account-level stats (followers etc.)
|
|
261
|
+
accounts = client.analytics.accounts(platform="instagram")
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Best times to post
|
|
265
|
+
|
|
266
|
+
```python
|
|
267
|
+
best = client.analytics.best_times(platform="instagram", timezone="Europe/Amsterdam")
|
|
268
|
+
for slot in best["data"]["best_times"]:
|
|
269
|
+
print(slot)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Locations (Instagram place tagging)
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
results = client.locations.search("Blue Bottle Coffee Oakland")
|
|
276
|
+
location_id = results["data"][0]["id"]
|
|
277
|
+
client.locations.validate(location_id)
|
|
278
|
+
|
|
279
|
+
client.posts.create(
|
|
280
|
+
content="Great coffee here",
|
|
281
|
+
channels=["instagram"],
|
|
282
|
+
media_urls=["https://example.com/latte.jpg"],
|
|
283
|
+
location_id=location_id,
|
|
284
|
+
)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Webhooks
|
|
288
|
+
|
|
289
|
+
Subscribe to `post.scheduled`, `post.published`, and `post.failed` events:
|
|
290
|
+
|
|
291
|
+
```python
|
|
292
|
+
webhook = client.webhooks.create(
|
|
293
|
+
url="https://example.com/hooks/omnisocials",
|
|
294
|
+
events=["post.published", "post.failed"],
|
|
295
|
+
)
|
|
296
|
+
secret = webhook["data"]["secret"] # only shown once, store it
|
|
297
|
+
|
|
298
|
+
client.webhooks.list()
|
|
299
|
+
client.webhooks.get(webhook["data"]["id"])
|
|
300
|
+
client.webhooks.update(webhook["data"]["id"], is_active=False)
|
|
301
|
+
client.webhooks.rotate_secret(webhook["data"]["id"]) # new secret, shown once
|
|
302
|
+
client.webhooks.delete(webhook["data"]["id"])
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Verifying webhook signatures (FastAPI example)
|
|
306
|
+
|
|
307
|
+
Every delivery is signed with HMAC-SHA256. The `X-OmniSocials-Signature` header has the form `t=<unix>,v1=<hex>`, signed over `"{timestamp}.{raw_body}"`. Use `verify_webhook_signature` with the raw request body (do not parse it first):
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
from fastapi import FastAPI, Header, HTTPException, Request
|
|
311
|
+
from omnisocials import WebhookVerificationError, verify_webhook_signature
|
|
312
|
+
|
|
313
|
+
app = FastAPI()
|
|
314
|
+
WEBHOOK_SECRET = "whsec_from_webhook_creation"
|
|
315
|
+
|
|
316
|
+
@app.post("/hooks/omnisocials")
|
|
317
|
+
async def omnisocials_webhook(
|
|
318
|
+
request: Request,
|
|
319
|
+
x_omnisocials_signature: str = Header(None),
|
|
320
|
+
):
|
|
321
|
+
payload = await request.body() # raw bytes, exactly as received
|
|
322
|
+
try:
|
|
323
|
+
event = verify_webhook_signature(
|
|
324
|
+
payload, x_omnisocials_signature, WEBHOOK_SECRET, tolerance=300
|
|
325
|
+
)
|
|
326
|
+
except WebhookVerificationError:
|
|
327
|
+
raise HTTPException(status_code=400, detail="Invalid signature")
|
|
328
|
+
|
|
329
|
+
if event["type"] == "post.published":
|
|
330
|
+
for target in event["data"]["targets"]:
|
|
331
|
+
print(target["platform"], target["status"], target.get("native_post_id"))
|
|
332
|
+
return {"ok": True}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
`verify_webhook_signature` uses a constant-time comparison, rejects timestamps older than `tolerance` seconds (default 300) to block replays, and returns the parsed event dict on success. Respond with any 2xx within 10 seconds to acknowledge; deliveries are retried up to 3 times.
|
|
336
|
+
|
|
337
|
+
## Error handling
|
|
338
|
+
|
|
339
|
+
Non-2xx responses raise typed exceptions with `status`, `code`, `message`, and the parsed `body`:
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
from omnisocials import (
|
|
343
|
+
OmniSocials,
|
|
344
|
+
APIError,
|
|
345
|
+
APIConnectionError,
|
|
346
|
+
AuthenticationError,
|
|
347
|
+
NotFoundError,
|
|
348
|
+
RateLimitError,
|
|
349
|
+
ValidationError,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
client = OmniSocials()
|
|
353
|
+
|
|
354
|
+
try:
|
|
355
|
+
client.posts.get("does-not-exist")
|
|
356
|
+
except NotFoundError as exc:
|
|
357
|
+
print("Gone:", exc.code, exc.message)
|
|
358
|
+
except RateLimitError as exc:
|
|
359
|
+
print(f"Slow down, retry in {exc.retry_after}s")
|
|
360
|
+
except ValidationError as exc:
|
|
361
|
+
print("Bad request:", exc.body)
|
|
362
|
+
except AuthenticationError:
|
|
363
|
+
print("Check your API key")
|
|
364
|
+
except APIConnectionError:
|
|
365
|
+
print("Network problem")
|
|
366
|
+
except APIError as exc:
|
|
367
|
+
print("API error:", exc.status, exc.message)
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
The full hierarchy: `OmniSocialsError` is the base; `APIError` covers all HTTP errors with subclasses `AuthenticationError` (401), `PermissionDeniedError` (403), `NotFoundError` (404), `ValidationError` (400/422), `RateLimitError` (429, with `.retry_after`), and `ServerError` (5xx); `APIConnectionError` covers network failures; `WebhookVerificationError` covers signature failures.
|
|
371
|
+
|
|
372
|
+
## Rate limits
|
|
373
|
+
|
|
374
|
+
The API allows **100 requests per minute** per API key. Every response includes `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers. On 429 the SDK automatically waits for the `Retry-After` value and retries. When syncing analytics for many posts, prefer the bulk `client.analytics.posts([...])` endpoint (100 posts per request) over per-post calls.
|
|
375
|
+
|
|
376
|
+
## Retries and timeouts
|
|
377
|
+
|
|
378
|
+
The SDK automatically retries on 429, 5xx, and connection errors with exponential backoff (0.5s, 1s, 2s, with jitter), honoring the `Retry-After` header when present. Other 4xx errors are never retried. Defaults: 2 retries, 30 second timeout. Both are configurable:
|
|
379
|
+
|
|
380
|
+
```python
|
|
381
|
+
client = OmniSocials(
|
|
382
|
+
api_key="omsk_live_...",
|
|
383
|
+
timeout=60.0, # seconds
|
|
384
|
+
max_retries=5, # retries after the first attempt
|
|
385
|
+
base_url="https://api.omnisocials.com/v1", # override for testing
|
|
386
|
+
)
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## More
|
|
390
|
+
|
|
391
|
+
- Full API reference and guides: [https://docs.omnisocials.com](https://docs.omnisocials.com)
|
|
392
|
+
- MCP server for AI agents: `npx -y @omnisocials/mcp-server`
|
|
393
|
+
- Agent skills CLI: `npx skills add OmniSocials/omnisocials-agent-skills`
|
|
394
|
+
|
|
395
|
+
## License
|
|
396
|
+
|
|
397
|
+
MIT
|