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

Texte brut
https://api.appwin.io/api/sdk/community/v1

Authentification

Jeton porteur, obtenu par AppwinCore au configure :

Texte brut
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êteRôle
AuthorizationRequis
X-Appwin-LanguageLangue du lecteur (ISO 639-1), pour la traduction
If-None-MatchVersion de config en cache, sur /config

Codes de retour

CodeSignification
401Jeton absent, invalide ou révoqué
403Produit Community désactivé sur l'App ID, communauté éteinte, ou membre banni
404Ressource inexistante ou hors du projet du membre
400Validation 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.

json
{
  "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ètreDéfautDescription
groupIdtousRestreint à un groupe
sortrecentrecent ou top
cursorCurseur opaque de la page suivante
limit201 à 50
json
{ "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

json
{ "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

json
{ "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

json
{ "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 :

json
{ "targetId": "…", "myReaction": "like", "likeCount": 13 }

POST /views

json
{ "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

json
{ "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

json
{ "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

json
{ "notificationIds": [] }

Liste vide = tout marquer comme lu.

POST /translate

json
{ "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/signPOST /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 :

Texte brut
wss://api.appwin.io/realtime/sdk/community

Authentification par le même jeton, dans auth.token du handshake.

ÉvénementDéclencheur
community.post.createdPublication mise en ligne
community.post.updatedPublication éditée ou modérée
community.post.deletedPublication supprimée
community.comment.createdCommentaire ajouté
community.comment.deletedCommentaire supprimé
community.reaction.changedRé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.