Publicaciones

Endpoints de publicaciones con cURL/Python/Node: anuncios, eventos, ofertas, programación, campañas por ubicación y masivas, consulta de estado y borrado.

Una publicación es una actualización breve publicada en el perfil de Google Business Profile o en la página de Facebook de una ubicación: un anuncio, un evento con fechas o una oferta con un cupón. Una campaña puede dirigirse a muchas ubicaciones y a ambos sitios a la vez. La publicación es asíncrona, por lo que cada llamada de creación devuelve una respuesta inmediatamente y debes consultar periódicamente el resultado. Los tres endpoints GET funcionan con una clave Read (lectura); para crear y eliminar se necesita una clave Write (escritura).

Resumen de los endpoints

MétodoRutaQué haceReferencia
POST/postsPublica un anuncio, un mensaje sencillo con un botón y una imagen opcionales.Crear una publicación de anuncio
POST/postsPublica un evento, con un título y un intervalo de inicio y fin.Crear una publicación de evento
POST/postsPublica una oferta, con un código de cupón, un descuento y condiciones.Crear una publicación de oferta
GET/posts/{postId}Una campaña con el estado de publicación y los recuentos de interacciones por sitio.Obtener una publicación
GET/locations/{locationId}/postsUna página de campañas dirigidas a una ubicación.Obtener las publicaciones de una ubicación
DELETE/posts/{postId}Elimina permanentemente una campaña y retira su publicación de todos los sitios.Eliminar una publicación
POST/bulk-postsUna campaña distribuida entre muchas ubicaciones.Crear una publicación masiva
GET/bulk-posts/{postId}Una campaña masiva con un desglose de métricas por ubicación.Obtener una publicación masiva por ID
GET/locations/{locationId}/bulk-postsCampañas masivas que incluyen una ubicación, con filtros y ordenación.Obtener las publicaciones masivas de una ubicación

Valores que usarás en todos los casos

Todos los endpoints de creación comparten las cuatro enumeraciones siguientes.

CampoValores aceptados
postTypeANNOUNCEMENT, EVENT, OFFER, COVID19, PRODUCT
postSites y cada siteGOOGLE, FACEBOOK
postCta[].typeBOOK, ORDER, SHOP, LEARN_MORE, SIGN_UP, GET_OFFER
postMediaUrl[].typeIMAGE, VIDEO

Publicar un anuncio

POST /posts

Un anuncio acepta un mensaje y, opcionalmente, un botón y una imagen por sitio. Proporciona una entrada postMessage para cada sitio incluido en postSites. La respuesta devuelve la nueva campaña con status establecido en INPROGRESS, porque la publicación se ejecuta en segundo plano.

Parámetros

NombreEnObligatorioNotas
input.postNamebodySíNombre interno de la campaña, que nunca se muestra a los clientes.
input.locationIdsbodySíArray de ID de ubicación en base64.
input.postTypebodySíANNOUNCEMENT en este caso.
input.postSitesbodySíArray de GOOGLE y FACEBOOK.
input.postMessagebodyNoArray de objetos con site y message.
input.postCtabodyNoArray de objetos con site, type, url.
input.postMediaUrlbodyNoArray de objetos con site, url, type.
input.clientMutationIdbodyNoSe devuelve sin cambios para facilitar la correlación.
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]      }    }  }}

Guarda socialPost.id. Es el ID de la publicación en base64 que necesitan todos los demás endpoints de esta página. Las funciones auxiliares de ambos SDK aceptan una única cadena message y la expanden en una entrada por sitio, por lo que solo tienes que construir manualmente el array postMessage en cURL.

Publicar un evento

POST /posts

Un evento se muestra como una tarjeta con fechas. La programación del propio evento está en postContextInfo. El objeto independiente postScheduledDates controla cuándo aparece y caduca la publicación en sí, lo que suele corresponder al período previo al evento. Google rechaza los eventos sin título, y ambos SDK generan un error de validación antes del envío si falta title.

Parámetros

NombreEnObligatorioNotas
input.postTypebodySíEVENT en este caso.
input.postContextInfo.titlebodySí, en la prácticaTítulo del evento. Google rechaza los eventos que no tienen título.
input.postContextInfo.startDay, endDaybodyNoFechas, YYYY-MM-DD.
input.postContextInfo.startTime, endTimebodyNoCadenas de texto para mostrar, como 10:00am.
input.postScheduledDatesbodyNoObjeto con startDatetime, endDatetime, removalSites.

Todo lo indicado en la tabla del anuncio también se aplica.

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"        }      }    }  }}

El valor contextInfo devuelto es el intervalo del evento. scheduledStartDate y scheduledEndDate describen la programación de la propia publicación, que en este caso coincide con las fechas del evento porque no se envió postScheduledDates.

Publicar una oferta

POST /posts

Una oferta es una tarjeta promocional que contiene un código de cupón, un descuento, un enlace de canje y las condiciones. Todo eso va en postContextInfo, y startDay junto con endDay delimitan la validez de la oferta en lugar de la de la publicación.

Parámetros

NombreEnObligatorioNotas
input.postTypebodySíAquí, OFFER.
input.postContextInfo.titlebodySí, en la prácticaDa nombre a la oferta. Ambos SDK lo requieren.
input.postContextInfo.couponCodebodyNoCódigo que canjea el cliente.
input.postContextInfo.discountbodyNoTexto libre, como 25%.
input.postContextInfo.redeemUrlbodyNoDónde se canjea la oferta.
input.postContextInfo.termsConditionsbodyNoLetra pequeña.
input.postContextInfo.startDay, endDaybodyNoPeriodo de validez, 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 acepta ORDER y Facebook acepta GET_OFFER para los botones de las ofertas. Si una promoción termina antes de tiempo, elimina la campaña en lugar de editarla.

Consultar una publicación y su estado

GET /posts/{postId}

Devuelve el resultado publicado de una campaña aceptada. Responde bajo data.socialPostView con el contenido de la campaña, una entrada publishDetails por sitio, contadores de interacción y los enlaces activos una vez que la publicación se completa correctamente.

Parámetros

NombreEnObligatorioNotas
postIdpathSíIdentificador de la publicación en Base64, obtenido de la respuesta de creación o de una lista.
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 }      ]    }  }}

Hay dos campos de estado relevantes, y cada uno responde a una pregunta distinta.

CampoValorSignificado
socialPostInfo.statusINPROGRESSAceptada; la publicación sigue en curso.
SUCCESSLa publicación ha finalizado. Consulta publishDetails para ver el resultado por sitio.
SCHEDULEDEn espera de que comience el periodo de postScheduledDates.
EXPIREDEl periodo programado ha terminado y la publicación ya no está activa.
publishDetails[].statusPUBLISHINGEse sitio aún no ha respondido.
PUBLISHEDActiva en ese sitio. Busca la URL en postLinks.
ERROREse sitio la ha rechazado. submissionError indica el motivo.

Una campaña puede mostrar SUCCESS mientras uno de sus sitios tiene ERROR, así que comprueba siempre publishDetails en lugar de consultar solo el estado de nivel superior.

Listar las publicaciones de una ubicación

GET /locations/{locationId}/posts

Un historial paginado por desplazamiento de todas las campañas que incluyen una ubicación, bajo data.postsByLocation. Úsalo para revisar lo que ya está activo antes de programar algo nuevo.

Parámetros

NombreEnObligatorioNotas
locationIdpathSíIdentificador en Base64, que se pasa sin cambios.
pagequeryNoNúmero de página empezando por 1; el valor predeterminado es 1.
perPagequeryNoEl valor predeterminado es 10 y no debe superar 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 }    }  }}

Esta ruta no tiene ningún filtro en el servidor. Solo acepta page y perPage e ignora todo lo demás, así que consulta type y status en cada registro y filtra en tu propio código. Para ordenar y filtrar en el servidor, usa el endpoint de listado masivo que se describe a continuación.

Eliminar una publicación

DELETE /posts/{postId}

Es permanente y retira la publicación de todos los sitios en los que se publicó. No se puede deshacer ni existe un endpoint de edición, por lo que corregir una publicación activa implica eliminarla y publicar otra que la sustituya.

Parámetros

NombreEnObligatorioNotas
postIdpathSíIdentificador de la publicación en Base64.
clientMutationIdbodyNoOpcional; se devuelve sin cambios para correlacionar la solicitud y la respuesta.
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=="    }  }}

Eliminar una publicación desconocida o ya eliminada devuelve una respuesta de error con data establecido en null en lugar de success: false.

Publicar una campaña en muchas ubicaciones

POST /bulk-posts

El mismo contrato que POST /posts, diseñado para conjuntos de ubicaciones: incluye todas las ubicaciones en locationIds y una sola campaña las abarcará todas. La respuesta se devuelve bajo la clave createSocialPost, igual que en POST /posts.

Parámetros

NombreEnObligatorioNotas
input.postNamebodySíNombre interno de la campaña.
input.locationIdsbodySíTodos los identificadores en base64 de las ubicaciones en las que publicar.
input.postTypebodySíEVENT, OFFER y PRODUCT también necesitan postContextInfo o products.
input.postSitesbodySíGOOGLE, FACEBOOK o ambos.
input.postScheduledDatesbodyNostartDatetime y endDatetime en formato ISO 8601. Crea la publicación como SCHEDULED.
input.componentIdentifierbodyNoTu propia referencia, útil como filtro más adelante.
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]      }    }  }}

Las funciones auxiliares de ambos SDK establecen Google y Facebook como valor predeterminado de sites, expanden por ti un mapa de mensajes por sitio y codifican los identificadores numéricos de las ubicaciones, por lo que una publicación masiva suele ser la llamada más corta en cualquiera de los dos SDK.

Consultar una campaña masiva

GET /bulk-posts/{postId}

El equivalente masivo de GET /posts/{postId}. Devuelve la misma estructura, con publishedLocationsCount mayor que uno y un desglose de analíticas por tienda en socialPostAnalyticsByLocationId.

Parámetros

NombreEnObligatorioNotas
postIdpathSíID de publicación masiva en Base64, no un entero sin codificar.
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 suma los totales de la campaña en todas las tiendas, mientras que socialPostAnalyticsByLocationId desglosa las mismas cifras por tienda. Revisa los valores de status de cada tienda para encontrar cualquier ubicación en la que haya fallado un sitio.

Listar campañas masivas de una ubicación

GET /locations/{locationId}/bulk-posts

Usa paginación por desplazamiento bajo data.rollupSocialPosts. A diferencia de la lista por ubicación, esta ruta sí admite filtros y ordenación, lo que la convierte en el endpoint adecuado para un panel de campañas.

Parámetros

NombreEnObligatorioNotas
locationIdpathSíID en Base64, que se pasa sin cambios.
page, perPagequeryNoPaginación por desplazamiento.
filtersqueryNoCodificado en JSON, por type, status, creationDateRange o siteUrls.
sortFieldsqueryNoCodificado en JSON, como el campo created_at con el orden Descending.
locationIdsqueryNoArray codificado en JSON con los ID de ubicaciones adicionales que se incluirán.
componentIdentifierqueryNoLimita los resultados a las campañas que creó tu integración.

Los campos por los que puedes ordenar son name, type, status, created_at, schedule_start_date, schedule_end_date, client_name, clicks, views y 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 }    }  }}

Ambos SDK codifican en JSON los objetos filters y sortFields por ti, así que pásalos como diccionarios u objetos nativos.

Errores que verás

Código o estadoSignificadoSolución
400: falta la variable $inputEl cuerpo no se encapsuló en input.Anida todos los campos bajo una única clave input.
SY90016 como estado HTTP 400Se usó una clave Read en una operación de creación o eliminación.Usa una clave Write.
401Clave ausente o no válida.Envía Authorization: API <YOUR_API_KEY>.
429 con Retry-AfterDemasiadas llamadas, a menudo por consultar repetidamente el estado de una publicación.Espera los segundos indicados. Consulta Límites de solicitudes.
200 con success: falseLa validación falló.Lee errors en data.createSocialPost.
per_page must not exceed 50perPage supera el límite en la lista de ubicaciones.Solicita 50 o menos.
tag is not a supported parameter for this querySe envió tag a la ruta de publicaciones por ubicación.Envía solo page y perPage.

La lista completa está en Códigos de error.

Próximos pasos