المنشورات

كل نقاط نهاية المنشورات بأمثلة cURL وPython وNode: إعلانات وفعاليات وعروض، جدولة وحملات لكل موقع وجماعية، استعلام دوري عن الحالة وحذف.

المنشور هو تحديث قصير يُنشر على Google Business Profile أو صفحة Facebook الخاصة بموقع: إعلان، أو فعالية محددة التاريخ، أو عرض يتضمن قسيمة. يمكن لحملة واحدة استهداف مواقع متعددة وكلتا المنصتين في آن واحد. النشر غير متزامن، لذا يعيد كل استدعاء إنشاء استجابته فورًا، وتستعلم دوريًا عن النتيجة. تعمل نقاط النهاية الثلاث GET باستخدام مفتاح Read (قراءة)؛ ويتطلب الإنشاء والحذف مفتاح Write (كتابة).

نقاط النهاية في لمحة

الطريقةالمسارالوظيفةالمرجع
POST/postsنشر إعلان، وهو رسالة بسيطة مع زر وصورة اختياريين.إنشاء منشور إعلان
POST/postsنشر فعالية بعنوان وفترة محددة ببداية ونهاية.إنشاء منشور فعالية
POST/postsنشر عرض يتضمن رمز قسيمة وخصمًا وشروطًا.إنشاء منشور عرض
GET/posts/{postId}حملة واحدة مع حالة النشر وأعداد التفاعلات لكل منصة.جلب منشور
GET/locations/{locationId}/postsصفحة من الحملات التي تستهدف موقعًا واحدًا.جلب منشورات موقع
DELETE/posts/{postId}حذف حملة نهائيًا وإلغاء نشرها في كل مكان.حذف منشور
POST/bulk-postsحملة واحدة موزعة على مواقع متعددة.إنشاء منشور جماعي
GET/bulk-posts/{postId}حملة جماعية واحدة مع تحليلات مفصلة لكل موقع.جلب منشور جماعي حسب المعرّف
GET/locations/{locationId}/bulk-postsالحملات الجماعية التي تشمل موقعًا واحدًا، مع خيارات التصفية والترتيب.جلب المنشورات الجماعية لموقع

القيم التي ستستخدمها في كل موضع

تستخدم جميع نقاط نهاية الإنشاء أنواع التعداد الأربعة أدناه.

الحقلالقيم المقبولة
postTypeANNOUNCEMENT, EVENT, OFFER, COVID19, PRODUCT
postSites وكل siteGOOGLE, FACEBOOK
postCta[].typeBOOK, ORDER, SHOP, LEARN_MORE, SIGN_UP, GET_OFFER
postMediaUrl[].typeIMAGE, VIDEO

نشر إعلان

POST /posts

يتطلب الإعلان رسالة، ويمكن اختياريًا إضافة زر واحد وصورة واحدة لكل منصة. وفّر عنصر postMessage واحدًا لكل منصة مدرجة في postSites. تعيد الاستجابة الحملة الجديدة مع تعيين status إلى INPROGRESS، لأن النشر يجري في الخلفية.

المعاملات

الاسمالموضعمطلوبملاحظات
input.postNamebodyنعماسم داخلي للحملة لا يُعرض للعملاء مطلقًا.
input.locationIdsbodyنعممصفوفة معرّفات مواقع بترميز base64.
input.postTypebodyنعمANNOUNCEMENT هنا.
input.postSitesbodyنعممصفوفة من GOOGLE وFACEBOOK.
input.postMessagebodyلامصفوفة كائنات تتضمن site وmessage.
input.postCtabodyلامصفوفة كائنات تتضمن site وtype وurl.
input.postMediaUrlbodyلامصفوفة كائنات تتضمن site وurl وtype.
input.clientMutationIdbodyلاتُعاد دون تغيير لأغراض الربط.
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]      }    }  }}

احتفظ بـ socialPost.id. فهو معرّف المنشور بترميز base64 الذي تحتاج إليه كل نقطة نهاية أخرى هنا. تأخذ كلتا الدالتين المساعدتين في SDK سلسلة نصية واحدة message وتحوّلانها إلى عنصر واحد لكل منصة، لذا لا تحتاج إلى إنشاء مصفوفة postMessage يدويًا إلا في cURL.

نشر فعالية

POST /posts

تُعرض الفعالية كبطاقة محددة التاريخ. يوجد الجدول الزمني للفعالية نفسها في postContextInfo. يتحكم الكائن المنفصل postScheduledDates في موعد ظهور المنشور نفسه وانتهاء صلاحيته، وتكون هذه الفترة عادةً هي الفترة التي تسبق الفعالية. ترفض Google أي فعالية بلا عنوان، وتُصدر كلتا حزمتَي SDK خطأ تحقق قبل الإرسال إذا كان title مفقودًا.

المعاملات

الاسمالموضعمطلوبملاحظات
input.postTypebodyنعمEVENT هنا.
input.postContextInfo.titlebodyنعم عمليًاعنوان الفعالية. ترفض Google الفعاليات التي لا تتضمن عنوانًا.
input.postContextInfo.startDay, endDaybodyلاتواريخ، YYYY-MM-DD.
input.postContextInfo.startTime, endTimebodyلاسلاسل نصية للعرض مثل 10:00am.
input.postScheduledDatesbodyلاكائن يتضمن startDatetime وendDatetime وremovalSites.

ينطبق أيضًا كل ما ورد في جدول الإعلان.

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

تمثل قيمة contextInfo المُعادة الفترة الزمنية للفعالية. يصف scheduledStartDate وscheduledEndDate الجدول الزمني للمنشور نفسه، الذي يطابق تواريخ الفعالية هنا لأن postScheduledDates لم يُرسل.

نشر عرض

POST /posts

العرض بطاقة ترويجية تتضمن رمز قسيمة وخصمًا ورابطًا للاستفادة من العرض وشروطًا. توضع كل هذه التفاصيل في postContextInfo، ويحدد startDay مع endDay فترة صلاحية العرض لا المنشور.

المعاملات

الاسمالموضعمطلوبملاحظات
input.postTypebodyنعمOFFER هنا.
input.postContextInfo.titlebodyنعم عمليًايحدد اسم العرض. تتطلبه كلتا حزمتَي SDK.
input.postContextInfo.couponCodebodyلاالرمز الذي يستخدمه العميل للاستفادة من العرض.
input.postContextInfo.discountbodyلانص حر مثل 25%.
input.postContextInfo.redeemUrlbodyلاالمكان الذي يمكن فيه الاستفادة من العرض.
input.postContextInfo.termsConditionsbodyلاالشروط التفصيلية.
input.postContextInfo.startDay, endDaybodyلافترة الصلاحية، 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 القيمة ORDER ويستخدم Facebook القيمة GET_OFFER لأزرار العروض. إذا انتهى عرض ترويجي مبكرًا، فاحذف الحملة بدلًا من تعديلها.

قراءة منشور واحد وحالته

GET /posts/{postId}

يعيد نتيجة النشر لحملة مقبولة. تتضمن الاستجابة، ضمن data.socialPostView، محتوى الحملة ومدخلًا في publishDetails لكل موقع وعدادات التفاعل وروابط المنشور على المنصات بعد نجاح النشر.

المعاملات

الاسمالموضعمطلوبملاحظات
postIdpathنعممعرّف المنشور بترميز Base64 من استجابة الإنشاء أو من قائمة.
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 }      ]    }  }}

هناك حقلان مهمان للحالة، ويجيب كل منهما عن سؤال مختلف.

الحقلالقيمةالمعنى
socialPostInfo.statusINPROGRESSقُبل المنشور، ولا يزال النشر جاريًا.
SUCCESSاكتمل النشر. اقرأ publishDetails للاطلاع على النتيجة لكل موقع.
SCHEDULEDفي انتظار بدء الفترة postScheduledDates.
EXPIREDانتهت الفترة المجدولة ولم يعد المنشور متاحًا.
publishDetails[].statusPUBLISHINGلم يرد ذلك الموقع بعد.
PUBLISHEDالمنشور متاح على ذلك الموقع. ابحث في postLinks عن عنوان URL.
ERRORرفضه ذلك الموقع. يوضح submissionError السبب.

قد تكون حالة الحملة SUCCESS بينما تكون حالة أحد المواقع ضمنها ERROR، لذا تحقق دائمًا من publishDetails بدلًا من الاكتفاء بالحالة على المستوى الأعلى.

عرض قائمة المنشورات الخاصة بموقع

GET /locations/{locationId}/posts

سجل مقسّم إلى صفحات باستخدام الإزاحة لكل حملة تشمل موقعًا واحدًا، ضمن data.postsByLocation. استخدمه لتدقيق ما هو منشور بالفعل قبل جدولة محتوى جديد.

المعاملات

الاسمالموضعمطلوبملاحظات
locationIdpathنعممعرّف بترميز Base64، يُمرَّر دون تغيير.
pagequeryلارقم الصفحة، يبدأ من 1، وقيمته الافتراضية 1.
perPagequeryلاالقيمة الافتراضية 10، ويجب ألا تتجاوز 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 }    }  }}

لا يدعم هذا المسار التصفية على جانب الخادم. يقبل فقط page وperPage، ويتجاهل أي شيء آخر، لذا اقرأ type وstatus في كل سجل ونفّذ التصفية في التعليمات البرمجية الخاصة بك. وللفرز والتصفية على جانب الخادم، استخدم بدلًا من ذلك نقطة نهاية عرض القائمة المجمّعة أدناه.

حذف منشور

DELETE /posts/{postId}

الحذف دائم، ويلغي نشر المنشور من كل موقع وصل إليه. لا يمكن التراجع عن الحذف ولا توجد نقطة نهاية للتعديل، لذا فإن تصحيح منشور متاح يعني حذفه ونشر بديل له.

المعاملات

الاسمالموضعمطلوبملاحظات
postIdpathنعممعرّف المنشور بترميز Base64.
clientMutationIdbodyلااختياري، ويُعاد دون تغيير لربط الاستجابة بالطلب.
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=="    }  }}

تؤدي محاولة حذف منشور غير معروف أو محذوف بالفعل إلى إرجاع استجابة خطأ تكون فيها قيمة data مضبوطة على null بدلًا من success: false.

نشر حملة واحدة عبر مواقع متعددة

POST /bulk-posts

يستخدم مواصفات POST /posts نفسها، وهو مصمم لمجموعات المواقع: أدرج كل موقع في locationIds لتشملها جميعًا حملة واحدة. توضع الاستجابة تحت المفتاح createSocialPost، كما في POST /posts.

المعاملات

الاسمالموضعمطلوبملاحظات
input.postNamebodyنعمالاسم الداخلي للحملة.
input.locationIdsbodyنعمجميع معرّفات المواقع المراد النشر إليها بترميز base64.
input.postTypebodyنعمتتطلب EVENT وOFFER وPRODUCT أيضًا postContextInfo أو products.
input.postSitesbodyنعمGOOGLE أو FACEBOOK أو كلاهما.
input.postScheduledDatesbodyلاstartDatetime وendDatetime بتنسيق ISO 8601. ينشئ المنشور بالحالة SCHEDULED.
input.componentIdentifierbodyلامرجع خاص بك، يفيد في التصفية لاحقًا.
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]      }    }  }}

تضبط الدالتان المساعدتان في حزمتَي SDK القيمة الافتراضية لـ sites على Google وFacebook، وتوسّعان لك خريطة الرسائل المخصصة لكل موقع، وترمّزان معرّفات المواقع الرقمية، لذا يكون النشر المجمّع عادةً أقصر استدعاء في أيٍّ من حزمتَي SDK.

قراءة حملة جماعية واحدة

GET /bulk-posts/{postId}

النظير الجماعي لـ GET /posts/{postId}. يُرجع البنية نفسها، مع قيمة publishedLocationsCount أكبر من 1 وتفصيل للتحليلات حسب المتجر في socialPostAnalyticsByLocationId.

المعاملات

الاسمالموضعمطلوبملاحظات
postIdpathنعممعرّف منشور جماعي بترميز Base64، وليس عددًا صحيحًا خامًا.
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 إجماليات الحملة عبر جميع المتاجر، بينما يفصّل socialPostAnalyticsByLocationId الأرقام نفسها حسب المتجر. افحص قيم status لكل متجر للعثور على أي متجر فشل النشر له على إحدى المنصات.

سرد الحملات الجماعية في موقع

GET /locations/{locationId}/bulk-posts

تُقسَّم النتائج إلى صفحات باستخدام الإزاحة ضمن data.rollupSocialPosts. بخلاف القائمة الخاصة بكل موقع، يقبل هذا المسار عوامل التصفية والفرز، مما يجعله نقطة النهاية المناسبة للوحة التحكم بالحملات.

المعاملات

الاسمالموضعمطلوبملاحظات
locationIdpathنعممعرّف بترميز Base64، يُمرَّر دون تغيير.
page, perPagequeryلاتقسيم النتائج إلى صفحات باستخدام الإزاحة.
filtersqueryلابترميز JSON، حسب type أو status أو creationDateRange أو siteUrls.
sortFieldsqueryلابترميز JSON، مثل الحقل created_at بترتيب Descending.
locationIdsqueryلامصفوفة بترميز JSON تضم معرّفات المواقع الإضافية المراد تضمينها.
componentIdentifierqueryلايقصر النتائج على الحملات التي أنشأها تكاملك.

الحقول التي يمكن الفرز حسبها هي name وtype وstatus وcreated_at وschedule_start_date وschedule_end_date وclient_name وclicks وviews و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 }    }  }}

تتولى كلتا حزمتَي SDK ترميز الكائنين filters وsortFields بصيغة JSON نيابةً عنك، لذا مرّرهما كقواميس أو كائنات أصلية في لغة البرمجة.

الأخطاء التي ستواجهها

الرمز أو الحالةالمعنىالحل
400 مع غياب المتغير $inputلم يُغلَّف جسم الطلب داخل input.ضع جميع الحقول تحت مفتاح واحد هو input.
SY90016 بحالة HTTP 400استُخدم مفتاح Read في عملية إنشاء أو حذف.استخدم مفتاح Write.
401المفتاح مفقود أو غير صالح.أرسل Authorization: API <YOUR_API_KEY>.
429 مع Retry-Afterعدد الاستدعاءات كبير جدًا، وغالبًا ما ينتج ذلك عن الاستعلام الدوري عن حالة منشور.انتظر عدد الثواني المحدد. راجع حدود معدل الطلبات.
200 مع success: falseفشل التحقق من صحة البيانات.اقرأ errors في data.createSocialPost.
per_page must not exceed 50تجاوزت قيمة perPage الحد الأقصى في قائمة المواقع.اطلب 50 أو أقل.
tag is not a supported parameter for this queryأُرسل tag إلى مسار المنشورات الخاص بكل موقع.أرسل page وperPage فقط.

القائمة الكاملة في رموز الأخطاء.

الخطوات التالية