Any media_urls (or media_ids) entry can also be a dict carrying per-media
alt text (max 1500 chars), delivered to Mastodon, Bluesky, X (photos/GIFs), and
Pinterest:
Code
media_urls=[{"url": "https://example.com/teaser.jpg", "alt": "Red sneaker on a white background"}],
Publish immediately:
Code
post = client.posts.create_and_publish( content="We are live!", channels=["x", "bluesky"],)
Upload media
Code
media = client.media.upload_from_url( url="https://example.com/promo.mp4", name="promo-august", folder="Campaigns",)media_id = media["data"]["id"]
Local files: client.media.upload(file="/path/to/image.jpg", name="hero-shot")
(file accepts a path, bytes, or a binary file-like object). Base64:
client.media.upload_from_base64(data=b64_string, mime_type="image/png").
Analytics
Code
# One poststats = client.analytics.post("123")# Up to 100 posts in one request (avoids rate limit trouble when syncing)bulk = client.analytics.posts(["123", "124", "125"])# Workspace overviewoverview = client.analytics.overview(period="30d")# Account-level stats (followers etc.)accounts = client.analytics.accounts(platform="instagram")
Verify webhooks
Code
from fastapi import FastAPI, Header, HTTPException, Requestfrom omnisocials import WebhookVerificationError, verify_webhook_signatureapp = FastAPI()WEBHOOK_SECRET = "whsec_from_webhook_creation"@app.post("/hooks/omnisocials")async def omnisocials_webhook( request: Request, x_omnisocials_signature: str = Header(None),): payload = await request.body() # raw bytes, exactly as received try: event = verify_webhook_signature( payload, x_omnisocials_signature, WEBHOOK_SECRET, tolerance=300 ) except WebhookVerificationError: raise HTTPException(status_code=400, detail="Invalid signature") if event["type"] == "post.published": for target in event["data"]["targets"]: print(target["platform"], target["status"], target.get("native_post_id")) return {"ok": True}
Errors
The base class is OmniSocialsError. APIError covers HTTP errors with subclasses
AuthenticationError (401), PermissionDeniedError (403), NotFoundError (404),
ValidationError (400/422), RateLimitError (429, with .retry_after), and
ServerError (5xx), plus APIConnectionError and WebhookVerificationError.
Each carries status, code, message, and body.
Code
from omnisocials import ( OmniSocials, APIError, APIConnectionError, AuthenticationError, NotFoundError, RateLimitError, ValidationError,)client = OmniSocials()try: client.posts.get("does-not-exist")except NotFoundError as exc: print("Gone:", exc.code, exc.message)except RateLimitError as exc: print(f"Slow down, retry in {exc.retry_after}s")except ValidationError as exc: print("Bad request:", exc.body)except AuthenticationError: print("Check your API key")except APIConnectionError: print("Network problem")except APIError as exc: print("API error:", exc.status, exc.message)
Configuration
Code
client = OmniSocials( api_key="omsk_live_...", timeout=60.0, # seconds max_retries=5, # retries after the first attempt base_url="https://api.omnisocials.com/v1", # override for testing)
Async client
AsyncOmniSocials mirrors every resource and method with identical names:
Code
import asynciofrom omnisocials import AsyncOmniSocialsasync def main(): async with AsyncOmniSocials() as client: posts = await client.posts.list(status="scheduled") print(len(posts["data"]))asyncio.run(main())
Notes
Responses are plain dicts; list endpoints include data and pagination
Top-level None values are dropped from request bodies; nested None is sent as JSON null