Publica publicaciones en Google y Facebook
Publica anuncios, eventos y ofertas; programa un post en varias ubicaciones, verifica cada sitio, gestiona publicaciones y responde a webhooks.
Una publicación es una tarjeta con fecha en el Google Business Profile o en la página de Facebook de una ubicación: un anuncio, un evento o una oferta. Una llamada la crea, la publicación se ejecuta en segundo plano y una segunda llamada te indica si cada sitio la aceptó.
Los tres tipos de publicación usan POST /posts con el cuerpo envuelto en input. Un cuerpo sin envoltorio se rechaza con un 400. Los SDK crean el envoltorio y validan la carga útil antes de enviarla.
1. Publica un anuncio
Un anuncio admite un mensaje, un botón opcional y una imagen opcional.
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://acmekitchen.example.com/opening" } ], "postMediaUrl": [ { "site": "GOOGLE", "url": "https://cdn.example.com/opening.jpg", "type": "IMAGE" } ] }}'{ "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] } } }}Guarda socialPost.id. status comienza en INPROGRESS y postLinks está vacío hasta que un sitio acepta la publicación; en el paso 5 se consultan ambos.
2. Publica un evento
Un evento añade postContextInfo con un title, startDay, endDay y horas de visualización opcionales. Google rechaza los eventos sin título y los SDK lanzan ValidationError antes del envío si falta.
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, giveaways, and up to 40% off in store." } ] }}'La respuesta tiene la misma estructura que en el paso 1, con type: "EVENT" y las fechas devueltas dentro de socialPost.contextInfo. postContextInfo contiene las fechas del propio evento; postScheduledDates (paso 4) controla cuándo se publica y se retira la publicación en sí.
3. Publica una oferta
El postContextInfo de una oferta contiene un title, couponCode, discount, redeemUrl, termsConditions y el período de validez definido por startDay y endDay. Combínalo con un botón GET_OFFER en Facebook o ORDER en Google; no existe el tipo REDEEM.
curl -X POST https://listingsapi.com/api/v4/posts \-H "Authorization: API $LISTINGSAPI_KEY" \-H "Content-Type: application/json" \-d '{ "input": { "postName": "Summer 25% Off", "locationIds": ["TG9jYXRpb246MTgwMDI4OQ=="], "postType": "OFFER", "postSites": ["GOOGLE"], "postContextInfo": { "title": "25% off storewide", "couponCode": "SUMMER25", "discount": "25%", "redeemUrl": "https://acmekitchen.example.com/summer", "termsConditions": "Valid in store only through July 6. Cannot be combined with other offers.", "startDay": "2026-06-22", "endDay": "2026-07-06" }, "postMessage": [ { "site": "GOOGLE", "message": "Celebrate with us: 25% off everything in store this week." } ] }}'La respuesta devuelve los campos de la oferta dentro de socialPost.contextInfo y establece scheduledStartDate y scheduledEndDate a partir del período de validez.
4. Envía una publicación a muchas ubicaciones de forma programada
POST /bulk-posts acepta el mismo input y envía una campaña a cada id de locationIds. Añade postScheduledDates con startDatetime y endDatetime en formato ISO-8601 para publicar más adelante y retirar la publicación automáticamente; en ese caso, la publicación se crea como SCHEDULED. Asigna a cada sitio su propio mensaje cuando el tono sea diferente.
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" } }}'El endpoint de operaciones masivas devuelve la misma estructura que POST /posts, por lo que la respuesta también usa la clave createSocialPost, con status: "SCHEDULED" y ambos ids en locationIds. Un problema de validación devuelve 200 con success: false y un array errors con elementos; los SDK lanzan ValidationError en ese caso. Consulta una campaña masiva con GET /bulk-posts/{postId}, que añade el estado por ubicación.
5. Comprueba el estado de publicación y recopila los enlaces de las publicaciones
GET /posts/{postId} devuelve la campaña dentro de socialPostView. Consúltalo periódicamente después de crearla hasta que socialPostInfo.status deje de ser INPROGRESS y, a continuación, lee publishDetails[] para conocer el resultado de cada sitio y postLinks[] para obtener las URL de las publicaciones activas. Deja intervalos de decenas de segundos entre consultas; un 429 viene acompañado de Retry-After.
curl https://listingsapi.com/api/v4/posts/U29jaWFsUG9zdDo0NDEyMg== \-H "Authorization: API $LISTINGSAPI_KEY"{ "data": { "socialPostView": { "socialPostInfo": { "id": "U29jaWFsUG9zdDo0NDEyMg==", "status": "SUCCESS", "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" } ] }, "socialPostAnalyticsBySite": [ { "site": "GOOGLE", "views": 128, "clicks": 14, "reactions": 3, "shares": 0, "comments": 1 } ] } }}views, clicks, reactions, shares y comments se acumulan en el mismo registro, desglosados por sitio en socialPostAnalyticsBySite. Los SDK devuelven socialPostInfo directamente, por lo que los ejemplos leen post.status.
6. Enumera las publicaciones de una ubicación y elimina una
GET /locations/{locationId}/posts es una página con paginación basada en desplazamiento (page, perPage hasta 50) de todas las campañas que incluyen la ubicación, bajo postsByLocation con records[] y pageInfo.totalRecords. Pasa el id en base64 sin modificarlo; no lo codifiques para URL. No hay ningún filtro por tipo, así que selecciona las ofertas o los eventos a partir de type en tu propio código. Las campañas para varias ubicaciones del paso 4 se enumeran por separado mediante GET /locations/{locationId}/bulk-posts.
DELETE /posts/{postId} elimina la campaña aquí y la retira de todos los sitios a los que llegó. No se puede deshacer, así que confirma primero el id en la lista.
curl "https://listingsapi.com/api/v4/locations/TG9jYXRpb246MTgwMDI4OQ==/posts?page=1&perPage=20" \-H "Authorization: API $LISTINGSAPI_KEY" curl -X DELETE https://listingsapi.com/api/v4/posts/U29jaWFsUG9zdDo0NDEyMg== \-H "Authorization: API $LISTINGSAPI_KEY"Una eliminación devuelve data.deleteSocialPost con success: true y el socialPostId que eliminó. Eliminar un id que ya no existe devuelve, en su lugar, una respuesta errors con data: null, por lo que es fácil detectar una segunda eliminación.
7. Reacciona a los webhooks de publicaciones
Con una URL de webhook configurada en /dashboard/webhooks, cuatro eventos cubren el ciclo de vida de la publicación, todos con la estructura de datos estándar. Se emiten por ubicación, no por campaña: una publicación masiva en 20 ubicaciones produce 20 valores de post_item_id con un único post_id compartido.
function handle(payload: { event: string; location_id: string; data: Record<string, unknown> }) {if (!payload.event.startsWith('local_post.')) return; const key = String(payload.data.post_item_id); // one row per locationswitch (payload.event) { case 'local_post.published': markLive(key, payload.location_id, payload.data.post_external_id); break; case 'local_post.rejected': markRejected(key, payload.location_id, payload.data.rejection_reason); break; case 'local_post.deleted': markDeleted(key); break;}}published_url suele estar ausente, así que sigue consultando periódicamente como en el paso 5 para obtener postLinks si necesitas un enlace en el que se pueda hacer clic. Responde con 200 en un plazo de 5 segundos; no hay reintentos. El receptor y la comprobación de la firma se encuentran en Reacciona a los eventos con webhooks.
Siguientes pasos
- Conecta Google e importa ubicaciones y Conecta páginas de Facebook: el requisito previo para cada sitio.
- Obtén las estadísticas del perfil: visualizaciones y acciones a nivel de perfil junto con los contadores por publicación que se muestran aquí.
- Reacciona a los eventos con webhooks: el receptor al que llegan estos eventos.
- Referencia: Crea una publicación de anuncio, Crea una publicación de evento, Crea una publicación de oferta, Crea una publicación masiva, Obtén una publicación, Obtén las publicaciones de una ubicación, Elimina una publicación.