Référence - API SDK Community
Endpoints consommés par le SDK. Documentés pour le débogage et pour un éventuel portage sur une plateforme non couverte.
Tu n'as pas à les appeler toi-même si tu utilises le SDK : il s'en charge, y compris de la session et du renouvellement de jeton.
Base
https://api.appwin.io/api/sdk/community/v1
Authentification
Jeton porteur, obtenu par AppwinCore au configure :
Authorization: Bearer <token>
Le jeton porte à lui seul l'organisation, le projet et le profil du membre : aucun identifiant de tenant ne transite dans les chemins.
| En-tête | Rôle |
|---|---|
Authorization | Requis |
X-Appwin-Language | Langue du lecteur (ISO 639-1), pour la traduction |
If-None-Match | Version de config en cache, sur /config |
Codes de retour
| Code | Signification |
|---|---|
401 | Jeton absent, invalide ou révoqué |
403 | Produit Community désactivé sur l'App ID, communauté éteinte, ou membre banni |
404 | Ressource inexistante ou hors du projet du membre |
400 | Validation refusée : longueur, anti-flood, contenu refusé par la modération |
Démarrage
GET /bootstrap
Config, groupes, profil et pastille en un aller-retour. Compte une ouverture de la communauté dans les statistiques.
{
"config": { "theme": {}, "features": {}, "limits": {}, "context": {}, "version": 3 },
"groups": [
{ "id": "…", "name": "Général", "emoji": "💬", "isDefault": true, "canPost": true, "postCount": 42 }
],
"profile": { "id": "…", "nickname": "Curious Otter 417", "isAnonymous": true, "isMe": true },
"unreadNotificationCount": 3
}
GET /config
Config seule. Envoie If-None-Match: <version> pour obtenir un 304 quand rien
n'a changé.
La politique de modération n'est jamais servie : un membre n'a pas à lire le seuil qu'il aurait à contourner.
Fil
GET /feed
| Paramètre | Défaut | Description |
|---|---|---|
groupId | tous | Restreint à un groupe |
sort | recent | recent ou top |
cursor | — | Curseur opaque de la page suivante |
limit | 20 | 1 à 50 |
{ "data": [ { "id": "…", "body": "…", "likeCount": 12 } ], "nextCursor": "eyJ…" }
Le curseur est opaque : à renvoyer tel quel, jamais à interpréter.
nextCursor: null signale la fin.
Ce que le fil sert : les publications publiées, plus les siennes en attente de relecture. Les contenus des membres shadow bannis sont absents pour tout le monde sauf leur auteur.
POST /posts
{ "groupId": "…", "body": "…", "media": [ { "url": "…", "width": 1200, "height": 800 } ] }
groupId omis → groupe par défaut. Refusé si la longueur dépasse la limite du
projet, si le rythme dépasse l'anti-flood, ou si la modération bloque.
PATCH /posts/:id · DELETE /posts/:id
Réservés à l'auteur. Une édition repasse par la modération : sans ça, publier un texte anodin puis l'éditer suffirait à contourner le filtre.
Commentaires
GET /posts/:id/comments
Commentaires racine, chacun avec ses premières réponses inline. replyCount
donne le total serveur, qui peut dépasser les réponses livrées.
POST /posts/:id/comments
{ "body": "…", "parentCommentId": "…" }
Un seul niveau : répondre à une réponse rattache au même parent racine, sans que le client ait à le savoir.
DELETE /comments/:id
Réactions et vues
POST /posts/:id/reactions · POST /comments/:id/reactions
{ "kind": "like" }
Trois comportements selon l'état : poser, remplacer, ou retirer si on repose la même. La réponse permet de mettre à jour le compteur sans refetch :
{ "targetId": "…", "myReaction": "like", "likeCount": 13 }
POST /views
{ "postIds": ["…", "…"] }
Lot de posts vus, envoyé quand le membre quitte l'écran. Les vues sont uniques par (post, membre) : rescroller ne regonfle rien.
Profil
GET /profiles/:id
POST /me - passerelle setUser
{ "nickname": "…", "avatarUrl": "…", "bio": "…" }
Champs optionnels ; un champ absent n'est pas écrasé. Fournir un nickname sort
le profil de l'anonymat.
PATCH /me - édition par le membre
Accepte en plus isAnonymous. Repasser à true regénère le pseudo anonyme et
efface l'avatar : l'anonymat doit être visible, pas un simple drapeau.
Signalement, notifications, traduction
POST /reports
{ "targetType": "post", "targetId": "…", "reason": "spam", "note": "…" }
targetType : post, comment, profile.
reason : spam, harassment, hate_speech, sexual_content, violence,
misinformation, off_topic, other.
Toujours 204, même si ce membre avait déjà signalé cette cible. Lui dire
« déjà signalé » ne l'aide pas et révèle l'état de la file.
GET /notifications · POST /notifications/read
{ "notificationIds": [] }
Liste vide = tout marquer comme lu.
POST /translate
{ "targetType": "post", "targetId": "…", "targetLanguage": "fr" }
targetLanguage omis → X-Appwin-Language. Le résultat est mis en cache : le
premier lecteur d'une langue paie l'appel, les suivants lisent la base.
Uploads
POST /uploads/sign → POST /uploads/:id/confirm
Signature d'un dépôt direct vers le stockage, puis confirmation. Le purpose est
forcé serveur-side à community_media : un client ne peut pas viser un autre
usage même en modifiant sa requête.
Realtime
Namespace Socket.IO :
wss://api.appwin.io/realtime/sdk/community
Authentification par le même jeton, dans auth.token du handshake.
| Événement | Déclencheur |
|---|---|
community.post.created | Publication mise en ligne |
community.post.updated | Publication éditée ou modérée |
community.post.deleted | Publication supprimée |
community.comment.created | Commentaire ajouté |
community.comment.deleted | Commentaire supprimé |
community.reaction.changed | Réaction posée ou retirée |
Le payload est minimal (resourceId, projectId) : le client refait un
appel REST pour récupérer la ressource.
Ce n'est pas de l'économie de bande passante, c'est une question de correction. Une room rassemble tous les membres d'un projet ; y pousser du contenu obligerait à rejouer côté socket toutes les règles de visibilité - shadow ban, modération, contenus en attente - et un jour l'une d'elles manquerait. Le refetch REST repasse par ces règles, une seule fois, au bon endroit.