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.

Campo inputPropósito
postNameNombre interno de la campaña. No se muestra a los clientes.
locationIdsIds de ubicación en base64.
postTypeANNOUNCEMENT, EVENT o OFFER.
postSitesGOOGLE, FACEBOOK o ambos.
postMessage[]Un { site, message } por sitio en postSites.
postCta[]{ site, type, url } opcional. type es BOOK, ORDER, SHOP, LEARN_MORE, SIGN_UP o GET_OFFER.
postMediaUrl[]{ site, url, type } opcional con type IMAGE o VIDEO.
postContextInfoFechas del evento o detalles de la oferta. Obligatorio para EVENT y OFFER.

1. Publica un anuncio

Un anuncio admite un mensaje, un botón opcional y una imagen opcional.

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://acmekitchen.example.com/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. 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.

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, 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.

Offer
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.

Bulk post
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.

Post status
curl https://listingsapi.com/api/v4/posts/U29jaWFsUG9zdDo0NDEyMg== \-H "Authorization: API $LISTINGSAPI_KEY"
JSON
{  "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 }      ]    }  }}
CampoValoresSignificado
socialPostInfo.statusINPROGRESS, SCHEDULED, SUCCESS, EXPIREDLa campaña en su conjunto. SUCCESS significa que el proceso de publicación ha finalizado, no que todos los sitios la hayan aceptado.
publishDetails[].statusPUBLISHING, PUBLISHED, ERRORUna entrada por sitio. En caso de ERROR, submissionError indica el motivo.
postLinks[]{ site, link }La URL activa por sitio, una vez publicada.

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.

List and delete
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.

EventoSe emite cuandoCampo adicional en data
local_post.createdEl elemento está en la cola para Google. Aún no está publicado.
local_post.publishedGoogle lo ha aceptado y ya está publicado.post_external_id, el nombre del recurso en Google.
local_post.rejectedGoogle lo ha rechazado o el envío ha fallado.rejection_reason.
local_post.deletedEl elemento se ha eliminado.post_external_id.
Route local_post events
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