Skip to content

Python example

Everything below uses the requests library and a partner key in MISSLESS_API_KEY. Python 3.10 or newer.

Terminal window
pip install requests
export MISSLESS_API_KEY="mls_live_3f9ac2e8b71d4c5f9e2a7b6c1d0e"
missless.py
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)
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 active

Same external_id twice returns the same workspace, so this is safe to call on every login.

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 here

After 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'}

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"])
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"])
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:00Z

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"]
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:
raise

The full verifier and a Flask example are on Verify signatures. The core of it:

import hashlib
import hmac
import 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)
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.