Publications

Posts : exemples cURL/Python/Node ; annonces, événements, offres, planification, campagnes par établissement ou en masse, suivi du statut, suppression.

Une publication est une courte mise à jour publiée sur le profil Google Business Profile ou la page Facebook d'un établissement : une annonce, un événement daté ou une offre avec un coupon. Une campagne peut cibler plusieurs établissements et les deux sites à la fois. La publication est asynchrone : chaque appel de création renvoie donc immédiatement une réponse, et vous interrogez ensuite le statut pour obtenir le résultat. Les trois endpoints GET fonctionnent avec une clé Read (lecture) ; la création et la suppression nécessitent une clé Write (écriture).

Aperçu des endpoints

MéthodeCheminFonctionRéférence
POST/postsPublier une annonce, un simple message avec un bouton et une image facultatifs.Créer une publication d'annonce
POST/postsPublier un événement avec un titre et une période de début et de fin.Créer une publication d'événement
POST/postsPublier une offre avec un code de coupon, une réduction et des conditions.Créer une publication d'offre
GET/posts/{postId}Une campagne avec son statut de publication et ses compteurs d'engagement par site.Obtenir une publication
GET/locations/{locationId}/postsUne page de campagnes ciblant un établissement.Récupérer les publications d'un établissement
DELETE/posts/{postId}Supprimer définitivement une campagne et retirer sa publication de tous les sites.Supprimer une publication
POST/bulk-postsUne campagne diffusée sur plusieurs établissements.Créer une publication en masse
GET/bulk-posts/{postId}Une campagne en masse avec une ventilation des données analytiques par établissement.Récupérer une publication en masse par son identifiant
GET/locations/{locationId}/bulk-postsLes campagnes en masse incluant un établissement, avec des filtres et des options de tri.Récupérer les publications en masse d'un établissement

Valeurs utilisées partout

Les quatre énumérations ci-dessous sont communes à tous les endpoints de création.

ChampValeurs acceptées
postTypeANNOUNCEMENT, EVENT, OFFER, COVID19, PRODUCT
postSites et chaque siteGOOGLE, FACEBOOK
postCta[].typeBOOK, ORDER, SHOP, LEARN_MORE, SIGN_UP, GET_OFFER
postMediaUrl[].typeIMAGE, VIDEO

Publier une annonce

POST /posts

Une annonce comprend un message et, éventuellement, un bouton et une image par site. Fournissez une entrée postMessage pour chaque site figurant dans postSites. La réponse renvoie la nouvelle campagne avec status défini sur INPROGRESS, car la publication s'effectue en arrière-plan.

Paramètres

NomEmplacementObligatoireRemarques
input.postNamebodyOuiNom interne de la campagne, jamais affiché aux clients.
input.locationIdsbodyOuiTableau d'identifiants d'établissement en base64.
input.postTypebodyOuiANNOUNCEMENT ici.
input.postSitesbodyOuiTableau de GOOGLE et FACEBOOK.
input.postMessagebodyNonTableau d'objets contenant site et message.
input.postCtabodyNonTableau d'objets contenant site, type, url.
input.postMediaUrlbodyNonTableau d'objets contenant site, url, type.
input.clientMutationIdbodyNonRenvoyé sans modification pour la corrélation.
Announcement
curl -X POST https://listingsapi.com/api/v4/posts \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{  "input": {    "postName": "Grand Opening Announcement",    "locationIds": ["TG9jYXRpb246MTgwMDI4OQ=="],    "postType": "ANNOUNCEMENT",    "postSites": ["GOOGLE"],    "postMessage": [      { "site": "GOOGLE", "message": "We are now open! Visit us this week for our grand opening." }    ],    "postCta": [      { "site": "GOOGLE", "type": "LEARN_MORE", "url": "https://jennyhome.example/opening" }    ],    "postMediaUrl": [      { "site": "GOOGLE", "url": "https://cdn.example.com/opening.jpg", "type": "IMAGE" }    ]  }}'
JSON
{  "data": {    "createSocialPost": {      "success": true,      "errors": [],      "socialPost": {        "id": "U29jaWFsUG9zdDo0NDEyMg==",        "name": "Grand Opening Announcement",        "status": "INPROGRESS",        "type": "ANNOUNCEMENT",        "sites": ["GOOGLE"],        "publishDetails": [          { "site": "GOOGLE", "status": "PUBLISHING", "createdDate": "2026-06-28", "submissionError": null }        ],        "postLinks": [],        "locationIds": [1800289]      }    }  }}

Conservez socialPost.id. Il s'agit de l'identifiant de publication en base64 requis par tous les autres endpoints présentés ici. Les deux fonctions utilitaires des SDK acceptent une seule chaîne message et la déclinent en une entrée par site ; vous ne construisez donc le tableau postMessage manuellement que dans cURL.

Publier un événement

POST /posts

Un événement s'affiche sous forme de carte datée. Le calendrier propre à l'événement se trouve dans postContextInfo. L'objet distinct postScheduledDates contrôle le moment où la publication elle-même apparaît et expire, ce qui correspond généralement à la période précédant l'événement. Google rejette les événements sans titre, et les deux SDK lèvent une erreur de validation avant l'envoi si title est absent.

Paramètres

NomEmplacementObligatoireRemarques
input.postTypebodyOuiEVENT ici.
input.postContextInfo.titlebodyOui en pratiqueTitre de l'événement. Google rejette les événements qui n'en ont pas.
input.postContextInfo.startDay, endDaybodyNonDates, YYYY-MM-DD.
input.postContextInfo.startTime, endTimebodyNonChaînes d'affichage telles que 10:00am.
input.postScheduledDatesbodyNonObjet contenant startDatetime, endDatetime, removalSites.

Tous les éléments du tableau des annonces s'appliquent également.

Event
curl -X POST https://listingsapi.com/api/v4/posts \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{  "input": {    "postName": "Summer Sale Kickoff Event",    "locationIds": ["TG9jYXRpb246MTgwMDI4OQ=="],    "postType": "EVENT",    "postSites": ["GOOGLE"],    "postContextInfo": {      "title": "Summer Sale Kickoff",      "startDay": "2026-07-15",      "startTime": "10:00am",      "endDay": "2026-07-15",      "endTime": "06:00pm"    },    "postMessage": [      { "site": "GOOGLE", "message": "Join us for the Summer Sale kickoff, refreshments and up to 40 percent off in store." }    ]  }}'
JSON
{  "data": {    "createSocialPost": {      "success": true,      "socialPost": {        "id": "U29jaWFsUG9zdDo0NDEyMw==",        "status": "INPROGRESS",        "type": "EVENT",        "scheduledStartDate": "2026-07-15",        "scheduledEndDate": "2026-07-15",        "contextInfo": {          "title": "Summer Sale Kickoff",          "startDay": "2026-07-15",          "startTime": "10:00am",          "endDay": "2026-07-15",          "endTime": "06:00pm"        }      }    }  }}

La valeur contextInfo renvoyée correspond à la période de l'événement. scheduledStartDate et scheduledEndDate décrivent le calendrier propre à la publication, qui correspond ici aux dates de l'événement, car aucun postScheduledDates n'a été envoyé.

Publier une offre

POST /posts

Une offre est une carte promotionnelle contenant un code de réduction, une remise, un lien pour en bénéficier et des conditions. Tous ces éléments figurent dans postContextInfo, et startDay avec endDay délimitent la période de validité de l'offre plutôt que celle de la publication.

Paramètres

NomDansObligatoireRemarques
input.postTypebodyOuiOFFER ici.
input.postContextInfo.titlebodyOui en pratiqueDéfinit le libellé de l'offre. Les deux SDK l'exigent.
input.postContextInfo.couponCodebodyNonCode utilisé par le client pour bénéficier de l'offre.
input.postContextInfo.discountbodyNonTexte libre, par exemple 25%.
input.postContextInfo.redeemUrlbodyNonAdresse à laquelle bénéficier de l'offre.
input.postContextInfo.termsConditionsbodyNonConditions détaillées.
input.postContextInfo.startDay, endDaybodyNonPériode de validité, YYYY-MM-DD.
Offer
curl -X POST https://listingsapi.com/api/v4/posts \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{  "input": {    "postName": "Festive 25 Percent Off",    "locationIds": ["TG9jYXRpb246MTgwMDI4OQ=="],    "postType": "OFFER",    "postSites": ["GOOGLE"],    "postContextInfo": {      "title": "25% off storewide",      "couponCode": "FESTIVE25",      "discount": "25%",      "redeemUrl": "https://jennyhome.example/festive",      "termsConditions": "Valid in-store only through July 6.",      "startDay": "2026-06-22",      "endDay": "2026-07-06"    },    "postMessage": [      { "site": "GOOGLE", "message": "Celebrate with us, 25 percent off everything in store this week." }    ],    "postCta": [      { "site": "GOOGLE", "type": "GET_OFFER", "url": "https://jennyhome.example/festive" }    ]  }}'
JSON
{  "data": {    "createSocialPost": {      "success": true,      "socialPost": {        "id": "U29jaWFsUG9zdDo0NDEyNA==",        "status": "INPROGRESS",        "type": "OFFER",        "contextInfo": {          "title": "25% off storewide",          "couponCode": "FESTIVE25",          "discount": "25%",          "startDay": "2026-06-22",          "endDay": "2026-07-06"        }      }    }  }}

Google utilise ORDER et Facebook utilise GET_OFFER pour les boutons des offres. Si une promotion se termine plus tôt que prévu, supprimez la campagne plutôt que de la modifier.

Consulter une publication et son statut

GET /posts/{postId}

Renvoie le résultat de publication d'une campagne acceptée. La réponse contient, sous data.socialPostView, le contenu de la campagne, une entrée publishDetails par site, des compteurs d'engagement et les liens vers les publications en ligne une fois la publication réussie.

Paramètres

NomDansObligatoireRemarques
postIdpathOuiIdentifiant de publication en Base64 provenant de la réponse à la création ou d'une liste.
Get a post
curl https://listingsapi.com/api/v4/posts/U29jaWFsUG9zdDo0NDEyMg== \-H "Authorization: API $LISTINGSAPI_KEY"
JSON
{  "data": {    "socialPostView": {      "socialPostId": "U29jaWFsUG9zdDo0NDEyMg==",      "socialPostInfo": {        "status": "SUCCESS",        "type": "ANNOUNCEMENT",        "views": 128,        "clicks": 14,        "publishDetails": [          { "site": "GOOGLE", "status": "PUBLISHED", "publishedDate": "2026-06-28", "submissionError": null }        ],        "postLinks": [          { "site": "GOOGLE", "link": "https://www.google.com/maps/place/?post=44122" }        ]      },      "publishedLocationsCount": 1,      "socialPostAnalyticsBySite": [        { "site": "GOOGLE", "views": 128, "clicks": 14, "reactions": 3, "shares": 0, "comments": 1 }      ]    }  }}

Deux champs de statut sont importants et répondent à des questions différentes.

ChampValeurSignification
socialPostInfo.statusINPROGRESSAcceptée, la publication est toujours en cours.
SUCCESSPublication terminée. Consultez publishDetails pour obtenir le résultat par site.
SCHEDULEDEn attente de l'ouverture de la période postScheduledDates.
EXPIREDLa période planifiée est terminée et la publication n'est plus en ligne.
publishDetails[].statusPUBLISHINGCe site n'a pas encore répondu.
PUBLISHEDEn ligne sur ce site. Consultez postLinks pour obtenir l'URL.
ERRORCe site l'a refusée. submissionError en indique la raison.

Une campagne peut afficher SUCCESS alors que l'un de ses sites présente le statut ERROR ; vérifiez donc toujours publishDetails plutôt que de vous fier uniquement au statut de premier niveau.

Lister les publications d'un établissement

GET /locations/{locationId}/posts

Un historique paginé par décalage de toutes les campagnes qui incluent un établissement donné, sous data.postsByLocation. Utilisez-le pour vérifier ce qui est déjà en ligne avant de planifier une nouvelle publication.

Paramètres

NomDansObligatoireRemarques
locationIdpathOuiIdentifiant en Base64, transmis sans modification.
pagequeryNonNuméro de page commençant à 1, 1 par défaut.
perPagequeryNon10 par défaut, ne doit pas dépasser 50.
Posts on a location
curl "https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/posts?page=1&perPage=10" \-H "Authorization: API $LISTINGSAPI_KEY"
JSON
{  "data": {    "postsByLocation": {      "records": [        {          "id": "U29jaWFsUG9zdDo0NDEyMg==",          "name": "Summer Hours Update",          "status": "SUCCESS",          "type": "ANNOUNCEMENT",          "createdAt": "2026-06-28",          "views": 128,          "clicks": 14,          "sites": ["GOOGLE"]        }      ],      "pageInfo": { "totalPages": 1, "hasNextPage": false, "totalRecords": 1 }    }  }}

Cette route ne propose aucun filtre côté serveur. Elle accepte uniquement page et perPage et ignore tout autre paramètre ; consultez donc type et status dans chaque enregistrement et appliquez le filtrage dans votre propre code. Pour trier et filtrer côté serveur, utilisez plutôt le point de terminaison de liste en masse ci-dessous.

Supprimer une publication

DELETE /posts/{postId}

La suppression est définitive et retire la publication de tous les sites sur lesquels elle a été publiée. Il n'existe ni annulation ni point de terminaison de modification ; pour corriger une publication en ligne, vous devez donc la supprimer et publier une nouvelle version.

Paramètres

NomDansObligatoireRemarques
postIdpathOuiIdentifiant de publication en Base64.
clientMutationIdbodyNonFacultatif, renvoyé sans modification à des fins de corrélation.
Delete
curl -X DELETE https://listingsapi.com/api/v4/posts/U29jaWFsUG9zdDo0NDEyMg== \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "clientMutationId": "delete-44122-req-1" }'
JSON
{  "data": {    "deleteSocialPost": {      "success": true,      "socialPostId": "U29jaWFsUG9zdDo0NDEyMg=="    }  }}

La suppression d'une publication inconnue ou déjà supprimée renvoie une réponse d'erreur avec data défini sur null plutôt que sur success: false.

Publier une campagne pour plusieurs établissements

POST /bulk-posts

Le contrat est identique à celui de POST /posts, mais conçu pour les réseaux d'établissements : indiquez chaque établissement dans locationIds et une seule campagne les couvre tous. La réponse figure sous la clé createSocialPost, comme pour POST /posts.

Paramètres

NomDansObligatoireRemarques
input.postNamebodyOuiNom interne de la campagne.
input.locationIdsbodyOuiTous les identifiants d'établissement en base64 pour lesquels publier.
input.postTypebodyOuiEVENT, OFFER et PRODUCT nécessitent également postContextInfo ou products.
input.postSitesbodyOuiGOOGLE, FACEBOOK ou les deux.
input.postScheduledDatesbodyNonstartDatetime et endDatetime au format ISO 8601. Crée la publication avec le statut SCHEDULED.
input.componentIdentifierbodyNonVotre propre référence, utile comme filtre par la suite.
Bulk publish
curl -X POST https://listingsapi.com/api/v4/bulk-posts \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{  "input": {    "postName": "July Reopening Announcement",    "locationIds": ["TG9jYXRpb246MTgwMDI4OQ==", "TG9jYXRpb246MTgwMDI5MA=="],    "postType": "ANNOUNCEMENT",    "postSites": ["GOOGLE", "FACEBOOK"],    "postMessage": [      { "site": "GOOGLE", "message": "We are back. Visit us this week for our summer menu." },      { "site": "FACEBOOK", "message": "We are back. Swing by for the new summer menu." }    ],    "postScheduledDates": {      "startDatetime": "2026-07-10T09:00:00Z",      "endDatetime": "2026-07-24T23:59:00Z"    }  }}'
JSON
{  "data": {    "createSocialPost": {      "success": true,      "errors": null,      "socialPost": {        "id": "U29jaWFsUG9zdDo5OTIwMQ==",        "name": "July Reopening Announcement",        "status": "SCHEDULED",        "type": "ANNOUNCEMENT",        "scheduledStartDate": "2026-07-10",        "scheduledEndDate": "2026-07-24",        "sites": ["GOOGLE", "FACEBOOK"],        "locationIds": [1800289, 1800290]      }    }  }}

Les fonctions utilitaires des deux SDK définissent par défaut sites sur Google et Facebook, développent pour vous une table de correspondance des messages par site et encodent les identifiants numériques des établissements ; la publication en masse est donc généralement l'appel le plus court dans chacun des SDK.

Lire une campagne de publication en masse

GET /bulk-posts/{postId}

L'équivalent de GET /posts/{postId} pour les publications en masse. Il renvoie la même structure, avec publishedLocationsCount supérieur à 1 et une ventilation des données analytiques par établissement dans socialPostAnalyticsByLocationId.

Paramètres

NomEmplacementObligatoireRemarques
postIdpathOuiIdentifiant de publication en masse encodé en Base64, et non un entier brut.
Get a bulk post
curl https://listingsapi.com/api/v4/bulk-posts/U29jaWFsUG9zdDo1MTA4OA== \-H "Authorization: API $LISTINGSAPI_KEY"
JSON
{  "data": {    "socialPostView": {      "socialPostId": "U29jaWFsUG9zdDo1MTA4OA==",      "socialPostInfo": {        "name": "Festive Offer - All Stores",        "status": "SUCCESS",        "type": "OFFER",        "views": 4210,        "clicks": 318,        "sites": ["GOOGLE", "FACEBOOK"]      },      "socialPostAnalyticsByLocationId": [        {          "locationId": 1800289,          "siteStats": [            { "site": "GOOGLE", "status": "PUBLISHED", "views": 1040, "clicks": 80 },            { "site": "FACEBOOK", "status": "PUBLISHED", "views": 360, "clicks": 26 }          ]        }      ]    }  }}

socialPostAnalyticsBySite présente les totaux de la campagne pour l'ensemble des établissements, tandis que socialPostAnalyticsByLocationId ventile ces mêmes chiffres par établissement. Parcourez les valeurs de status par établissement pour repérer tout établissement pour lequel un site a rencontré un échec.

Lister les campagnes de publication en masse d'un établissement

GET /locations/{locationId}/bulk-posts

La liste est paginée par décalage sous data.rollupSocialPosts. Contrairement à la liste par établissement, cette route accepte les filtres et le tri, ce qui en fait l'endpoint adapté à un tableau de bord de campagnes.

Paramètres

NomEmplacementObligatoireRemarques
locationIdpathOuiIdentifiant encodé en Base64, transmis sans modification.
page, perPagequeryNonPagination par décalage.
filtersqueryNonEncodé en JSON, selon type, status, creationDateRange ou siteUrls.
sortFieldsqueryNonEncodé en JSON, par exemple avec le champ created_at et l'ordre Descending.
locationIdsqueryNonTableau encodé en JSON contenant les identifiants des établissements supplémentaires à inclure.
componentIdentifierqueryNonLimite les résultats aux campagnes créées par votre intégration.

Les champs utilisables pour le tri sont name, type, status, created_at, schedule_start_date, schedule_end_date, client_name, clicks, views et shares.

Bulk posts on a location
curl -G https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/bulk-posts \-H "Authorization: API $LISTINGSAPI_KEY" \--data-urlencode 'page=1' \--data-urlencode 'perPage=10' \--data-urlencode 'sortFields={"field":"created_at","order":"Descending"}'
JSON
{  "data": {    "rollupSocialPosts": {      "records": [        {          "id": "U29jaWFsUG9zdDo1MTA4OA==",          "name": "Festive Offer - All Stores",          "status": "SUCCESS",          "type": "OFFER",          "createdAt": "2026-06-20",          "views": 4210,          "clicks": 318        }      ],      "pageInfo": { "totalPages": 1, "hasNextPage": false, "totalRecords": 1 }    }  }}

Les deux SDK encodent les objets filters et sortFields en JSON pour vous ; transmettez-les donc sous forme de dictionnaires ou d'objets natifs.

Erreurs que vous rencontrerez

Code ou statutSignificationCorrection
400 : variable $input manquanteLe corps n'a pas été encapsulé dans input.Imbriquez tous les champs sous une seule clé input.
SY90016 avec le statut HTTP 400Une clé Read a été utilisée pour une création ou une suppression.Utilisez une clé Write.
401Clé manquante ou non valide.Envoyez Authorization: API <YOUR_API_KEY>.
429 avec Retry-AfterTrop d'appels, souvent dus à des interrogations répétées du statut d'une publication.Attendez le nombre de secondes indiqué. Consultez Limites de débit.
200 avec success: falseLa validation a échoué.Lisez errors dans data.createSocialPost.
per_page must not exceed 50perPage dépasse le plafond de la liste des établissements.Demandez 50 ou moins.
tag is not a supported parameter for this querytag envoyé à la route des publications par établissement.Envoyez uniquement page et perPage.

Liste complète dans Codes d'erreur.

Étapes suivantes