> ## Documentation Index
> Fetch the complete documentation index at: https://developers.partoo.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Post Management

> Create and publish engaging content across Google, Facebook, Instagram, and Store Locator to boost your business visibility

<Info>
  Posts allow businesses to share announcements, offers, events, and updates directly on connected platforms. Once published, your content appears on Google, Facebook, Instagram, and your Store Locator, helping you engage with customers and improve your online presence.

  For Google: When a business is connected to a Google location (action performed in Partoo app on [Partner Connection](https://app.partoo.co/settings/platforms/connection) page), existing Google Posts are automatically imported and kept in sync as Partoo Posts. If the business is later disconnected, the Google Posts published on Google will be deleted from Partoo database.
</Info>

## Prerequisites

Before using the Posts API, ensure you have:

* Active subscription to **Posts** plan
* [API key](/guides/api/resources/settings/security_and_api_key_usage) with appropriate role (Business Manager, Group Manager, Organization Manager, or Organization Admin)
* Platform Integrations: Businesses must be connected to target platforms

  <Tabs>
    <Tab title="Google My Business">
      * Businesses have a `google_location_id` connected
      * `DIFFUSION` feature enabled
    </Tab>

    <Tab title="Facebook">
      * Businesses have a `facebook_page_id` connected
      * `DIFFUSION` feature enabled
    </Tab>

    <Tab title="Instagram">
      * Businesses have a `facebook_page_id` with Instagram linked
      * `DIFFUSION` feature enabled
    </Tab>

    <Tab title="Store Locator">
      * No platform connection required
      * `STORE_LOCATOR_POSTS` feature enabled
    </Tab>
  </Tabs>

<Tip>
  If you need help with any of these prerequisites, please contact your Account Manager who can guide you through the process.
</Tip>

## Understanding Post Types

Different post types serve different purposes and have specific requirements:

<Tabs>
  <Tab title="News Posts">
    News posts are announcements and updates about your business.

    **Required fields:**

    * `summary`: Main content (max 1500 chars for Google, 10000 for Facebook, 2200 for Instagram)

    **Supported platforms:**

    * ✅ Google
    * ✅ Facebook
    * ✅ Instagram (requires at least 1 image)
    * ✅ Store Locator

    <Info>
      In API requests, use `"post_type": "news"` to create news posts.
    </Info>
  </Tab>

  <Tab title="Event Posts">
    Event posts promote time-specific events at your business.

    **Required fields:**

    * `title`: Event name (max 58 chars)
    * `start_at`: Event start date/time
    * `end_at`: Event end date/time
    * `summary`: Event description (optional, max 1500 chars)

    **Supported platforms:**

    * ✅ Google only
    * ❌ Facebook (not supported)
    * ❌ Instagram (not supported)
    * ❌ Store Locator (not supported)

    <Warning>
      Event end time must be in the future, and schedule time (if provided) must be before the end time.
    </Warning>

    <Info>
      In API requests, use `"post_type": "event"` to create event posts.
    </Info>
  </Tab>

  <Tab title="Offer Posts">
    Offer posts promote deals and promotions with validity periods.

    **Required fields:**

    * `title`: Offer name (max 58 chars)
    * `start_at`: Offer start date/time
    * `end_at`: Offer end date/time
    * `summary`: Offer description (optional, max 1500 chars)
    * `coupon_code`: Discount code (optional, max 58 chars)
    * `offer_link`: Link to offer details (optional)
    * `offer_terms`: Terms and conditions (optional, max 5000 chars)

    **Supported platforms:**

    * ✅ Google only
    * ❌ Facebook (not supported)
    * ❌ Instagram (not supported)
    * ❌ Store Locator (not supported)

    <Warning>
      Offer end time must be in the future, and schedule time (if provided) must be before the end time.
    </Warning>

    <Info>
      In API requests, use `"post_type": "offer"` to create offer posts.
    </Info>
  </Tab>
</Tabs>

## Creating Posts

All roles can create posts for the businesses they have access to. Each business gets its own individual post.

<Info>
  **Who can create posts for multiple businesses:**

  * **Organization Admin & Organization Manager**: All businesses in the organization
  * **Group Manager**: All businesses in their assigned groups
  * **Business Manager**: Their assigned businesses (can be one or multiple)

  **About bulk\_id**: When you create posts for more than 1 business in a single request, the system assigns a shared `bulk_id` to group them together. However, only Organization Admin and Organization Manager can use the [bulk management endpoints](#managing-existing-bulks) to update/delete all posts in a bulk at once.
</Info>

<AccordionGroup>
  <Accordion title="Single Business Post">
    ## Creating a post for one business

    All roles (Business Manager, Group Manager, Organization Manager, Organization Admin) can create posts for individual businesses they have access to.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST 'https://api.partoo.co/v2/posts' \
        -H 'Authorization: Bearer YOUR_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "business_search": {
            "business__in": ["5409c35a97bbc544d8e26737"]
          },
          "publishers": ["google_my_business", "facebook"],
          "data": {
            "post_type": "news",
            "summary": "We are excited to announce our new summer menu! Come try our fresh seasonal dishes. 🌞"
          }
        }'
      ```

      ```python Python theme={null}
      import requests

      response = requests.post(
        'https://api.partoo.co/v2/posts',
        headers={
          'Authorization': 'Bearer YOUR_API_KEY',
          'Content-Type': 'application/json'
        },
        json={
          'business_search': {
            'business__in': ['5409c35a97bbc544d8e26737']
          },
          'publishers': ['google_my_business', 'facebook'],
          'data': {
            'post_type': 'news',
            'summary': 'We are excited to announce our new summer menu! Come try our fresh seasonal dishes. 🌞'
          }
        }
      )

      result = response.json()
      print(f"Post created: {result['ids']}")
      ```

      ```javascript Node.js theme={null}
      const response = await fetch('https://api.partoo.co/v2/posts', {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer YOUR_API_KEY',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          business_search: {
            business__in: ['5409c35a97bbc544d8e26737']
          },
          publishers: ['google_my_business', 'facebook'],
          data: {
            post_type: 'news',
            summary: 'We are excited to announce our new summer menu! Come try our fresh seasonal dishes. 🌞'
          }
        })
      });

      const result = await response.json();
      console.log(`Post created: ${result.ids}`);
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Multiple Businesses Post (Bulk Post)">
    ## Creating posts for multiple businesses

    Each business gets its own individual post. When targeting more than one business, all posts share a `bulk_id` for grouping purposes.

    <Tabs>
      <Tab title="By Group">
        Group Managers, Organization Managers, and Organization Admins can create posts for businesses in specific groups:

        <CodeGroup>
          ```bash cURL theme={null}
          curl -X POST 'https://api.partoo.co/v2/posts' \
            -H 'Authorization: Bearer YOUR_API_KEY' \
            -H 'Content-Type: application/json' \
            -d '{
              "business_search": {
                "group_ids__in": [111, 222]
              },
              "publishers": ["google_my_business"],
              "data": {
                "post_type": "news",
                "summary": "All our locations will be closed on December 25th for the holidays. Happy holidays!"
              }
            }'
          ```

          ```python Python theme={null}
          import requests

          response = requests.post(
            'https://api.partoo.co/v2/posts',
            headers={
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            json={
              'business_search': {
                'group_ids__in': [111, 222]
              },
              'publishers': ['google_my_business'],
              'data': {
                'post_type': 'news',
                'summary': 'All our locations will be closed on December 25th for the holidays. Happy holidays!'
              }
            }
          )

          result = response.json()
          print(f"Created {len(result['data'])} posts with bulk_id")
          ```

          ```javascript Node.js theme={null}
          const response = await fetch('https://api.partoo.co/v2/posts', {
            method: 'POST',
            headers: {
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              business_search: {
                group_ids__in: [111, 222]
              },
              publishers: ['google_my_business'],
              data: {
                post_type: 'news',
                summary: 'All our locations will be closed on December 25th for the holidays. Happy holidays!'
              }
            })
          });

          const result = await response.json();
          console.log(`Created ${result.data.length} posts with bulk_id`);
          ```
        </CodeGroup>
      </Tab>

      <Tab title="By Organization">
        Organization Admins and Organization Managers can create posts for all businesses in the organization:

        <CodeGroup>
          ```bash cURL theme={null}
          curl -X POST 'https://api.partoo.co/v2/posts' \
            -H 'Authorization: Bearer YOUR_API_KEY' \
            -H 'Content-Type: application/json' \
            -d '{
              "business_search": {
                "org_id__in": [789]
              },
              "publishers": ["google_my_business"],
              "data": {
                "post_type": "news",
                "summary": "Extended hours for all locations during the holiday season!"
              }
            }'
          ```

          ```python Python theme={null}
          import requests

          response = requests.post(
            'https://api.partoo.co/v2/posts',
            headers={
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            json={
              'business_search': {
                'org_id__in': [789]
              },
              'publishers': ['google_my_business'],
              'data': {
                'post_type': 'news',
                'summary': 'Extended hours for all locations during the holiday season!'
              }
            }
          )

          result = response.json()
          print(f"Created {len(result['data'])} posts across organization")
          ```

          ```javascript Node.js theme={null}
          const response = await fetch('https://api.partoo.co/v2/posts', {
            method: 'POST',
            headers: {
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              business_search: {
                org_id__in: [789]
              },
              publishers: ['google_my_business'],
              data: {
                post_type: 'news',
                summary: 'Extended hours for all locations during the holiday season!'
              }
            })
          });

          const result = await response.json();
          console.log(`Created ${result.data.length} posts across organization`);
          ```
        </CodeGroup>
      </Tab>
    </Tabs>

    <Info>
      **What happens:**

      * Each business gets its own separate Post record
      * All posts share the same `bulk_id` (assigned internally)
      * To get the `bulk_id`, query one of the created posts using its `post_id`
      * Only ORG\_ADMIN/ORG\_MANAGER can use [bulk management endpoints](#managing-existing-bulks) to update/delete all posts at once
    </Info>
  </Accordion>

  <Accordion title="Create an Event Post">
    ## Publishing event posts with time constraints

    Event posts are perfect for promoting time-specific events at your business locations.

    <Steps>
      <Step title="Define event details">
        Prepare all required event information:

        * Event title (max 58 characters)
        * Start and end dates/times
        * Event description (optional)
        * Media files (optional)
      </Step>

      <Step title="Create the event post">
        <CodeGroup>
          ```bash cURL theme={null}
          curl -X POST 'https://api.partoo.co/v2/posts' \
            -H 'Authorization: Bearer YOUR_API_KEY' \
            -H 'Content-Type: application/json' \
            -d '{
              "business_search": {
                "business__in": ["5409c35a97bbc544d8e26737"]
              },
              "publishers": ["google_my_business"],
              "data": {
                "post_type": "event",
                "title": "Live Jazz Night",
                "summary": "Join us for an evening of jazz with local popular artists. Free entry!",
                "start_at": "2024-12-20T19:00:00Z",
                "end_at": "2024-12-20T23:00:00Z"
              }
            }'
          ```

          ```python Python theme={null}
          import requests
          from datetime import datetime, timedelta

          # Event starts in 30 days at 7 PM and ends at 11 PM
          start_time = datetime.now() + timedelta(days=30)
          start_time = start_time.replace(hour=19, minute=0, second=0)
          end_time = start_time.replace(hour=23)

          response = requests.post(
            'https://api.partoo.co/v2/posts',
            headers={
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            json={
              'business_search': {
                'business__in': ['5409c35a97bbc544d8e26737']
              },
                'publishers': ['google_my_business'],
              'data': {
                'post_type': 'event',
                'title': 'Live Jazz Night',
                'summary': 'Join us for an evening of jazz with local popular artists. Free entry!',
                'start_at': start_time.strftime('%Y-%m-%dT%H:%M:%SZ'),
                'end_at': end_time.strftime('%Y-%m-%dT%H:%M:%SZ')
              }
            }
          )
          ```

          ```javascript Node.js theme={null}
          // Event starts in 30 days at 7 PM and ends at 11 PM
          const startTime = new Date();
          startTime.setDate(startTime.getDate() + 30);
          startTime.setHours(19, 0, 0, 0);

          const endTime = new Date(startTime);
          endTime.setHours(23, 0, 0, 0);

          const response = await fetch('https://api.partoo.co/v2/posts', {
            method: 'POST',
            headers: {
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              business_search: {
                business__in: ['5409c35a97bbc544d8e26737']
              },
              publishers: ['google_my_business'],
              data: {
                post_type: 'event',
                title: 'Live Jazz Night',
                summary: 'Join us for an evening of jazz with local popular artists. Free entry!',
                start_at: startTime.toISOString(),
                end_at: endTime.toISOString()
              }
            })
          });
          ```
        </CodeGroup>

        <Warning>
          Event posts are **only supported** on Google. Do not include Facebook, Instagram, or Store Locator as publishers for event posts.
        </Warning>
      </Step>
    </Steps>

    <Check>
      The post will be created with state `pending` and will transition to `live` once published. This process is asynchronous and may take a few minutes.
    </Check>
  </Accordion>

  <Accordion title="Create an Offer Post">
    ## Publishing promotional offers with validity periods

    Offer posts help you promote special deals, discounts, and limited-time promotions.

    <Steps>
      <Step title="Prepare offer details">
        Gather all offer information:

        * Offer title (max 58 characters)
        * Validity period (start and end dates/times)
        * Offer description (optional)
        * Coupon code (optional, max 58 characters)
        * Terms and conditions (optional, max 5000 characters)
      </Step>

      <Step title="Create the offer post">
        <CodeGroup>
          ```bash cURL theme={null}
          curl -X POST 'https://api.partoo.co/v2/posts' \
            -H 'Authorization: Bearer YOUR_API_KEY' \
            -H 'Content-Type: application/json' \
            -d '{
              "business_search": {
                "business__in": ["5409c35a97bbc544d8e26737"]
              },
              "publishers": ["google_my_business"],
              "data": {
                "post_type": "offer",
                "title": "Summer Sale - 20% Off",
                "summary": "Enjoy 20% off all summer items! Limited time only.",
                "coupon_code": "SUMMER20",
                "offer_terms": "Valid on all summer collection items. Cannot be combined with other offers. Expires August 31st.",
                "start_at": "2024-06-01T00:00:00Z",
                "end_at": "2024-08-31T23:59:59Z"
              }
            }'
          ```

          ```python Python theme={null}
          import requests
          from datetime import datetime

          response = requests.post(
            'https://api.partoo.co/v2/posts',
            headers={
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            json={
              'business_search': {
                'business__in': ['5409c35a97bbc544d8e26737']
              },
                'publishers': ['google_my_business'],
              'data': {
                'post_type': 'offer',
                'title': 'Summer Sale - 20% Off',
                'summary': 'Enjoy 20% off all summer items! Limited time only.',
                'coupon_code': 'SUMMER20',
                'offer_terms': 'Valid on all summer collection items. Cannot be combined with other offers. Expires August 31st.',
                'start_at': '2024-06-01T00:00:00Z',
                'end_at': '2024-08-31T23:59:59Z'
              }
            }
          )

          if response.status_code == 200:
              result = response.json()
              print(f"Offer created successfully: {result['ids']}")
          ```

          ```javascript Node.js theme={null}
          const response = await fetch('https://api.partoo.co/v2/posts', {
            method: 'POST',
            headers: {
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              business_search: {
                business__in: ['5409c35a97bbc544d8e26737']
              },
              publishers: ['google_my_business'],
              data: {
                post_type: 'offer',
                title: 'Summer Sale - 20% Off',
                summary: 'Enjoy 20% off all summer items! Limited time only.',
                coupon_code: 'SUMMER20',
                offer_terms: 'Valid on all summer collection items. Cannot be combined with other offers. Expires August 31st.',
                start_at: '2024-06-01T00:00:00Z',
                end_at: '2024-08-31T23:59:59Z'
              }
            })
          });

          const result = await response.json();
          console.log(`Offer created successfully: ${result.ids}`);
          ```
        </CodeGroup>

        <Warning>
          Offer posts are **only supported** on Google. Do not include Facebook, Instagram, or Store Locator as publishers for offer posts.
        </Warning>
      </Step>
    </Steps>
  </Accordion>

  <Accordion title="Schedule Posts for Future Publishing">
    ## Publishing posts at a specific future time

    Scheduled posts allow you to prepare content in advance and have it automatically published at a specific time.

    <Steps>
      <Step title="Understand scheduling vs validity">
        It's important to distinguish between two different time concepts:

        * **`schedule_time`**: When the post should be **published** to the platforms
        * **`start_at` / `end_at`**: The validity period of the post content (for events and offers)

        <Info>
          The `schedule_time` must be at least 2 hours in the future from the current time.
          You can schedule a news post, event post, or offer post. The `schedule_time` controls publication, while `start_at`/`end_at` control content validity.
        </Info>
      </Step>

      <Step title="Create a scheduled post">
        <CodeGroup>
          ```bash cURL theme={null}
          curl -X POST 'https://api.partoo.co/v2/posts' \
            -H 'Authorization: Bearer YOUR_API_KEY' \
            -H 'Content-Type: application/json' \
            -d '{
              "business_search": {
                "business__in": ["5409c35a97bbc544d8e26737"]
              },
              "publishers": ["google_my_business", "facebook"],
              "data": {
                "post_type": "news",
                "summary": "Happy New Year! We wish you a prosperous 2026! 🎉",
                "schedule_time": "2025-01-01T00:00:00Z"
              }
            }'
          ```

          ```python Python theme={null}
          import requests
          from datetime import datetime

          # Schedule post for New Year's Day at midnight
          schedule_time = datetime(2026, 1, 1, 0, 0, 0)

          response = requests.post(
            'https://api.partoo.co/v2/posts',
            headers={
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            json={
              'business_search': {
                'business__in': ['5409c35a97bbc544d8e26737']
              },
              'publishers': ['google_my_business', 'facebook'],
              'data': {
                'post_type': 'news',
                'summary': 'Happy New Year! We wish you a prosperous 2026! 🎉',
                'schedule_time': schedule_time.strftime('%Y-%m-%dT%H:%M:%SZ')
              }
            }
          )
          ```

          ```javascript Node.js theme={null}
          // Schedule post for New Year's Day at midnight
          const scheduleTime = new Date('2026-01-01T00:00:00Z');

          const response = await fetch('https://api.partoo.co/v2/posts', {
            method: 'POST',
            headers: {
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              business_search: {
                business__in: ['5409c35a97bbc544d8e26737']
              },
              publishers: ['google_my_business', 'facebook'],
              data: {
                post_type: 'news',
                summary: 'Happy New Year! We wish you a prosperous 2026! 🎉',
                schedule_time: scheduleTime.toISOString()
              }
            })
          });
          ```
        </CodeGroup>

        <Check>
          The post will be created with state `scheduled` and will automatically transition to `pending` at the scheduled time, then to `live` once published.
        </Check>
      </Step>
    </Steps>

    <Tip>
      You can update or delete scheduled posts before they're published. Once they enter the `pending` state, updates are no longer possible (but deletion still works).
    </Tip>
  </Accordion>

  <Accordion title="Create Draft Posts for Validation">
    ## Creating drafts requiring approval before publishing

    Draft posts allow organization admins to create posts that require validation from Business Managers or Group Managers before being published.

    <Info>
      **Draft Post Requirements:**

      * User must have `ORG_ADMIN` role
      * Organization must have `DRAFT_POST` feature enabled
      * Organization must have more than 1 business
      * Local users (Business Managers or Group Managers) must exist to validate drafts
    </Info>

    <Steps>
      <Step title="Check if you can create drafts">
        Verify you have permission to create draft posts:

        <CodeGroup>
          ```bash cURL theme={null}
          curl -X GET 'https://api.partoo.co/v2/posts/allowed_operations' \
            -H 'Authorization: Bearer YOUR_API_KEY'
          ```

          ```python Python theme={null}
          import requests

          response = requests.get(
            'https://api.partoo.co/v2/posts/allowed_operations',
            headers={'Authorization': 'Bearer YOUR_API_KEY'}
          )

          permissions = response.json()
          if permissions['create_draft_posts']:
              print("You can create draft posts!")
          if permissions['validate_draft_posts']:
              print("You can validate draft posts!")
          ```

          ```javascript Node.js theme={null}
          const response = await fetch(
            'https://api.partoo.co/v2/posts/allowed_operations',
            {
              headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
            }
          );

          const permissions = await response.json();
          if (permissions.create_draft_posts) {
            console.log('You can create draft posts!');
          }
          if (permissions.validate_draft_posts) {
            console.log('You can validate draft posts!');
          }
          ```
        </CodeGroup>
      </Step>

      <Step title="Create a draft post">
        Create a post that requires validation before publishing:

        <CodeGroup>
          ```bash cURL theme={null}
          curl -X POST 'https://api.partoo.co/v2/posts' \
            -H 'Authorization: Bearer YOUR_API_KEY' \
            -H 'Content-Type: application/json' \
            -d '{
              "business_search": {
                "group_ids__in": [111, 222]
              },
              "publishers": ["google_my_business", "facebook"],
              "is_draft": true,
              "send_notifications": true,
              "data": {
                "post_type": "news",
                "summary": "Exciting announcement coming soon! Stay tuned.",
                "expiration_time": "2024-12-31T23:59:59Z"
              }
            }'
          ```

          ```python Python theme={null}
          import requests
          from datetime import datetime, timedelta

          # Draft expires in 7 days
          expiration = datetime.now() + timedelta(days=7)

          response = requests.post(
            'https://api.partoo.co/v2/posts',
            headers={
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            json={
              'business_search': {
                'group_ids__in': [111, 222]
              },
              'publishers': ['google_my_business', 'facebook'],
              'is_draft': True,
              'send_notifications': True,
              'data': {
                'post_type': 'news',
                'summary': 'Exciting announcement coming soon! Stay tuned.',
                'expiration_time': expiration.strftime('%Y-%m-%dT%H:%M:%SZ')
              }
            }
          )

          if response.status_code == 200:
              result = response.json()
              print(f"Draft created: {result['ids']}")
              print("Notification emails sent to Business/Group Managers")
          ```

          ```javascript Node.js theme={null}
          // Draft expires in 7 days
          const expiration = new Date();
          expiration.setDate(expiration.getDate() + 7);

          const response = await fetch('https://api.partoo.co/v2/posts', {
            method: 'POST',
            headers: {
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              business_search: {
                group_ids__in: [111, 222]
              },
              publishers: ['google_my_business', 'facebook'],
              is_draft: true,
              send_notifications: true,
              data: {
                post_type: 'news',
                summary: 'Exciting announcement coming soon! Stay tuned.',
                expiration_time: expiration.toISOString()
              }
            })
          });

          const result = await response.json();
          console.log(`Draft created: ${result.ids}`);
          console.log('Notification emails sent to Business/Group Managers');
          ```
        </CodeGroup>

        <Info>
          When `send_notifications: true`, all Business Managers and Group Managers with access to the businesses will receive an email notification about the draft post requiring validation.
        </Info>

        <Warning>
          Draft posts **must** include an `expiration_time`. If not validated before this time, the draft will expire and cannot be published.
        </Warning>
      </Step>

      <Step title="Validate and publish draft">
        Business Managers or Group Managers can validate drafts by updating them:

        <CodeGroup>
          ```bash cURL theme={null}
          curl -X PUT 'https://api.partoo.co/v2/posts/POST_ID' \
            -H 'Authorization: Bearer BM_OR_GM_API_KEY' \
            -H 'Content-Type: application/json' \
            -d '{
              "summary": "Updated announcement: Grand opening next week!"
            }'
          ```

          ```python Python theme={null}
          import requests

          # Business Manager or Group Manager validates the draft
          response = requests.put(
            f'https://api.partoo.co/v2/posts/{post_id}',
            headers={
              'Authorization': 'Bearer BM_OR_GM_API_KEY',
              'Content-Type': 'application/json'
            },
            json={
              'summary': 'Updated announcement: Grand opening next week!'
            }
          )

          if response.status_code == 200:
              print("Draft validated and will be published!")
          ```

          ```javascript Node.js theme={null}
          // Business Manager or Group Manager validates the draft
          const response = await fetch(`https://api.partoo.co/v2/posts/${postId}`, {
            method: 'PUT',
            headers: {
              'Authorization': 'Bearer BM_OR_GM_API_KEY',
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              summary: 'Updated announcement: Grand opening next week!'
            })
          });

          if (response.ok) {
            console.log('Draft validated and will be published!');
          }
          ```
        </CodeGroup>

        <Check>
          Once a Business Manager or Group Manager updates the draft, it transitions from `to_validate` to `pending` and will be published to the selected platforms.
        </Check>
      </Step>
    </Steps>
  </Accordion>

  <Accordion title="Add Images to Posts">
    ## Including images in your posts

    Images make your posts more engaging and increase visibility on platforms.

    <Steps>
      <Step title="Understand image requirements">
        Different platforms have different image requirements:

        <Tabs>
          <Tab title="Google My Business">
            * **Limit**: 1 image
            * **Min size**: 250 × 250px
            * **Max size**: 10000 × 10000px
            * **Max weight**: 5MB
            * **Formats**: JPEG, JPG, PNG
          </Tab>

          <Tab title="Facebook">
            * **Limit**: Up to 10 images
            * **Max weight**: 10MB
            * **Formats**: JPEG, JPG, PNG, GIF
          </Tab>

          <Tab title="Instagram">
            * **Limit**: 1-10 images (at least 1 required)
            * **Max weight**: 5MB
            * **Formats**: JPEG, JPG only
          </Tab>

          <Tab title="Store Locator">
            * **Limit**: 1 image
            * **Max weight**: 5MB
            * **Formats**: JPEG, JPG, PNG
          </Tab>
        </Tabs>
      </Step>

      <Step title="Create post with images">
        Add images to your posts using the `image_url` field:

        <CodeGroup>
          ```bash cURL theme={null}
          curl -X POST 'https://api.partoo.co/v2/posts' \
            -H 'Authorization: Bearer YOUR_API_KEY' \
            -H 'Content-Type: application/json' \
            -d '{
              "business_search": {
                "business__in": ["5409c35a97bbc544d8e26737"]
              },
              "publishers": ["google_my_business", "instagram"],
              "data": {
                "post_type": "news",
                "summary": "Check out our brand new storefront!",
                "image_url": "https://example.com/storefront-image.jpg"
              }
            }'
          ```

          ```python Python theme={null}
          import requests

          response = requests.post(
            'https://api.partoo.co/v2/posts',
            headers={
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            json={
              'business_search': {
                'business__in': ['5409c35a97bbc544d8e26737']
              },
              'publishers': ['google_my_business', 'instagram'],
              'data': {
                'post_type': 'news',
                'summary': 'Check out our brand new storefront!',
                'image_url': 'https://example.com/storefront-image.jpg'
              }
            }
          )
          ```

          ```javascript Node.js theme={null}
          const response = await fetch('https://api.partoo.co/v2/posts', {
            method: 'POST',
            headers: {
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              business_search: {
                business__in: ['5409c35a97bbc544d8e26737']
              },
              publishers: ['google_my_business', 'instagram'],
              data: {
                post_type: 'news',
                summary: 'Check out our brand new storefront!',
                image_url: 'https://example.com/storefront-image.jpg'
              }
            })
          });
          ```
        </CodeGroup>
      </Step>

      <Step title="Add multiple images">
        For platforms that support multiple images (Facebook, Instagram), provide an array of URL strings:

        <CodeGroup>
          ```bash cURL theme={null}
          curl -X POST 'https://api.partoo.co/v2/posts' \
            -H 'Authorization: Bearer YOUR_API_KEY' \
            -H 'Content-Type: application/json' \
            -d '{
              "business_search": {
                "business__in": ["5409c35a97bbc544d8e26737"]
              },
              "publishers": ["facebook", "instagram"],
              "data": {
                "post_type": "news",
                "summary": "Our team had an amazing time at the conference!",
                "image_url": [
                  "https://example.com/photo1.jpg",
                  "https://example.com/photo2.jpg",
                  "https://example.com/photo3.jpg"
                ]
              }
            }'
          ```

          ```python Python theme={null}
          import requests

          response = requests.post(
            'https://api.partoo.co/v2/posts',
            headers={
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            json={
              'business_search': {
                'business__in': ['5409c35a97bbc544d8e26737']
              },
              'publishers': ['facebook', 'instagram'],
              'data': {
                'post_type': 'news',
                'summary': 'Our team had an amazing time at the conference!',
                'image_url': [
                  'https://example.com/photo1.jpg',
                  'https://example.com/photo2.jpg',
                  'https://example.com/photo3.jpg'
                ]
              }
            }
          )
          ```

          ```javascript Node.js theme={null}
          const response = await fetch('https://api.partoo.co/v2/posts', {
            method: 'POST',
            headers: {
              'Authorization': 'Bearer YOUR_API_KEY',
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              business_search: {
                business__in: ['5409c35a97bbc544d8e26737']
              },
              publishers: ['facebook', 'instagram'],
              data: {
                post_type: 'news',
                summary: 'Our team had an amazing time at the conference!',
                image_url: [
                  'https://example.com/photo1.jpg',
                  'https://example.com/photo2.jpg',
                  'https://example.com/photo3.jpg'
                ]
              }
            })
          });
          ```
        </CodeGroup>

        <Warning>
          * **Instagram:** Requires at least 1 image (posts without images will fail). Supports JPEG/JPG only.
          * **Google & Store Locator:** Only support 1 image per post
          * **Facebook:** Supports up to 10 images
        </Warning>
      </Step>
    </Steps>
  </Accordion>
</AccordionGroup>

## Managing Existing Bulks

Once you've created posts for multiple businesses (which automatically generates a `bulk_id` internally), Organization Admins and Organization Managers can use bulk management endpoints to search, update, or delete all posts in a bulk at once.

<Tip>
  To get the `bulk_id`, you need to retrieve one of the created posts using its `post_id` (from the create response). The post object contains the `bulk_id` field.
</Tip>

<Warning>
  **Role Restriction**: Bulk management endpoints (`/posts/bulks`, `/posts/count_bulk`, `/posts/delete_bulk`, `/posts/edit_bulk`) are restricted to Organization Admin and Organization Manager roles only.

  While Group Managers can **create** posts for multiple businesses (which generates a bulk\_id), they **cannot** use the bulk management endpoints to search, update, or delete those bulks.
</Warning>

<AccordionGroup>
  <Accordion title="Search Bulk Posts">
    ## Finding and tracking posts in a bulk

    Use the bulk search endpoint to retrieve all posts in a specific bulk. You can use the `bulk_id` if you know it, or search by business/group to find your posts.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X GET 'https://api.partoo.co/v2/posts/bulks?bulk_id=12345' \
        -H 'Authorization: Bearer YOUR_API_KEY'
      ```

      ```python Python theme={null}
      import requests

      response = requests.get(
        'https://api.partoo.co/v2/posts/bulks',
        headers={'Authorization': 'Bearer YOUR_API_KEY'},
        params={'bulk_id': 12345}
      )

      posts = response.json()['posts']
      print(f"Found {len(posts)} posts in this bulk")

      # Check states
      states = {}
      for post in posts:
          state = post['state']
          states[state] = states.get(state, 0) + 1

      print("Post states distribution:")
      for state, count in states.items():
          print(f"  {state}: {count} posts")
      ```

      ```javascript Node.js theme={null}
      const response = await fetch(
        'https://api.partoo.co/v2/posts/bulks?bulk_id=12345',
        {
          headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
        }
      );

      const data = await response.json();
      console.log(`Found ${data.posts.length} posts in this bulk`);

      // Check states
      const states = {};
      data.posts.forEach(post => {
        states[post.state] = (states[post.state] || 0) + 1;
      });

      console.log('Post states distribution:');
      Object.entries(states).forEach(([state, count]) => {
        console.log(`  ${state}: ${count} posts`);
      });
      ```
    </CodeGroup>

    <Tip>
      The `bulk_id` parameter is optional. Without it, `/posts/bulks` returns the list of all posts in bulk. You can combine `bulk_id` with other filters like `state__in`, `topic_type`, etc. The first post in results will contain the `bulk_id` field.
    </Tip>
  </Accordion>

  <Accordion title="Count Posts in a Bulk">
    ## Getting the total count

    Get the total count of posts in a bulk without retrieving all the data:

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X GET 'https://api.partoo.co/v2/posts/count_bulk/12345' \
        -H 'Authorization: Bearer YOUR_API_KEY'
      ```

      ```python Python theme={null}
      import requests

      response = requests.get(
        f'https://api.partoo.co/v2/posts/count_bulk/{bulk_id}',
        headers={'Authorization': 'Bearer YOUR_API_KEY'}
      )

      count = response.json()['count']
      print(f"Total posts in bulk: {count}")
      ```

      ```javascript Node.js theme={null}
      const response = await fetch(
        `https://api.partoo.co/v2/posts/count_bulk/${bulkId}`,
        {
          headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
        }
      );

      const data = await response.json();
      console.log(`Total posts in bulk: ${data.count}`);
      ```
    </CodeGroup>
  </Accordion>

  <Accordion title="Update Bulk Posts">
    ## Modifying content across multiple posts

    You can update all posts in a bulk simultaneously, which is useful for correcting errors or updating information.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST 'https://api.partoo.co/v2/posts/edit_bulk' \
        -H 'Authorization: Bearer YOUR_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "bulk_id": 12345,
          "post_data": {
            "summary": "UPDATED: Extended hours continue through January 15th!"
          }
        }'
      ```

      ```python Python theme={null}
      import requests

      response = requests.post(
        'https://api.partoo.co/v2/posts/edit_bulk',
        headers={
          'Authorization': 'Bearer YOUR_API_KEY',
          'Content-Type': 'application/json'
        },
        json={
          'bulk_id': 12345,
          'post_data': {
            'summary': 'UPDATED: Extended hours continue through January 15th!'
          }
        }
      )

      result = response.json()
      print(f"Updated {result['edited']} posts")
      ```

      ```javascript Node.js theme={null}
      const response = await fetch('https://api.partoo.co/v2/posts/edit_bulk', {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer YOUR_API_KEY',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          bulk_id: 12345,
          post_data: {
            summary: 'UPDATED: Extended hours continue through January 15th!'
          }
        })
      });

      const result = await response.json();
      console.log(`Updated ${result.edited} posts`);
      ```
    </CodeGroup>

    <Info>
      **Platform Behavior:**

      * **Google & Facebook:** Updates propagate asynchronously (a few minutes delay)
      * **Store Locator:** Updates apply instantly
      * **Instagram:** Cannot be edited once live (API limitation)
    </Info>
  </Accordion>

  <Accordion title="Delete Bulk Posts">
    ## Removing multiple posts at once

    Delete all posts in a bulk when content is no longer relevant or needs to be removed.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X POST 'https://api.partoo.co/v2/posts/delete_bulk' \
        -H 'Authorization: Bearer YOUR_API_KEY' \
        -H 'Content-Type: application/json' \
        -d '{
          "bulk_id": 12345
        }'
      ```

      ```python Python theme={null}
      import requests

      response = requests.post(
        'https://api.partoo.co/v2/posts/delete_bulk',
        headers={
          'Authorization': 'Bearer YOUR_API_KEY',
          'Content-Type': 'application/json'
        },
        json={
          'bulk_id': 12345
        }
      )

      result = response.json()
      print(f"Deleted {result['deleted']} posts")
      ```

      ```javascript Node.js theme={null}
      const response = await fetch('https://api.partoo.co/v2/posts/delete_bulk', {
        method: 'POST',
        headers: {
          'Authorization': 'Bearer YOUR_API_KEY',
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          bulk_id: 12345
        })
      });

      const result = await response.json();
      console.log(`Deleted ${result.deleted} posts`);
      ```
    </CodeGroup>

    <Info>
      **Deletion Behavior:**

      * Published posts transition through `pending_deleted` (asynchronous, a few minutes)
      * Unpublished posts are deleted immediately
      * **Instagram:** Only removes Partoo records, not the actual Instagram post (manual deletion required)
    </Info>
  </Accordion>
</AccordionGroup>

## Platform-Specific Guidelines

Quick reference for key differences between platforms:

<Tabs>
  <Tab title="Google My Business">
    **Supported Post Types:** News, Event, Offer

    **Key Limits:**

    * Summary: 1500 characters
    * Title: 58 characters (events/offers)
    * Offer terms: 5000 characters
    * Images: 1 per post

    <Info>
      Only platform supporting all three post types (News, Event, Offer).
    </Info>
  </Tab>

  <Tab title="Facebook">
    **Supported Post Types:** News only

    **Key Limits:**

    * Summary: 10,000 characters
    * Images: Up to 10

    **Capabilities:**

    * ✅ Can edit after publishing
    * ✅ Analytics available (impressions, clicks)
  </Tab>

  <Tab title="Instagram">
    **Supported Post Types:** News only

    **Key Limits:**

    * Summary: 2200 characters
    * Images: 1-10 (at least 1 required)

    **Capabilities:**

    * ✅ Analytics available (reach, interactions)
    * ❌ Cannot edit once live
    * ❌ Cannot delete from Instagram via API

    <Warning>
      Instagram posts cannot be edited or deleted once published. Plan carefully before posting.
    </Warning>
  </Tab>

  <Tab title="Store Locator">
    **Supported Post Types:** News only

    **Key Limits:**

    * Summary: No limit
    * Images: 1 per post

    **Capabilities:**

    * ✅ Can edit and delete freely
    * ✅ No external connection required
    * ❌ No analytics available
  </Tab>
</Tabs>

## Post Lifecycle and States

Understanding post states helps you track the publishing process and troubleshoot issues:

<AccordionGroup>
  <Accordion title="Post States">
    * `to_validate`: draft post awaiting validation from a Business Manager or Group Manager

    * `scheduled`: scheduled for future publishing at the specified `schedule_time`

    * `pending`: queued for publishing

    * `live`: successfully published

    * `to_configure`: configuration error

    * `error`: publisher error

    * `pending_deleted`: queued for deletion

    * `deleted`: successfully deleted from platforms and Partoo

    * `expired`: draft post expired before validation, or post passed its `end_at` date
  </Accordion>

  <Accordion title="State Transitions">
    * Draft posts: `to_validate` → `pending` (after validation) → `live`
    * Scheduled posts: `scheduled` → `pending` (at schedule time) → `live`
    * Immediate posts: `pending` → `live` (or `error`/`to_configure` on failure)
    * Deletions: → `pending_deleted` → `deleted`

    ### Monitoring Post Progress

    State transitions are asynchronous. To monitor post status:

    **Use the search endpoint** `GET /api/v2/posts` with filters like `state__in=live` or `state__in=pending` to check if posts are published successfully.

    <Warning>
      The `/state` endpoint (`GET /api/v2/posts/{post_id}/state`) **only returns error information** for publishers in `to_configure` state. It does NOT show live, pending, scheduled, or `error` state posts. Use it only for troubleshooting configuration issues.
    </Warning>

    ### Troubleshooting Failed Posts

    If your post enters `to_configure` state, use the state diagnostics endpoint to understand what configuration is missing.

    <Info>
      The `/state` endpoint only returns publishers in `to_configure` state. An empty response `{}` means no configuration errors were detected (posts may be pending, scheduled, live, or in other error states).
    </Info>

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X GET 'https://api.partoo.co/v2/posts/12345/state' \
        -H 'Authorization: Bearer YOUR_API_KEY'
      ```

      ```python Python theme={null}
      import requests

      response = requests.get(
        f'https://api.partoo.co/v2/posts/{post_id}/state',
        headers={'Authorization': 'Bearer YOUR_API_KEY'}
      )

      state_info = response.json()

      if not state_info:
          print("✓ No configuration errors detected")
      else:
          print("Configuration errors found:")
          for platform, info in state_info.items():
              print(f"\n{platform}:")
              print(f"  State: {info['state']}")
              print(f"  Error: {info.get('error_message', 'N/A')}")
              print(f"  Config needed: {info.get('expected_error_message', 'N/A')}")
      ```

      ```javascript Node.js theme={null}
      const response = await fetch(
        `https://api.partoo.co/v2/posts/${postId}/state`,
        {
          headers: { 'Authorization': 'Bearer YOUR_API_KEY' }
        }
      );

      const stateInfo = await response.json();

      if (Object.keys(stateInfo).length === 0) {
        console.log('✓ No configuration errors detected');
      } else {
        console.log('Configuration errors found:');
        Object.entries(stateInfo).forEach(([platform, info]) => {
          console.log(`\n${platform}:`);
          console.log(`  State: ${info.state}`);
          console.log(`  Error: ${info.error_message || 'N/A'}`);
          console.log(`  Config needed: ${info.expected_error_message || 'N/A'}`);
        });
      }
      ```
    </CodeGroup>

    <Tip>
      The `expected_error_message` field contains an error key indicating what configuration is missing (e.g., `"publisher_connection_required"`, `"business_is_not_subscribed_to_pm"`).
    </Tip>
  </Accordion>
</AccordionGroup>
