Aller au contenu
Développeurs

API ouverte des communautés

Une API à l'échelle d'une communauté : une clé est liée à une communauté et agit au nom des membres que vous désignez. Ce n'est ni l'API du tableau de bord créateur ni l'API membre — il n'y a pas de session de connexion ici.

Télécharger openapi.jsonContrat publié en septembre 2026

Avant de commencer

Deux conditions doivent être remplies avant qu'une clé ne fonctionne, et aucune ne se règle depuis votre code.

1. Le forfait de la communauté doit inclure l'accès API

C'est la personne propriétaire de la communauté qui décide, pas celle qui intègre. Le forfait doit comporter la fonctionnalité d'accès API — sinon chaque appel renvoie 22203 — et un quota journalier non nul, faute de quoi chaque appel renvoie 22204.

Les communautés créées il y a un certain temps ont l'accès API désactivé par défaut, même lorsque le forfait inclut déjà d'autres fonctionnalités avancées. Si une clé fraîchement émise renvoie 22203 à chaque appel, demandez à la personne propriétaire de vérifier la console plutôt que de relire votre code.

Inutile de le découvrir en collectionnant les erreurs : /capabilities/get y répond directement, et fonctionne encore dans ces deux cas.

2. Créez une clé dans la console d'administration de la communauté

Administration de la communauté → Intégrations → API Keys → Créer. Une clé, c'est mfk_ suivi de 64 caractères hexadécimaux, et la valeur complète n'est affichée qu'une seule fois : ensuite, seul le préfixe reste visible.

Les portées cochées à la création déterminent les endpoints que la clé peut appeler. Elles ne sont pas modifiables ensuite ; pour en changer, créez une nouvelle clé et révoquez l'ancienne.

Vous pouvez restreindre une clé à une liste d'IP autorisées (adresses seules ou plages CIDR). Les requêtes venues d'ailleurs échouent avec 10007 — le même code qu'une clé invalide, ce qui mérite d'être gardé en tête quand une clé qui marchait hier cesse de fonctionner depuis une nouvelle machine.

Portées

PortéeCouvre
site.readProfil et réglages de la communauté
content.readLecture des publications, fils, recherche et arbres de réponses
content.writeCréer, modifier et supprimer des publications
media.readURL signées des médias stockés
media.writeJetons d'envoi de médias — lit aussi les URL signées, par compatibilité
spaces.readAnnuaire et détail des espaces
members.readLecture des membres et recherche par e-mail

Le sens de * a changé en septembre 2026

Il signifiait auparavant « tout, y compris les portées ajoutées plus tard », ce qui élargissait silencieusement chaque clé déjà émise à chaque nouvelle capacité livrée par la plateforme. Désormais, une nouvelle clé enregistre la liste concrète en vigueur au moment de sa création, et un * hérité ne couvre que les quatre portées qui existaient lors de sa signature : site.read, content.read, content.write et media.write.

Une ancienne clé ne peut donc pas appeler les nouveaux endpoints — espaces, membres et lecture d'URL signées. Émettez une nouvelle clé avec ces portées cochées. C'est délibéré : personne ne devrait livrer son annuaire de membres parce que la plateforme a sorti une nouvelle fonctionnalité.

Authentification

Envoyez votre clé dans l'un des deux en-têtes. Ils sont équivalents ; si vous envoyez les deux, X-API-Key l'emporte. La communauté est déduite de la clé, vous ne transmettez donc jamais d'identifiant de site.

Option 1 : en-tête X-API-Key (recommandé)
X-API-Key: mfk_xxx
Option 2 : en-tête Authorization Bearer
Authorization: Bearer mfk_xxx

La clé est à elle seule l'identifiant : gardez-la côté serveur. Elle n'a sa place ni dans du code navigateur ni dans un bundle mobile, où quiconque peut la lire et publier à la place de vos membres.

Conventions

Elles valent pour tous les endpoints ci-dessous.

  • Tous les endpoints sont en POST. Il n'y a ni GET, ni PUT, ni DELETE — les lectures aussi sont des POST.
  • Envoyez Content-Type: application/json. Les endpoints sans paramètre attendent quand même un corps JSON vide.
  • Tous les identifiants sont des chaînes hashid comme "kZ3mQ9x" — communauté, utilisateur, publication, espace et média compris. Les identifiants numériques sont rejetés.
  • Les horodatages sont renvoyés en RFC3339, par exemple 2026-08-18T10:00:00Z.
  • Les montants sont des entiers dans la plus petite unité monétaire (centimes).
  • Un en-tête Accept-Language facultatif (zh, en, ja, ko, es, fr, de, pt) localise les messages d'erreur.
  • La communauté est déduite de votre clé d'API. N'envoyez pas d'identifiant de site, ni dans le chemin ni dans le corps.

Une entrée déroge à cette règle : scheduled_at, sur /post/create et /post/update, est un horodatage Unix i64 en secondes, pas une chaîne. Voir les publications programmées plus bas.

Succès et échec partagent la même enveloppe

JSON
{
  "code": 0,
  "msg": "success",
  "data": { }
}

Endpoints

Tous les endpoints vivent sous /site_open_api/v1, utilisent POST avec un corps JSON, et exigent sur votre clé la portée indiquée.

Introspection
POST/capabilities/getaucune portée

Les portées de votre clé, l'état de l'accès API, et le quota consommé, restant et son heure de réinitialisation. C'est le seul endpoint qui répond encore quand le forfait ou le quota journalier bloque tout le reste — et il ne consomme pas de quota. Un quota_limit à -1 signifie illimité ; les trois champs de quota apparaissent et disparaissent ensemble, alors vérifiez qu'ils existent avant de soustraire.

Communauté
POST/site/getsite.read

Profil, réglages et métadonnées de la communauté

Lecture des publications
POST/post/getcontent.read

Une publication par identifiant

Obligatoire: id

POST/post/batch_getcontent.read

Jusqu'à 50 publications d'un coup ; les identifiants introuvables reviennent dans missing_ids au lieu de faire échouer le lot

Obligatoire: ids

POST/post/searchcontent.read

Rechercher des publications — le champ s'appelle query, pas keyword

Obligatoire: query

POST/post/featured_postscontent.read

Publications à la une, éventuellement limitées à un espace

POST/post/replies/listcontent.read

Toutes les réponses sous une publication

Obligatoire: post_id

POST/post/direct_replies/listcontent.read

Uniquement les réponses de premier niveau

Obligatoire: post_id

POST/post/reply_descendants/listcontent.read

Le sous-arbre sous une réponse

Obligatoire: post_id

POST/post/thread_chaincontent.read

La chaîne d'ancêtres d'une réponse

Obligatoire: post_id

Fils
POST/feed/listcontent.read

Fil de la communauté, du plus récent au plus ancien (curseur chaîne). since_id ne remonte que les publications plus récentes — il ne voit ni les modifications ni les suppressions, il complète donc une timeline mais ne la synchronise pas

POST/feed/topcontent.read

Fil de la communauté par score (curseur flottant)

POST/feed/featuredcontent.read

Fil à la une (curseur chaîne)

POST/space/feed/listcontent.read

Le fil d'un espace, avec tri, recherche et filtres questions-réponses facultatifs (curseur chaîne)

Obligatoire: space_id

POST/space/feed/topcontent.read

Le fil d'un espace par score (curseur flottant) — l'ancienne porte d'entrée, conservée par compatibilité

Obligatoire: space_id

Écriture des publications
POST/post/createcontent.write

Publier en tant que membre. Corps, espace, titre, cibles de réponse et de citation, médias, sondages, audio et programmation sont tous facultatifs

Obligatoire: author_user_id, idempotency_key

POST/post/updatecontent.write

Modifier une publication ; passez version pour un verrouillage optimiste

Obligatoire: actor_user_id, idempotency_key, post_id

POST/post/deletecontent.write

Supprimer une publication

Obligatoire: actor_user_id, idempotency_key, post_id

Espaces
POST/space/listspaces.read

Les espaces visibles pour l'observateur — omettez viewer_user_id pour la vue anonyme, limitée au publiquement lisible

POST/space/getspaces.read

Un espace par identifiant

Obligatoire: id

Membres
POST/member/getmembers.read

Un membre par identifiant

Obligatoire: id

POST/member/lookupmembers.read

Résoudre une adresse e-mail exacte en membre

Obligatoire: email

Médias
POST/media/get_tokenmedia.write

Jeton de courte durée pour le service d'envoi de médias

Obligatoire: author_user_id

POST/media/get_signed_urlsmedia.read | media.write

URL signées pour les médias privés (elles expirent — re-signez, ne les stockez pas)

Obligatoire: author_user_id, items, access_level

cURL
curl -X POST https://api.mateflow.com/site_open_api/v1/capabilities/get \
  -H "X-API-Key: mfk_xxx" -H "Content-Type: application/json" -d '{}'

# → data: {
#     "api_version": "v1",
#     "site_id": "kZ3mQ9x",
#     "scopes": ["site.read", "content.read"],
#     "api_access": true,
#     "quota_limit": 5000,     // -1 means unlimited
#     "quota_used": 128,
#     "quota_reset_at": "2026-09-15T00:00:00Z"
#   }
  • Les lectures qui prennent une liste d'identifiants plafonnent à 50, et limit plafonne aussi à 50 sur tous les endpoints paginés.
  • Sur /space/feed/list, l'encodage du curseur suit sort : changer de tri impose donc de reprendre la pagination. Les espaces questions-réponses ignorent sort et s'ordonnent selon qa_sort. Les nouvelles intégrations devraient utiliser /space/feed/list avec sort=top plutôt que /space/feed/top.
  • Les réponses membre sont une projection étroite : id, username, display_name, avatar_url, status, role et created_at. L'e-mail n'est jamais renvoyé, même quand c'est par lui que vous avez cherché — vous détenez déjà cette adresse, et renvoyer celle de chaque membre transformerait /member/get en export de carnet d'adresses. La recherche est strictement exacte, et ne rien trouver est un succès normal et non une erreur : les résultats trouvés et non trouvés sont indiscernables, tant par le code de statut que par le temps de réponse. C'est aussi la limite de débit la plus stricte de l'API.

Agir au nom d'un membre

L'API n'a pas de session de connexion : qui agit — et pour qui les résultats sont rendus — est toujours explicite dans le corps de la requête. Les trois prennent un hashid de membre.

  • author_user_id

    Le membre au nom duquel la nouvelle publication paraît. Il doit déjà appartenir à cette communauté, sinon 20303.

  • actor_user_id

    Qui effectue une modification ou une suppression. Les droits sont évalués sur ce membre — quelqu'un qui n'est ni l'auteur ni administrateur obtient 10004.

  • viewer_user_id

    Facultatif en lecture. Renvoie les résultats tels que ce membre les voit : espaces privés, état des mentions j'aime et des favoris. Omettez-le pour la vue anonyme, publique uniquement.

Pagination

  • Les fils et les listes renvoient leurs éléments accompagnés de next_cursor. Renvoyez next_cursor tel quel pour obtenir la page suivante ; un next_cursor vide signifie que vous êtes arrivé au bout.
  • limit plafonne à 50 partout.
  • Sur /space/feed/list, le curseur encode le tri en cours. Changer sort en cours de liste l'invalide — repartez de la première page.

Attention : les curseurs de /feed/top et /space/feed/top sont des scores en virgule flottante, pas des chaînes. Stocker les deux types dans une même variable texte casse silencieusement la pagination.

Idempotence

Toute écriture prend un idempotency_key dans le corps de la requête. Il y a ici exactement une couche d'idempotence — voyez la note ci-dessous si vous connaissez déjà celle de la plateforme, basée sur un en-tête.

  • idempotency_key est obligatoire sur chaque écriture et plafonné à 190 caractères. Le réutiliser ne crée jamais de deuxième publication.
  • Sa portée, c'est la communauté, le type de requête et la clé pris ensemble, indépendamment de la clé d'API utilisée : deux clés distinctes d'une même communauté qui envoient le même idempotency_key au même endpoint, et le second appel rejoue le résultat du premier.
  • La même clé avec un corps différent renvoie une erreur de conflit stable, au lieu de passer silencieusement pour une nouvelle tentative réussie.
  • Quand une requête expire, réessayez avec la même clé — n'en générez jamais une nouvelle.

L'en-tête générique Idempotency-Key de la plateforme — celui au cache de réponse de 24 heures — ne s'applique pas à /site_open_api/**. Cette couche se situe avant l'authentification par clé : un succès de cache sauterait purement et simplement la vérification de la clé. N'envoyez pas cet en-tête en attendant qu'il fasse quoi que ce soit ici.

Publications programmées

La programmation est le seul endroit où le format d'entrée et le comportement diffèrent tous deux du reste.

  • scheduled_at est un horodatage Unix i64 en secondes — et non la chaîne RFC3339 qu'utilisent toutes les autres dates.
  • Sur /post/update, omettre scheduled_at laisse la programmation existante intacte. Ce n'est pas un effacement.

Passer une valeur inférieure ou égale à 0 annule la programmation et renvoie la publication en brouillon — mais seuls les articles de blog ont un état brouillon. Sur une publication ordinaire, le même appel renvoie 10005 avec schedule_cancel_unsupported, car le chemin sous-jacent y est « publier maintenant » : l'accepter rendrait le contenu public en avance, déclencherait le fil et les notifications, et ne laisserait rien à annuler.

Envoyer des médias

Les fichiers vont au service de médias, pas à l'hôte de l'API. En trois étapes :

  1. Échangez votre clé d'API contre un jeton d'envoi de courte durée via /media/get_token.
  2. Envoyez le fichier en multipart au service de médias, avec le jeton dans un en-tête littéralement nommé "token" et file_cate valant Media, Avatar, Header, Audio ou File.
  3. Passez l'identifiant de média renvoyé dans media_ids au moment de créer la publication.

Changement cassant, septembre 2026

Un jeton émis sans portée explicite ne porte désormais que le droit d'envoi ; il portait auparavant envoi, lecture et suppression réunis. Si vous utilisiez ce jeton pour lire ou supprimer, passez explicitement une portée read ou delete. Par ailleurs, une communauté en période de grâce — essai expiré, aucune carte enregistrée — se voit refuser le jeton d'envoi par le forfait ; les jetons de lecture et de suppression ne sont pas concernés.

cURL
# 1. Exchange the API key for a short-lived upload token.
#    Since 2026-09 a token with no explicit scope is upload-only.
curl -X POST https://api.mateflow.com/site_open_api/v1/media/get_token \
  -H "X-API-Key: mfk_xxx" -H "Content-Type: application/json" \
  -d '{"author_user_id": "kZ3mQ9x", "scope": "upload"}'

# 2. Upload the file to the media service (token goes in a "token" header)
curl -X POST https://media.mateflow.com/api/v1/media/upload \
  -H "token: <token from step 1>" \
  -F "file_cate=Media" -F "file=@photo.jpg"

# 3. Attach the media id when creating the post
curl -X POST https://api.mateflow.com/site_open_api/v1/post/create \
  -H "X-API-Key: mfk_xxx" -H "Content-Type: application/json" \
  -d '{"author_user_id":"kZ3mQ9x","idempotency_key":"6f1c...","body":"Hi","media_ids":["m8Yq2Lp"]}'

Les médias privés se lisent via /media/get_signed_urls, qui prend les items et un access_level valant 1 (public), 2 (semi-privé) ou 3 (privé) et renvoie des URL avec expiration. Demandez-les à la demande plutôt que de les stocker, sinon elles finiront par renvoyer 403.

Joindre des médias à une publication

  • Envoyez soit medias — une liste de paires media_id et alt — soit media_ids, une simple liste d'identifiants. Envoyer les deux renvoie 10005.
  • alt est facultatif. Une chaîne vide efface le texte alternatif existant ; omettre le champ le laisse tel quel.

Codes d'erreur

Succès et échec partagent l'enveloppe, et le statut HTTP suit le code métier. Branchez sur code, pas sur le statut.

CodeHTTPSignification
0200Succès
10005400Paramètres invalides — msg nomme le champ fautif
10007401Clé invalide, révoquée ou expirée, ou IP appelante absente de la liste autorisée
21304403La clé n'a pas la portée exigée par cet endpoint ; les portées sont figées à la création
22203403Le forfait n'inclut pas l'accès API — voir data.required_plan
22204403Quota journalier épuisé — voir data.current et data.limit
20303404author_user_id / viewer_user_id n'est pas membre de cette communauté
10003404L'enregistrement visé n'existe pas
10004403L'acteur n'a aucun droit sur cette ressource
10202429Débit limité — ralentissez et respectez l'en-tête Retry-After
10001500Erreur serveur — vous pouvez réessayer sans risque

Attention au piège : 22203 et 22204 sont des 403, pas des 429. Seul 10202 est une limitation de débit. Détectez-la par code, jamais par le statut HTTP — c'est le contresens le plus fréquent sur cette API.

Les réponses d'erreur portent un request_id. Citez-le quand vous signalez un problème : c'est ainsi que nous retrouvons votre appel exact dans les journaux.

Les erreurs de forfait portent des métadonnées structurées

Les deux erreurs de forfait reviennent avec assez de détails pour dire à la personne propriétaire quoi faire, ce qui vous permet d'afficher un vrai message plutôt qu'un « une erreur est survenue ».

JSON
// The plan does not include API access
{
  "code": 22203,
  "msg": "...",
  "request_id": "00de5640-8ae5-4a71-aef8-2f2cab0ce8a7",
  "data": {
    "error_code": "feature_not_available",
    "feature": "api_access",
    "required_plan": "Growth"
  }
}

// The community is out of daily quota
{
  "code": 22204,
  "data": {
    "error_code": "quota_exceeded",
    "resource": "api_requests_per_day",
    "current": "5001",
    "limit": "5000",
    "required_plan": "Business"
  }
}

Quota et limites de débit

Trois mécanismes distincts. Les confondre, c'est pourquoi on signale qu'une intégration « est limitée en débit » alors qu'elle n'a en réalité plus de quota de forfait.

CoucheCe qui est comptéEn cas de dépassement
Quota journalier du forfaitUne journée entière, tous les endpoints, par communauté22204 / HTTP 403
Budget de rafale par cléFenêtres de 10 et 60 secondes, par clé, échelonné par endpoint10202 / HTTP 429
Limite générale par IPPar IP source, 60 par 10 s et 300 par minute10202 / HTTP 429

Le quota journalier compte les requêtes qui passent l'authentification, se réinitialise à 00:00 UTC, et indique dans data.limit le plafond réellement en vigueur — y compris une enveloppe relevée spécifiquement pour cette communauté. Quand le backend du compteur se comporte mal, la requête passe au lieu d'être rejetée : un comptage occasionnellement bas ne signifie donc pas que le quota a cessé de fonctionner.

Budget de rafale par clé (nouveau en septembre 2026)

Auparavant, le seul seau était par IP : plusieurs clients derrière une même adresse de sortie se disputaient la place, tandis qu'un appelant réparti sur de nombreuses adresses n'était presque pas contraint. Le budget est désormais échelonné par clé — la chose qu'une personne propriétaire émet, peut faire tourner, et dont elle répond.

PalierCouvrePar 10 sPar 60 s
LecturesFils, publications, réponses, espaces, introspection120600
Médiasget_token et get_signed_urls30120
ÉcrituresCréation, modification et suppression de publications20120
Recherchepost/search2060
Recherche de membremember/lookup510

member/lookup est nettement plus strict pour des raisons de sécurité, pas de capacité. Ses autres contraintes limitent ce qu'une seule réponse révèle ; celle-ci limite le nombre de fois où vous pouvez poser la question — et c'est précisément l'étape qui transforme une recherche en moisson d'annuaire. Pour l'usage prévu, résoudre au rythme humain des adresses que vous détenez déjà, dix par minute suffisent largement.

  • Un dépassement renvoie HTTP 429 avec code 10202, plus Retry-After et les en-têtes X-RateLimit-Limit, X-RateLimit-Remaining et X-RateLimit-Reset.
  • Prenez ces chiffres comme un point de départ et non comme une mesure : ils sont ajustés d'après le trafic observé. Ne les figez pas dans votre logique de réessai ; respectez le 429 et Retry-After. Toute la couche de limitation est désactivée par défaut et tourne un moment en mode observation avant d'être appliquée.
  • Implémentez de toute façon un backoff sur 429, et maintenez le trafic de lecture à environ dix requêtes par seconde au plus.

Recevoir des webhooks

Si la personne propriétaire de la communauté a configuré un endpoint de webhook ou connecté Slack, Discord ou Zapier, les livraisons portent deux jeux d'identifiants qui n'ont pas le même sens.

ChampIdentifieEntre deux tentatives
event_idL'événement métierStable
occurred_atQuand l'événement s'est produitStable
delivery_idCette tentative de livraisonChange
timestampQuand cette tentative a été envoyéeChange

Dédupliquez sur event_id. Une livraison que vous avez traitée correctement mais à laquelle vous avez répondu trop lentement revient avec un nouveau delivery_id et le même event_id. Quand un même événement part à la fois vers un webhook maison et vers un Zap, les deux côtés voient le même event_id : vous pouvez donc les rapprocher.

La livraison est au moins une fois et peut arriver dans le désordre. Les doublons comme les arrivées tardives d'événements anciens sont normaux ; c'est au récepteur d'être idempotent.

event_id peut être absent. Les tâches mises en file avant l'arrivée du champ ne le portent pas, et la livraison omet le champ entièrement au lieu d'envoyer 0 — un 0 ferait ressembler toutes les vieilles tâches au même événement. Acceptez son absence et repliez-vous sur une déduplication au mieux via delivery_id.

Dépannage

Les symptômes qui remontent vraiment, et ce dont il s'agit la plupart du temps.

SymptômeEn général
10007 invalid API key, alors que la clé vient d'être copiée depuis la consoleUn espace ou un retour à la ligne autour de la clé ; ou la clé a été révoquée ; ou une liste d'IP autorisées est en place et votre adresse de sortie n'y figure pas
22203 feature_not_availableL'accès API est désactivé sur le forfait. Après modification par la personne propriétaire, comptez jusqu'à 10 minutes pour l'expiration du cache de forfait
22204 quota_exceeded avec un limit à 0Le quota journalier du forfait vaut 0, ce qui signifie indisponible — et non « non renseigné, donc illimité »
21304 sur un endpoint que vous pensiez utilisableLa clé n'a pas cette portée. Les portées sont figées à la création : la solution est une nouvelle clé
21304 sur une clé « tous les droits » (*)Un * hérité ne couvre pas les portées ajoutées ensuite — media.read, spaces.read, members.read. Émettez une nouvelle clé en les cochant
10202 avec HTTP 429Une limite de débit. Ralentissez selon Retry-After ; si c'était sur member/lookup, rappelez-vous que ce seau vaut dix par minute
10005 avec schedule_cancel_unsupportedVous avez passé un scheduled_at inférieur ou égal à 0 sur une publication ordinaire. Seuls les articles de blog ont un état brouillon où revenir
Un jeton de média qui servait à lire ou supprimer ne fonctionne plusDepuis septembre 2026, un jeton sans portée sert uniquement à l'envoi. Passez une portée read ou delete
/member/lookup renvoie 200 avec found à falseC'est un « non trouvé » normal, pas une erreur. Trouvé comme non trouvé renvoient 200 — branchez sur found
10005 invalid params mentionnant queryLe champ de recherche est query. keyword n'est pas un champ valide
20303 user not foundL'identifiant n'appartient pas à cette communauté, ou vous avez envoyé un identifiant numérique au lieu d'un hashid
La pagination n'avance plusLes curseurs de /feed/top et /space/feed/top sont des flottants. Sérialisés en chaînes, ils cessent de correspondre
Les URL de médias se mettent à renvoyer 403 au bout d'un momentLes URL signées ont un TTL. Re-signez à la demande plutôt que de les stocker

Notes

  • La communauté est déduite de votre clé d'API. N'envoyez pas d'identifiant de site, ni dans le chemin ni dans le corps.
  • Les portées sont figées à la création de la clé. Pour en changer, créez une nouvelle clé et révoquez l'ancienne.
  • Gardez vos clés d'API côté serveur. Une clé dans du code client permet à quiconque la lit de publier à la place de vos membres.
  • Le contrat OpenAPI couvre chaque chemin et chaque schéma. Une réserve : le générateur dont il provient n'émet pas required sur les corps de requête, prenez donc les champs obligatoires sur cette page plutôt que dans le contrat.

Commencez à construire

Créez une clé à portées dans la console d'administration de votre communauté, appelez /capabilities/get pour la vérifier, et c'est parti.

Essai gratuit de 14 jours · Sans carte bancaire

Démarrer l'essai gratuit