Docs
Overview
A simpler app is a web service you run. Groups install it, and it acts for each group through the Apps API with that group's token. It hears about what happens through webhooks. Nothing runs on simpler's servers.
- Register your app in My Apps. You get a client ID, a client secret (shown once) and a signing secret.
- Install it on a group you own. While it's private, only your own groups can install it.
- When it's ready, submit it for review to be listed in every group's Dashboard → Apps.
Install (OAuth 2)
When a group owner presses Install, simpler sends them to your App Address with ?install=1&group=<handle>. Send them on to the install screen:
https://simpler.social/apps/authorize
?client_id=YOUR_CLIENT_ID
&redirect_uri=https://your.app/callback (one of your Redirect Addresses)
&scope=group:read orders:read orders:write (space separated)
&state=RANDOM_VALUE_YOU_CHECK_LATER
&group=HANDLE (optional: preselects the group)
They pick a group and approve. simpler sends them back to redirect_uri?code=…&state=…&group=<handle> (or ?error=access_denied). Check state, then trade the code (it lasts 10 minutes, once) for the group's token:
curl -X POST https://simpler.social/apps/token \
-d client_id=YOUR_CLIENT_ID -d client_secret=YOUR_CLIENT_SECRET \
-d grant_type=authorization_code -d code=CODE -d redirect_uri=https://your.app/callback
{"access_token": "smp_…", "token_type": "bearer", "scope": "group:read orders:read orders:write",
"group": {"id": 12, "handle": "bassplayers", "name": "Bass Players"}}
Keep the token secret, one per group. It works until the group removes your app (you get app.uninstalled). Installing again gives a new token.
The Apps API
Base address https://simpler.social/apps/v1/. Send Authorization: Bearer <token>. JSON in and out. Money is in cents (USD).
| Call | Scope | What it does |
|---|---|---|
GET group | group:read | The group: id, handle, name, url, its shops. |
GET products | products:read | Products with their options. ?shop=<id> for one shop. |
POST products | products:write | Add a product (a Shop page is made if the group has none). |
GET products/{id} | products:read | One product. |
PATCH products/{id} | products:write | Change it. Options are matched by id or external_ref; ones left out stop being offered. |
DELETE products/{id} | products:write | Remove it. |
GET orders | orders:read | Paid shop orders, newest first. ?fulfillment=unfulfilled, ?since=ISO8601, ?limit= (up to 200). |
GET orders/{id} | orders:read | One order: items (with sku, external_ref), email, shipping address. |
POST orders/{id}/fulfillment | orders:write | Set shipping: {"status": "in_progress"|"shipped"|"canceled"|"unfulfilled", "carrier", "tracking_number", "tracking_url"}. Shipped emails the buyer once. |
A product:
{"name": "Bass Clef Tee", "description": "Soft cotton.", "price_cents": 2500, "available": true,
"image_url": "https://files.example/tee.png",
"options": [{"name": "Black / M", "price_cents": 2500, "sku": "TEE-BK-M", "external_ref": "your-variant-id"},
{"name": "Black / XXL", "price_cents": 2800, "external_ref": "your-variant-id-2"}]}
external_ref is yours: put your own id for each option there. Paid orders carry it on each item, so you know what to make.
Scopes
group:read | See the group's name, address and pages |
products:read | See the group's shop products |
products:write | Add, change and remove shop products |
orders:read | See the shop's orders, including buyers' names, emails and shipping addresses |
orders:write | Update orders' shipping status and tracking |
The install screen shows these words. Ask only for what you use: owners decide with this list in front of them.
Webhooks
Choose events on your app's page; they're sent to your Webhook Address (https, on the public internet) for groups whose install allowed the scope.
order.paid | needs orders:read |
order.updated | needs orders:read |
product.updated | needs products:read |
app.uninstalled | always sent |
POST https://your.app/webhooks
Simpler-Event: order.paid
Simpler-Signature: t=1761400000,v1=5f1c…
{"id": "evt_…", "event": "order.paid", "created": 1761400000,
"group": {"id": 12, "handle": "bassplayers"}, "data": {"order": { …same as GET orders/{id}… }}}
Answer with any 2xx within 10 seconds. Otherwise we try again after 1, 5 and 30 minutes, 2 and 6 hours. Use id to skip repeats. Check the signature, using your signing secret, before you trust a webhook:
# Python
import hmac, hashlib, time
def verify(body: bytes, header: str, secret: str) -> bool:
parts = dict(p.split("=", 1) for p in header.split(","))
if abs(time.time() - int(parts["t"])) > 300:
return False
expected = hmac.new(secret.encode(), f'{parts["t"]}.{body.decode()}'.encode(), hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts["v1"])
Open (launch)
Open in Dashboard → Apps sends the owner to your App Address with ?simpler_launch=<payload>.<signature>. Both parts are base64url; the signature is HMAC-SHA256 of the payload part with your signing secret. The payload says who and which group, for five minutes:
{"app": "app_…", "group": {"id": 12, "handle": "bassplayers", "name": "Bass Players"},
"user": {"id": 7, "name": "Pat Example"}, "exp": 1761400300}
Check the signature and exp, then start your own session for that group.
Review and listing
Private apps install only on your own groups. Submit for Review when your app has a tagline, description, icon, privacy policy and support email. We check that it works, asks only for what it needs, and treats buyers' information with care. Listed apps appear in every group's Dashboard → Apps.
Limits and errors
1,200 calls an hour per installation. Errors are JSON {"error": "…"} with 400 (fix the request), 401 (token), 403 (scope), 404, 405 or 429 (slow down).