Python example
Everything below uses the requests library and a partner key in MISSLESS_API_KEY. Python 3.10 or newer.
pip install requestsexport MISSLESS_API_KEY="mls_live_3f9ac2e8b71d4c5f9e2a7b6c1d0e"A small client
Section titled “A small client”import os
import requests
BASE = "https://social.missless.tel/v1"
class MissLessError(Exception): def __init__(self, status: int, code: str, message: str, field: str | None = None, details=None): super().__init__(f"{code}: {message}") self.status, self.code, self.field, self.details = status, code, field, details
class MissLess: def __init__(self, key: str | None = None): self.session = requests.Session() self.session.headers["Authorization"] = f"Bearer {key or os.environ['MISSLESS_API_KEY']}"
def request(self, method: str, path: str, **kwargs): res = self.session.request(method, f"{BASE}{path}", timeout=30, **kwargs) if res.status_code == 204: return None body = res.json() if res.content else {} if not res.ok: err = body.get("error", {}) raise MissLessError(res.status_code, err.get("code", "unknown"), err.get("message", res.reason), err.get("field"), err.get("details")) return body
def get(self, path: str, **params): return self.request("GET", path, params=params)
def post(self, path: str, json=None, **kwargs): return self.request("POST", path, json=json, **kwargs)Create a workspace
Section titled “Create a workspace”from missless import MissLess
ml = MissLess()
ws = ml.post("/workspaces", {"external_id": "user_123", "name": "Salon Nova", "timezone": "Europe/Amsterdam"})print(ws["id"], ws["status"]) # ws7k2m9p4q1r8t3 activeSame external_id twice returns the same workspace, so this is safe to call on every login.
Mint a connect link
Section titled “Mint a connect link”link = ml.post( f"/workspaces/{ws['id']}/connect-links", {"networks": ["facebook", "instagram"], "redirect_url": "https://app.example.com/settings/social", "expires_in": 900},)print(link["url"]) # send the user hereAfter the user comes back, list the accounts:
accounts = ml.get(f"/workspaces/{ws['id']}/accounts")["data"]active = {a["network"]: a["id"] for a in accounts if a["status"] == "active"}print(active) # {'instagram': 'acig4h2k5m7p9r1', 'facebook': 'acfb8n2c4v6b1m3'}Upload media
Section titled “Upload media”By URL:
media = ml.post("/media", {"workspace": ws["id"], "url": "https://cdn.example.com/cuts/friday.jpg", "alt": "Fresh haircut"})Or from a local file, as multipart:
with open("friday.jpg", "rb") as f: media = ml.post("/media", data={"workspace": ws["id"], "alt": "Fresh haircut"}, files={"file": ("friday.jpg", f, "image/jpeg")})print(media["id"], media["kind"], media["width"], media["height"])Publish and wait
Section titled “Publish and wait”post = ml.post( "/posts", { "workspace": ws["id"], "caption": "Fresh cuts, Friday walk-ins welcome", "media": [media["id"]], "targets": list(active.values()), "external_ref": "acme:post:1234", }, params={"wait": "true"},)print(post["status"])for t in post["targets"]: print(t["network"], t["status"], t["permalink"] or t["error"])Schedule instead
Section titled “Schedule instead”post = ml.post( "/posts", { "workspace": ws["id"], "caption": "Weekend hours: Saturday 9 to 17", "media": [media["id"]], "targets": [active["instagram"]], "schedule_at": "2026-09-05T08:00:00Z", "options": {"instagram": {"first_comment": "#salon #weekend"}}, },)print(post["status"], post["schedule_at"]) # scheduled 2026-09-05T08:00:00ZPoll for the outcome
Section titled “Poll for the outcome”If you did not use wait and do not run a webhook:
import time
while True: post = ml.get(f"/posts/{post['id']}") if post["status"] not in ("scheduled", "publishing"): break time.sleep(5)print(post["status"], post["last_error"])Or read the event log:
events = ml.get("/events", workspace=ws["id"], type="post.published", since="2026-08-25T00:00:00Z")["data"]Handle errors
Section titled “Handle errors”from missless import MissLessError
try: ml.post("/posts", {"workspace": ws["id"], "caption": "no media", "targets": [active["instagram"]]})except MissLessError as e: if e.code == "validation_error": print("fix field:", e.field) # media elif e.code == "account_disconnected": print("send the user through a connect link") elif e.code == "rate_limited": print("back off") elif e.code == "network_error": print("Meta said:", e.details) else: raiseVerify a webhook
Section titled “Verify a webhook”The full verifier and a Flask example are on Verify signatures. The core of it:
import hashlibimport hmacimport time
def verify_missless(raw_body: bytes, headers, secret: str, tolerance: int = 300) -> bool: timestamp = headers.get("X-MissLess-Timestamp", "") signature = headers.get("X-MissLess-Signature", "") if not timestamp or not signature: return False if abs(int(time.time()) - int(timestamp)) > tolerance: return False mac = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256) return hmac.compare_digest("sha256=" + mac.hexdigest(), signature)Reply to a DM
Section titled “Reply to a DM”convs = ml.get("/conversations", workspace=ws["id"], kind="dm", unread="true")["data"]for c in convs: print(c["contact"]["name"], c["last_message_preview"])
reply = ml.post(f"/conversations/{convs[0]['id']}/messages", {"text": "Yes, Friday 15:00 is free. Shall I book it?"})ml.request("PATCH", f"/conversations/{convs[0]['id']}", json={"unread": 0})Remember the 24-hour window on DMs; see Replying.